How to manage stack in branched quotations? - factor-lang

If true quotation has zero arguments I can use when word because implicit false quotation also has zero arguments (does nothing).
But when I want to consume argument, I need else branch just to clean-up the stack. If logic were more complex, I imagine it might be tedious and error-prone re-factoring. Is there an easier way?
: print-if-dir ( directory-entry -- ) dup directory? [ name>> . ] [ drop ] if ;

You need to use a smart-when*:
USE: combinators.smart
: print-if-string ( object -- ) [ string? ] [ . ] smart-when* ;
Testing this out in the listener:
scratchpad: 2 print-if-string ! Nothing happens
scratchpad: "2" print-if-string ! Prints "2"
"2"

Related

Bash Subshell Expansion as Parameter to Function

I have a bash function that looks like this:
banner(){
someParam=$1
someOtherParam=$2
precedingParams=2
for i in $(seq 1 $precedingParams);do
shift
done
for i in $(seq 1 $(($(echo ${##}) - $precedingParams)));do
quotedStr=$1
shift
#do some stuff with quotedStr
done
}
This function, while not entirely relevant, will build a banner.
All params, after the initial 2, are quoted lines of text which can contain spaces.
The function fits each quoted string within the bounds of the banner making new lines where it sees fit.
However, each new parameter ensures a new line
My function works great and does what's expected, the problem, however, is in calling the function with dynamic parameters as shown below:
e.g. of call with standard static parameters:
banner 50 true "this is banner text and it will be properly fit within the bounds of the banner" "this is another line of banner text that will be forced to be brought onto a new line"
e.g. of call with dynamic parameter:
banner 50 true "This is the default text in banner" "$([ "$someBool" = "true" ] && echo "Some text that should only show up if bool is true")"
The problem is that if someBool is false, my function will still register the resulting "" as a param and create a new empty line in the banner.
As I'm writing this, I'm finding the solution obvious. I just need to check if -n $quotedStr before continuing in the function.
But, just out of blatant curiosity, why does bash behave this way (what I mean by this is, what is the process through which subshell expansion occurs in relation to parameter isolation to function calls based on quoted strings)
The reason I ask is because I have also tried the following to no avail:
banner 50 true "default str" $([ "$someBool" = "true" ] && echo \"h h h h\")
Thinking it would only bring the quotes down if someBool is true.
Indeed this is what happens, however, it doesn't properly capture the quoted string as one parameter.
Instead the function identifies the following parameters:
default str
"h
h
h
h"
When what I really want is:
default str
h h h h
I have tried so many different iterations of calls, again to no avail:
$([ "$someBool" = "true" ] && echo "h h h h")
$([ "$someBool" = "true" ] && echo \\\"h h h h\\\")
$([ "$someBool" = "true" ] && awk 'BEGIN{printf "%ch h h h h%c",34,34}')
All of which result in similar output as described above, never treating the expansion as a true quoted string parameter.
The reason making the command output quotes and/or escapes doesn't work is that command substitutions (like variable substitutions) treat the result as data, not as shell code, so shell syntax (quotes, escapes, shell operators, redirects, etc) aren't parsed. If it's double-quoted it's not parsed at all, and if it's not in double-quotes, it's subject to word splitting and wildcard expansion.
So double-quotes = no word splitting = no elimination of empty string, and no-double-quotes = word splitting without quote/escape interpretation. (You could do unpleasant things to IFS to semi-disable word splitting, but that's a horrible kluge and can cause other problems.)
Usually, the cleanest way to do things like this is to build a list of conditional arguments (or maybe all arguments) in an array:
bannerlines=("This is the default text in banner") # Parens make this an array
[ "$someBool" = "true" ] &&
bannerlines+=("Some text that should only show up if bool is true")
banner 50 true "${bannerlines[#]}"
The combination of double-quotes and [#] prevents word-splitting, but makes bash expand each array element as a separate item, which is what you want. Note that if the array has zero elements, this'll expand to zero arguments (but be aware that an empty array, like bannerlines=() is different from an array with an empty element, like bannerlines=("")).

What do these symbols mean in the RFC docs regarding grammars?

Here are the examples:
Transfer-Encoding = "Transfer-Encoding" ":" 1#transfer-coding
Upgrade = "Upgrade" ":" 1#product
Server = "Server" ":" 1*( product | comment )
delta-seconds = 1*DIGIT
Via = "Via" ":" 1#( received-protocol received-by [ comment ] )
chunk-extension= *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
http_URL = "http:" "//" host [ ":" port ] [ abs_path [ "?" query ]]
date3 = month SP ( 2DIGIT | ( SP 1DIGIT ))
Questions are:
What is the 1#transfer-coding (the 1# regarding the rule transfer-coding)? Same with 1#product.
What does 1 times x mean, as in 1*( product | comment )? Or 1*DIGIT.
What do the brackets mean, as in [ comment ]? The parens (...) group it all, but what about the [...]?
What does the *(...) mean, as in *( ";" chunk-ext-name [ "=" chunk-ext-val ] )?
What do the nested square brackets mean, as in [ abs_path [ "?" query ]]? Nested optional values? It doesn't make sense.
What does 2DIGIT and 1DIGIT mean, where do those come from / get defined?
I may have missed where these are defined, but knowing these would help clarify how to parse the grammar definitions they use in the RFCs.
I get the rest of the grammar notation, juts not these few remaining pieces.
Update: Looks like this is a good start.
Square brackets enclose an optional element sequence:
[foo bar]
is equivalent to
*1(foo bar).
Specific Repetition: nRule
A rule of the form:
<n>element
is equivalent to
<n>*<n>element
That is, exactly <n> occurrences of <element>. Thus, 2DIGIT is a
2-digit number, and 3ALPHA is a string of three alphabetic
characters.
Variable Repetition: *Rule
The operator "*" preceding an element indicates repetition. The full
form is:
<a>*<b>element
where <a> and <b> are optional decimal values, indicating at least
<a> and at most <b> occurrences of the element.
Default values are 0 and infinity so that *<element> allows any
number, including zero; 1*<element> requires at least one;
3*3<element> allows exactly 3; and 1*2<element> allows one or two.
But what I'm still missing is what the # means?
Update 2: Found it I think!
#RULE: LISTS
A construct "#" is defined, similar to "*", as follows:
<l>#<m>element
indicating at least <l> and at most <m> elements, each separated
by one or more commas (","). This makes the usual form of lists
very easy; a rule such as '(element *("," element))' can be shown
as "1#element".
Also, what do these mean?
1*2DIGIT
2*4DIGIT

How to call bash function with boolean parameter?

I have a function called install_cont that is called twice from my bash script. However, inside that function there is a code block that I want to execute only if the boolean parameter is true. In Python I would do:
def install_cont(expres=False):
if expres:
# Do some code...
install_cont(True)
install_cont() # Can call with default False
How do I achieve this in bash? Reading online, I understand that all variables are string. However, what is the best way to achieve a boolean-like parameter?
Shell scripting doesn't have booleans, but it does have a convention for representing success as an integer. Confusingly for programmers coming from other languages, this is that 0 is success, and anything else is failure.
This is the basis of the if construct which runs a command, and tests its result for success. You can't test a variable directly with if, but you can use the test built-in, which "succeeds" if a particular test passes.
test is also spelled [ which is why you'll often see code with if [ ... ] - those brackets aren't part of the if syntax, they're just a command with a funny name. (Bash also has a [[ built-in with some extra features, but [ is more standard, so worth learning.)
The test command can perform various tests - examining numbers, checking the file system, etc. For a command-line option, you could test against the string true or false with the = binary operator.
So what you end up with is something like this:
install_cont() {
# Name the parameter for convenience
local expres="$1";
if [ "$expres" = "true" ];
then
# Do some code...
fi;
}
install_cont true
install_cont # default will not pass the test
If you want fancier parameters, you can have actual defaults with the slightly awkward ${foo:-default} and ${foo:=default} parameter expansion syntaxes. For example:
install_cont() {
# Default the mode to "boring"
local mode="${1:-boring}";
if [ "$mode" = "exciting" ];
then
# Do some code...
elif [ "$mode" = "boring" ];
then
# Do some code...
else
echo "Unrecognised mode";
fi;
}
Like this perhaps.
your_func () {
if [[ $1 ]]; then
# do things if parameter was present
fi
# do unconditional things
}
Bash on it's own does not have built-in boolean handling. What it does is handles strings and integers. However, you can define strings and numbers for definite exit status codes and make pseudo-boolean logic by design. Where ever you see true or false in bash, you are seeing constructs with specific exit codes. Since the default command exit codes are 0 or 1, unless you assign one to the contrary, you basically have a built-in method, if not inherently for the purpose, to perform boolean logic.
Using the test command with subsequent [] or [[]] will output a 0 for true or a 1 for false. But these are merely exit status codes.
Unix provides two commands:
true: return true value (i.e. exit value is 0)
false: return false value (i.e. exit value is non-0)
You can use them in if, while and until constructs.
Example:
install_cont()
{
local expres="${1:-false}" # Default value is false
if "$expres"; then
# Do some code...
else
# Do something else...
fi
}
install_cont true
install_cont # false implied
You are using Bash. If you want booleans that are also valid in arithmetic operations, then you can also use arithmetic booleans:
declare -i false=0 true=1
cond1="$true"
cond2="$false"
if ((cond1 || cond2)); then
# Do some code...
fi
Personally, I don't use arithmetic booleans as I'm perfectly happy with true and false commands.
One of the tricky things about bash and other Bourne-derived shells is that a condition used in an if or while statement is not an expression. It's a command, and the condition is treated as true or false depending on whether the command succeeded or failed. A command indicates success by returning an exit status of 0, and failure by returning anything else.
The built-in commands true and false do nothing other than returning an exit status denoting success or failure, respectively.
The [ ... ] and [[ ... ]] constructs can be used to simulate using an expression as a condition. [ is actually a command, very nearly equivalent to the test command.
If I need the equivalent of a Boolean variable in a bash script, I use a variable whose value is either the string true or the string false. If I use that variable as a condition, it expands to the command name true or false. I just have to be careful to ensure that such a variable never takes some value other than true or false.
And it's important to remember not to use the [ ... ] syntax. With a single argument, for example
if [ false ] ; then # This is incorrect!
the command succeeds because false is a non-empty string. Remember that the [ and ] are not part of the syntax of an if statement (though they're designed to look like they are).
An example:
#!/bin/bash
func() {
if $1 ; then
echo In func, the condition is true
else
echo In func, the condition is false
fi
}
func false
func true
The output:
In func, the condition is false
In func, the condition is true
This technique does have some drawbacks. It can break if you assign a value other than exactly true or false to a variable, or even if you misspell the name of a variable. And unlike in languages with a built-in Boolean type and false and true literals, the shell is likely to fail to diagnose such errors, causing your script to silently misbehave.

Match balanced occurrences of nested tag

I have a test string:
s = "A test [[you|n|note|content of the note with a [[link|n|link|http://link]] inside]] paragraph. wef [[you|n|note|content of the note with a [[link|n|link|http://link]] inside]] test".
I need to match the occurrences of the [[...]] parts of the string. There can be up to the second level of nested [[ ]] tags in the string (as shown in the test string).
I started with /\[\[.*?\]\]/, but that only matches the following:
[[you|n|note|content of the note with a [[link|n|link|http://link]] (it's missing the last occurrence of the ]].
How do I go about matching the remainder of each [[ .. ]] block? Is this possible with regex?
If you don't have single isolated [ or ], then it is pretty much simple. The following assumes no restriction on the nested level.
s.scan(/(?<match>\[\[(?:[^\[\]]|\g<match>)*\]\])/).flatten
returns:
[
"[[you|n|note|content of the note with a [[link|n|link|http://link]] inside]]",
"[[you|n|note|content of the note with a [[link|n|link|http://link]] inside]]"
]
Here's a non-regex solution. I've assumed left (right) brackets always appear in pairs.
level = 0
s.each_char.each_cons(2).with_index.with_object([]) do |(pair, i), a|
case pair.join
when "[["
level += 1
a << i if level==1
when "]]"
a << i+1 if level==1
level -= 1
end
end.each_slice(2).map { |b,e| s[b..e] }
#=> ["[[you|n|note|content of the note with a [[link|n|link|http://link]] inside]]",
# "[[you|n|note|content of the note with a [[link|n|link|http://link]] inside]]"]

Parenthesis and regular expressions [duplicate]

This question already has answers here:
Finding parenthesis via regular expression
(3 answers)
Closed 9 years ago.
I am currently working on an exercise in which I need to locate all the occurring (),[],{}, both grouped or single, and I can't figure out the regular expression. I do not need the text in between. I already filtered them out of my string with this:
string_updated = string.gsub(/([a-zA-Z]|\d+)|\s+/, "")
For example. in this string:
"I can't ( find the } regular ] expression to ) grab these[."
All I want is: (, }, ], ), [.
You should be able to do that with ([\(\[\{]).*([\)\]\}])
Here is a working example: http://regex101.com/r/qH1uF5
You should use the below regex:
([][)(}{])
Explanations:
( # Start capturing group
[ # Match any of these characters
][)(}{ # Desired chars
] # End of char group
) # End of capturing group
Live demo
What about:
a = "I can't ( find the } regular ] expression to ) grab these[."
brackets = %w|( ) [ ] { }|
puts a.scan(Regexp.union(brackets)).join(', ') #=> (, }, ], ), [

Resources