Alma : A Language for MAcro processing.

Alma is a general purpose macro-language powered by the Ruby programming language. Alma can be used to generate text documents in programming languages, markup languages, human languages and maybe other languages.

Alma lets you use any Ruby method in the same way you use macros, thus, out of the box, Alma comes with thousands of built-in functions.

Contents.

1 Introduction.
1.1 Overview.
1.2 A note to 'Rubyists'.
1.3 About this document.
2 The Alma language.
2.1 Simple Examples.
2.1.1 Hello World!
2.1.1.1 The Hello World! document.
2.1.1.2 Notes about syntax.
2.1.2 Duck Soup.
2.1.2.1 Duck Soup documents.
2.1.2.2 The Duck Soup project.
2.2 Grammar.
2.2.1 BNF notation.
2.2.2 Quoting.
2.2.3 Variable expansion and function calls.
2.2.4 Semantic actions.
2.3 Variables.
2.3.1 Access to variables.
2.3.2 Variables scope.
2.3.3 Alma specific variables sets.
2.3.4 Predefined variables and constants.
2.4 Functions.
2.4.1 Invocation.
2.4.1.1 Function arguments.
2.4.1.2 Keyword arguments supplied by Alma.
2.4.1.3 Methods and Macros.
2.4.2 Methods.
2.4.2.1 Method invocation.
2.4.2.2 Access to positionnal arguments.
2.4.2.3 Access to keyword arguments.
2.4.3 Macros.
2.4.3.1 Simple examples
2.4.3.2 Wrapping methods.
2.4.3.3 An example which comes from the real world.
2.4.3.4 Defining macros in separate files.
2.4.4 Built-in functions.
2.4.4.1 The void method.
2.4.4.2 Iterators.
2.4.4.3 If / unless.
2.4.4.4 While / until.
2.4.4.5 File Access.
2.4.4.6 Parser calls.
2.4.4.6.1 Simple examples
2.4.4.6.2 Parser calls vs Macro calls.
2.4.4.6.3 Overriding functions.
2.4.4.7 Objects creation.
2.4.4.8 Flavours.
2.4.4.8.1 Defining a flavour.
2.4.4.8.2 Changing flavours.
2.4.4.8.3 Built-in flavours.
2.4.4.9 Stream hooks.
2.4.4.10 Filters.
2.4.4.10.1 Text filters.
2.4.4.10.2 File filters.
2.4.4.11 Termination, errors and debugging
2.4.4.12 Parse_Array.
2.4.4.13 Miscellaneous methods.
2.5 More examples.
2.5.1 Symbolic Substitution.
2.5.2 Interactive Alma.
2.5.3 Alma to XML.
2.5.4 A 'real world' example : tinyDoc.
2.5.5 What you should not do in Alma.
3 The Alma API.
4 Using Alma.
4.1 Invoking Alma.
4.2 Installation.
4.3 Bugs and deficiencies.
5 Legal informations.

1 Introduction.

First, I have to apologize for my poor English.

1.1 Overview.

What is Alma ?

Alma is a general purpose macro-language, it may be used to generate text in other languages (or even in Alma).

Should I use Alma ? Why and when ?

Due to its specificities, Alma may be an alternative or a complement to other macro-languages and you may want to use it for the following reasons :

Which applications can use Alma ?

Application of any kind may use Alma with a little profit, but some kinds of application may use it with big profits.

1.2 A note to 'Rubyists'.

Ruby language practitioners are likely to be happy to find another way to use Ruby, however they may be deceived by the Alma syntax. This syntax is designed to avoid, as much as possible, quoting text, it is not designed for elegance. However, it is possible to write nice document sources, especially by using keyword arguments.

Alma main purpose is to structure source documents and to generate object documents with the help of Ruby, not to be a programming language. Many of its possibilities had been carried out for language completeness, not for intensive use.
Along with the variable expansion facility, Alma is mostly a way to call Ruby methods while parsing a document and should be considered as a thin layer between the documents and Ruby. Alma macros are not much useful as you can write Ruby methods instead which is easier, more readable and far more pleasant too. It is also faster to write and generally to execute.
Many examples in this documentation go far beyond what one needs to do in Alma.

An interesting Alma feature is its possibility to call macros with keyword arguments. Thus Alma macros can be used to wrap Ruby methods calls and supply their positional arguments in the right order. This can make the life of Alma document writers nicer and the resulting documents sources more readable.
This wrapping can be applied to already existing methods, but if you write a method specifically designed to be used from an Alma document, Alma provides Ruby methods a way to retrieve keyword arguments. Thus you can also write Ruby methods to wrap already existing Ruby methods.

The Alma parser is written as a Ruby C extension.

1.3 About this document.

You should have some basic knowledge of the the Ruby Language (what can be learned in an hour is enough) to read this documentation, but a knowledge of another Object-Oriented scripting language or even any programming language is sufficient to understand most of the Alma features.
This introduction to Ruby can help you if you have never heard of Ruby.

This document should be read from left to right and downward. Some parts might be boring, read them quickly but read the examples carefully, When you do not understand them, it is likely you missed something a bit above.

2 The Alma language.

This chapter describe the standard flavour of Alma, all its lexical units like '{', '}', '$' etc. can be changed.

While parsing a file or a string, the Alma parser outputs this string unchanged unless it finds an Alma expression which is generally placed there to modify the parser output.
These expressions may be variable expansions, function (macro or method) calls, comments, quoted text... etc.

2.1 Simple Examples.

These simple examples are sufficient to understand the basic features of Alma.
The 'Hello World!' example introduces us to the Alma syntax but may fail to make you understand Alma usefulness while the 'Duck soup' example has this goal.

2.1.1 Hello World!

2.1.1.1 The Hello World! document.
Here is an 'Hello World!' Alma implementation :
hello world
this document, say hello.alma, when supplied to the alma.rb program by this command line :
alma.rb -i hello.alma
produces :
hello world

Although simplistic this document is erroneous, The 'Hello World' specifications are clear, a correct hello-world program must produce 'Hello World!' not 'hello world' or whatever. Conformance to specifications and normative recommandations is an important issue in software development.

You probably have an idea about how to fix this document and this idea is probably the best one, however programmers sometimes do not like simple solutions to simple problems, have a look at this bug fix :

&setv{ ;hi;hello; }
&setv{ ;you;world; }
&capitalize{ ;&${hi}; } &capitalize{ ;&${you}; }!

This document introduces us to the most important Alma expressions.
&setv{ ;hi;hello; } set the variable hi to the string 'hello'.
&setv{ ;you;world; } set the variable you to the string 'world'. Ok, you understand quickly.
&${hi} is expanded to the variable hi value.
&capitalize{ ;&${hi}; } is the capitalize method invocation for the class String object &${hi} (which returns a copy of the object with the first character converted to uppercase and the remainder to lowercase).
Here &setv{ } et &capitalize{ } are called functions, they are both methods but macros are called in the same way.

This solution seems quite good, however have a look at the results :

hello
world
Hello World!
This still not meets specifications requirements !
The reason is simple. The Alma parser outputs everything outside an expression and everything an expression returns.
&setv{ ;foo;blah; } returns 'blah', it is one of its specifications. However, it is possible to have it return nothing, to only put 'blah' in the foo variable.
This is done simply by writing :
&.setv{ ;foo;blah; }
the dot after the '&' and before the function name inhibits the function output.
A problem remains : the newline which follows the function invocation. You can put comments in Alma, these comments are not output, just ignored, a comment starts with &# and ends with a newline which is included in the comment.
We can fix the Alma document like this :
&.setv{ ;hi;hello; }&#	set hi to 'hello'
&.setv{ ;you;world; }&#	set you to 'world'
&capitalize{ ;&${hi}; } &capitalize{ ;&${you}; }!
It will produce the expected result :
Hello World!
Exactly 'Hello World!' followed by a newline.

Another solution is to use the void function which only returns its positional arguments after they had been evaluated, if we consider a function as a filter for its argument we can say it does nothing. Its name is an empty string and it is not even actually implemented.
When called with a dot before the empty function name, its arguments are not returned, only evaluated.
&.{ whatever } returns nothing but can have side-effects.
The following code produce a result which conforms to the Hello World! specifications.

&.{
This is version 3.1 of Alma Hello.
Alma Hello is free software ...
	&setv{ ;hi;hello; }
	&setv{ ;you;world; }
}&#
&capitalize{ ;&${hi}; } &capitalize{ ;&${you}; }!

We can notice that comment lines do not begin with &#, they will only be discarded as they do not contains anything which can have side effects.

Happy ends actually exist. The Alma Hello project team experienced one, vou can verify in the sources of 'Alma Hello' last stable version :

&#Self explanatory, isn't it ?
Hello World!

You may wish to compare this source with 'Hello World!' implementations in other languages on these sites :
The Hello World Collection.
The ACM Hello World! Project.

2.1.1.2 Notes about syntax.

The syntax may seems a bit strange, but there are reasons for this.

The following 2 notations are equivalent :
&setv{;hi;hello;} 
&setv{ ;
    hi;
    hello;
} 

2.1.2 Duck Soup.

2.1.2.1 Duck Soup documents.

This example makes use of two Alma document sources.

&# file  duck_soup.alma
&file_call{
    _call=soup.alma
    animal=duck
    cabbage=green
    author=`Dr John Dolittle`
    year=2005
}&#
It parses the file 'soup.alma' after supplying its local keyword arguments.
The comment indicating the file name in the first line of a source tells that this file is in the test directory of the Alma distribution. The command
alma.rb -i duck_soup.alma
gives the following result :
The true duck soup with green cabbages.
    
For this recipe, you need
. a duck.
. a green cabbage, 3 onions, salt.

Threw the green cabbage into boiling water with onions,
salt and wait 30 minutes before turning off,
Let the soup cool down before feeding the duck.

Copyright (C) 2005 Dr John Dolittle. All rights reserved.
The true duck soup with green cabbages is free recipe.
It may be fed to ducks with or without seasoning.

The file 'soup.alma' aims to be reusable and stands as an Alma recipe library in a directory known by Alma.

&# file  soup.alma
The true &${animal} soup with &${cabbage} cabbages.
    
For this recipe, you need
. a &${animal}.
. a &${cabbage} cabbage, 3 onions, salt.

Threw the &${cabbage} cabbage into boiling water with onions,
salt and wait 30 minutes before turning off,
Let the soup cool down before feeding the &${animal}.

Copyright (C) &${year} &${author}. All rights reserved.
The true &${animal} soup with &${cabbage} cabbages is free recipe.
It may be fed to &${animal}s with or without seasoning.

Although this simple example only uses variable substitution, it is an easy way to make recipes using a template file which can be reused for the Goose soup project,

There is another way to get the same result using the same library file by using this alma source.

&# file  duck.alma
&.setv{ ;animal;duck; }&#
&.setv{ ;cabbage;green; }&#
&.setv{ ;author;Dr John Dolittle; }&#
&.setv{ ;year;2005; }&#
which probably stands in the current directory.
The following command line gives the same result.
alma.rb -p duck.alma -i soup.alma
Here 'duck.alma' is parsed (sourced) before parsing (sourcing) the input file 'soup.alma'. Later, we'll speak more about variables and parsing as sourcing or not.
2.1.2.2 The Duck Soup project.

In an Alma project like 'Duck soup', several kinds of people are involved, one who writes methods and macros (in the projects above there were none), one who writes document templates (maybe using functions), one who use theses templates and functions.
The distinction is not always so clear, some of these kinds of people may be embodied in the same person. For instance, In the 'Duck soup' project, the same person wrote the 2 documents, he also made the soup, fed the ducks and was seriously attacked by geese.

2.2 Grammar.

You should read this chapter, however, if you find formalism boring, read it quickly, but read the examples which are likely to be sufficient to understand the Alma grammar.

2.2.1 BNF notation.

This pseudo BNF notation grammar in not actually correct, I simplified it a little for better readability. Maybe it is buggy too, the Alma parser is 'hand-made' and not developped using a parser generator like bison and this grammar has not been checked.
I just hope it is human-readable.

alma_source       : <END_OF_DATA>
                  | expressions <END_OF_DATA>

expressions       :
                  | alma_expressions 
				
alma_expressions  : expr
                  | expr alma_expressions

expr              : <TEXT>
                  | var_expr
                  | func_expr
                  | quoted_ch
                  | quoted_str

var_expr          : "&${" alma_expressions "}"

func_expr         : "&" func_name "{" func_parms "}"
                  : "&" "." func_name "{" func_parms "}"

func_name         :
                  | <NAME>

func_parms        : keyword_parms
                  | keyword_parms ";" posit_parms

keyword_parms     :
                  | spaces keyword_parms
                  | keyword_parms spaces
                  | kw_expr <SPACE> keywords_parms

spaces            :
                  : <SPACE> spaces

kw_expr           : alma_expressions "=" alma_expressions

posit_parms       : posit_parms p_parm

p_parm            : spaces expression ";"
               
quoted_char       : "\" <CHAR>

quoted_string     : <OPENING_QUOTE> <STRING> <CLOSING_QUOTE>

You can notice recursion by the rules 'Alma-expression' which are often referenced directly or not. Any Alma expression may contain any Alma expression, this helps to keep the grammar and the parser simple.
However, the Alma parser has a fixed recursion arbitrary limit which correspond to the curly braces nesting, this limit value is 8192, because I noticed that, beyond this number, some people have difficulties to match braces (even when indented).

You can also notice the lack of conditional and iterative constructs, they are not implemented as language constructs but as functions.

2.2.2 Quoting.

Opening and closing quotes can be different, this allows quotes nesting which may be useful when defining Alma macros. However, the Alma standard flavour do not use this possibility and the same character (a backquote) is used as an opening and closing quote

To compare Alma standard flavour with other languages, the braces stand for parentheses, the semicolon for a comma, the backquote for single or double quote. The ampersand is a kind of escape character to detect Alma specific expressions such as functions calls, variable expansion and comments.

The reason is pragmatic, this flavour is designed mostly to embed Alma in natural language texts or programming language (especially Ruby) sources and/or generate text in these languages without the need to quote these texts.

However, when using the standard flavour, you should quote some text characters : These characters can be individualy quoted using a backslash, you mostly need to use backquotes in the following circumstances : Therefore, not so often.

Quoted text is not evaluated, but the enclosing quoting chars are removed.
Inside backquotes, a quoted backquote \`is transformed into a backquote and a quoted backslash \\ into a backslash \.
`This \`is\` an example` will give : This `is` an example.
`This is \another` will give : This is \another.
Outside backquotes(`), backslashes are removed and the following character is not evaluated, you can quote backslashes \ by writing \\.

Remember that all these characters (backquote, backslash, ampersand etc.) can be dynamically changed.

2.2.3 Variable expansion and function calls.

When an Alma document is parsed, whatever appears outside an Alma expression is output, quoted characters and strings are output as describe above. The result of function calls and variable expansions is also output. Comments are discarded,
Inside Alma expressions such as function call or variable expansion constructs, whatever appears within braces is evaluated in the same way.
In a variable expansion construct, the evaluation result will be a variable name. A variable expansion has this format :
&${expr}
In a function construct, semicolons (unless quoted) are also evaluated as delimitors. A function call has this format :
&func{ [keywords args] [;posit. arg ; ... ;] }
All this arguments are optionals but positional arguments number must correspond to the number of arguments expected by the functio when this function is a method, this number should correspond when it is a macro.

Function names cannot be built out of Alma expressions, maybe it will be implemented.

Note that a flavour can change the function call syntax and lets you omit the disgraceful but useful semicolon after the last positional argument and before the closing brace.

Examples

&${foo}
The foo variable is expanded, the value of the variable foo is returned, In fact, as in Ruby, it is a reference to a value.

&foo{}
The foo function is called with no argument,

&foo{ ; bar ; }
The foo function is called with one positional argument, the string 'bar '.

&foo{ myArg=bar }
The foo function is called with one keyword argument 'myArg' which takes the string 'bar' as value.

&foo{ myArg=&${bar} ; &foobar{ ;&${blah}; }; }
The foo function is called with one keyword argument 'myArg' which takes the expansion of the bar variable as value.
It also has an positional argument, the result of the foobar function.
The foobar function takes one argument, it is called with the result of the expansion of the 'blah' variable as this argument.
Arguments of a function is evaluated from left to right before the function call.
Which means in this example :
Variable bar is evaluated first, then variable blah, then the foobar function is evaluated (called), then the foo function is evaluated.

&${foo{ ;bar; }}
The result of the foo function call is used as a variable name and expanded.

2.2.4 Semantic actions.

the output of functions or variable expansions are generaly character strings as Alma is not designed for scientific calculation or comptability, but even for text processing you may need to deal with numbers, arrays, hashes, files etc. for example, in this Alma documentation of Alma to compute the chapters number.

Strings of sub-expressions are concatenated to build an expression result. when a sub-expression evaluation happens not to be a string, it replaces the previous value of the expression. Ad contrario, when this sub-expression happens to be a string and the previous value not to be a string, it do not replace this previous value.

I am not sure to be clear, let us look at some examples :
&${1} is the expansion of the variable '1' which is hard-wired to return the integer 1 (we'll talk about build-in variables later).
&${1} also returns the integer 1 as foo ${1} bar
foo ${1} foo &${2} returns the integer 2.

It is a kind of priority concerning types. All types except strings have the same priority, strings have a lower priority.
if you want to concatenate strings corresponding to integers, you can just use the Ruby method to_str
foo &to_str{ ;&${aNum}; } returns foo 5 if variable aNum has the integer 5 as value.
Hmmm... Not excessivly readable... In the next chapter, we'll discuss about functions which deal with variables. Just know that the example above can be more clearly written as :
foo &v_to_s{ ;aNum; }

2.3 Variables.

2.3.1 Access to variables.

As discussed above, arguments to functions are the character strings provided as arguments unless arguments are the result of an Alma axpression (variable expansion or function call). thus
&capitalize{ ;varName;} returns the string 'Varname' which is not much useful.
If varName is set to the string 'hello'.
&capitalize{ ;&${varName};} returns the string 'Hello' which may be more useful, although the explicit variable expansion is not much graceful.

Alma provides methods which accept variable names as arguments, In the Hello World! chapter. we already used the &setv{ ;varname;value; } function,
The alma variable expansion construct &${varname} is the &getv{;varname;} method call.
These two methods are the Alma basic variable access functions. They may be used from an Alma document or a Ruby script which (except for Alma specific variable sets discussed in next chapter) is unuseful since Ruby lets you reference variables by their name.
In a Ruby script, the notation setv( 'varname', value ) correspond to the Alma notation above.

In addition to these basic methods, Alma provides handfuls of methods to access variables by their name, They are trivial and their Ruby definition are far more explanatory than any explanation I could present. However they are mostly useful if you write macros but if you needs such methods, you had better write ruby methods instead.

# conversions
def v_to_s( fromVar )   # anything to string
    getv( fromVar).to_s
end

def v_to_i( fromVar )   # anything to integer
    getv( fromVar).to_i
end

def v_cp!( toVar,fromVar )  # copy  fromVar into toVar
    setv( toVar, getv( fromVar) )
end

def v_push!( toVar,fromVar )    #
    getv( toVar ) << getv( fromVar )
end

def v_pop( fromVar )
    getv( fromVar ).pop
end

def v_pop!( toVar,fromVar )
    setv( toVar, getv( fromVar ).pop )
end

def v_dup!( toVar,fromVar )
    setv( toVar, getv( fromVar).dup )
end

def v_to_s!( toVar,fromVar )
    setv( toVar, getv( fromVar).to_s )
end

def v_to_i!( toVar,fromVar )
    setv( toVar, getv( fromVar).to_i )
end

# integers, methods modifying toVar
def i_set!( toVar, num )
    setv( toVar, getv( num ).to_i )
end

def i_plus!( toVar, num )
    setv( toVar, getv( toVar ).to_i + getv( num ).to_i )
end

def i_minus!( toVar, num )
    setv( toVar, getv( toVar ).to_i - getv( num ).to_i )
end

def i_mult!( toVar, num )
    setv( toVar, getv( toVar ).to_i * getv( num ).to_i )
end

def i_div!( toVar, num )
    setv( toVar, getv( toVar ).to_i / getv( num ).to_i )
end

def i_mod!( toVar, num )
    setv( toVar, getv( toVar ).to_i % getv( num ).to_i )
end

def i_exp!( toVar, num )
    setv( toVar, getv( toVar ).to_i ** getv( num ).to_i )
end

def i_incr!( toVar )
    setv( toVar, getv( toVar ).to_i + 1 )
end

def i_decr!( toVar )
    setv( toVar, getv( toVar ).to_i - 1 )
end

def i_abs!( toVar )
    setv( toVar, getv( toVar ).to_i.abs )
end

# Integers, do not modify var
def i_plus( var, num )
    getv( var ).to_i + getv( num ).to_i
end

def i_minus( var, num )
    getv( var ).to_i - getv( num ).to_i
end

def i_mult( var, num )
    getv( var ).to_i * getv( num ).to_i
end

def i_div( var, num )
    getv( var ).to_i / getv( num ).to_i
end

def i_mod( var, num )
    getv( var ).to_i % getv( num ).to_i
end

def i_exp( var, num )
    getv( var ).to_i ** getv( num ).to_i
end

def i_incr( var )
    getv( var ).to_i + 1
end

def i_decr( var )
    getv( var ).to_i - 1
end

def i_abs( var )
    getv( var ).to_i.abs
end

# comparisons
def v_cmp( var1, var2 )
    getv( var1 ) <=> getv( var2 )
end

def v_eq( var1, var2 )
    (getv( var1 ) <=> getv( var2 )) == 0 ? true : false
end

def v_lt( var1, var2 )
    (getv( var1 ) <=> getv( var2 )) == -1 ? true : false
end

def v_le( var1, var2 )
    (getv( var1 ) <=> getv( var2 )) < 1 ? true : false
end

def v_gt( var1, var2 )
    (getv( var1 ) <=> getv( var2 )) == 1 ? true : false
end

def v_ge( var1, var2 )
    (getv( var1 ) <=> getv( var2 )) == 1 ? true : false
end

# That's all folks

All these methods and variables are defined the AlmaSem class. AlmaSem stands pompously for 'Semantics of the Alma parser'.
You may use this class to define your methods called by the Alma parser, it is the easiest way, however you should not give to your methods a name already used by Alma. The methods defined by Alma are those listed above and the ones which appears in the chapter Built-in functions..

Alma uses an instance variable of the AlmaSem class, its name is @_aP_vars, thus, please, do not give this pretty name to one of your scripts variables. setting this variable to something may give unpredictable (but probably bad) results. You might need to read and inspect this variable only if you want to hack Alma.

The Alma classes are also constants, for now AlmaSem, AlmaParser, AlmaError and AlmaGiveUpError. This will probably change, do not use constants which name starts with 'Alma' or 'ALMA'.

2.3.2 Variables scope.

Alma lets you access Ruby variables and constants, the convention for variable names is the same as in Ruby.

This convention is explained in the Programming Ruby book quoted below : (1)

Ruby uses a convention to help it distinguish the usage of a name: the first characters of a name indicate how the name is used. Local variables, method parameters, and method names should all start with a lowercase letter or with an underscore. Global variables are prefixed with a dollar sign ($), while instance variables begin with an ``at'' sign (@). Class variables start with two ``at'' signs (@@). Finally, class names, module names, and constants should start with an uppercase letter. (...)
Following this initial character, a name can be any combination of letters, digits, and underscores (with the proviso that the character following an @ sign may not be a digit).

It is not exactly the same in Alma, variable names which start with a lowercase letter or an underscore, when accessed through an Alma method such as setv or getv, are not the local variables of the calling method.
Instead, they refer to variables in an Alma specific variables set, which is local for a macro, this variables are said to be alma-local.
Next chapter describes the use of Alma specific variables sets.

There are some shortcuts : the Alma built-in variables.
When a variable name starts with a digit or with '+' or '-', the variable expansion returns the integer value corresponding to this variable name.
&${;+421;} returns 421.
&${;-abc;} should crash.
The variable names 'nil', 'true' and 'false' expansion return (respectively I hope) nil, true and false.
&${nil} returns nil.

2.3.3 Alma specific variables sets.

In addition to give access to Ruby variables, Alma provides 2 other sets of variables. You can access these sets from a Ruby method, the local variable set will be the Alma source (document or macro) one.
These two sets are hashes, keys are variable names and the values are references to the corresponding object. You may consider that these sets respond to the instance methods of a hash but you should use the Alma methods to modify these sets.

2.3.4 Predefined variables and constants.

Built-in variables, they are not stored by Alma in any set.
The nil, false and true variable names reference the nil true and false objects.
digits strings which may be preceeded by a plus or minus sign reference the corresponding integer object.

The variables of Alma instance set which have an uppercase letter following the ampersand in their name are read-only.
&ARGC and &ARGV reference the command line arguments count and array.
they correspond to the command line positional arguments placed after Alma options in the command line.
&ALMA_SELF references the AlmaSem class object.

_argc and _argv reference the positional arguments count and array of a macro in the local variable set of a macro, in a macro it is recommended to access _argv through the arg or each_arg method.

2.4 Functions.

2.4.1 Invocation.

&[.][funcName]{[ [ keyword argument ] ... ] [ ;[ positional argument ; ] ... ]}

The dot which may appear after the ampersand inhibits the output of the object returned by the function.
funcName is the function name, It may contains letters a..z, A..Z, digits 0..9 and characters ! % & * + - / < = > ? @ [ ] ^ _
Note that the backquote character (a Ruby method) cannot be used, If you want to get the output of a command, the bq method is aliased to the backquote method.
&bq{ ; cat README; } outputs the README file in the current directory.

2.4.1.1 Function arguments.

Positional arguments must be preceeded by a semicolon ';' which stands between keyword and positional arguments.
They are evaluated before a functiun call, the resulting object is an array which is placed in the macro local variable _argv when the function is a macro. When the function is a method, its arguments are passed in the usual Ruby way.

When coding a function call, keyword arguments are placed after the opening braces and optional whitespaces.
The keyword arguments string ( which starts after the opening brace and ends before the first semicolon is parsed and any expression inside is evaluated.
Keyword argument are separated by white spaces and each appears in the form key=value without white spaces before and after the equal sign.
Note that whatever appears between the last positional argument and the closing brace is also evaluated.
Key and value are Alma expressions (the key is generaly a string, it is it not necessary but it should evaluate as a string starting with a lowercase character).
A keyword argument may be
hi=hello
or
hi=&${something}
where 'something' is the name of a variable which may reference something interesting.

Note that it is possible to specify the positional arguments array in the keyword argument _argv, it will override the positional arguments array.

&# file  argv01.alma
&.eval{ ;
    def fun( *args )
        args.inspect
    end
    ;
}&#
&fun{ ; usr; local; bin; }
is the same as
&.setv{ ; ary; &split{ ; usr/local/bin; /; }; }&#
&fun{ _argv=&${ary} }
will produce :
["usr", "local", "bin"]
is the same as
["usr", "local", "bin"]

For an obfuscative purpose, you can also write things like this :

&.set_var( ;kw;hi; )&#
&.set_var( ;you;mondo; )&#
&hello_world( &${kw}=ciao you=&${you} )

'hello_world' being a macro which accepts keyword arguments. The line below will give the same result
&hello_world{ hi=ciao you=mondo }
2.4.1.2 Keyword arguments supplied by Alma.
When a function is called Alma supplies keyword arguments, their name begins with an underscore, we already discuss about _argc and _argv.
Others keyword arguments are : These names are the same name, except when the function override feature is used.
2.4.1.3 Methods and Macros.

There are 2 kinds of functions, macros and methods.
A function is first searched in the macro set then its existence as a method is checked.
This order may change in the future, as macros are not an important Alma feature.

2.4.2 Methods.

2.4.2.1 Method invocation.

When the AlmaSem object responds to the method, the AlmaSem method is called. Note that you can define methods in the AlmaSem class.

When the method cannot be applied to the AlmaSem object and it is called with positional arguments, if the first argument respond to the method, this method is applied to the object supplied as the first argument after the removal of this argument from the arguments array.

For example, this Alma method invocation
&split{ ;/usr/local/bin;/; }
correspond to this Ruby invocation :
'/usr/local/bin'.split( '/' )
2.4.2.2 Access to positionnal arguments.
As usual in Ruby.
The method arity is checked by Alma, but it is not completly implemented. Ruby handles errors that Alma do not find.
2.4.2.3 Access to keyword arguments.
At this time, keyword arguments do not exist in Ruby, They will appear in the future. For now, Alma methods let you retrieve these arguments.
The keyword arguments retrieval needs 2 operations : The kparm method performs these two steps. Which is more convenient if you only have one argument to retrieve.

Alma Hello Inc. decided to rewrite its product in Ruby as a fully parametrizable method.

# file hello.rb
require 'alma/almasem'
class AlmaSem

  def hello
    kparms= key_parms
    hi = get_kparm( kparms, 'hi' )
    unless hi.respond_to?( 'capitalize' )
      alma_error( 0, "Argument 'hi' missing or invalid\n" )
      return '' 
    end
    you = get_kparm( kparms, 'you' )
    unless you.respond_to?( 'capitalize' )
      alma_error( 0, "Argument 'you' missing or invalid\n" )
      return '' 
    end
    "#{hi.capitalize} #{you.capitalize}!"
  end
        
end
The alma_error method will be described later.
Specifying the AlmaSem Class is optional.

Now, an Alma document can use this method for any language !

&# file  hello05.alma
&# English
&hello{ hi=hello you=world }
&# French : note the backquotes `` 
&hello{ hi=`salut le` you=monde }
We can notice the use of backquotes, they are used because whitespaces are keyword argument delimiters.
This document is parsed by entering this command line :
alma.rb -r hello.rb -i hello05.alma
hello.rb and hello5.Alma are respectively the Ruby script and the Alma document above.
The '-r' (or '--require') command line option tells Alma to require the Alma script before parsing the input document. Command line options will be discussed later.
As an alternative to this command line option the Alma document might have begun with this line :
&require{;hello.rb;}

Have a look at the results :

Hello World!
Salut le Monde!
Impressive ! :-)

2.4.3 Macros.

Macros are not an Alma important feature, most of the time it is better to write Ruby methods but they may be useful for these reasons :

A macro is a variable which should be a string defined to be parsed by the Alma parser, it may be a variable which is distinguished by its naming convention as described above.
with the followind important difference : macros which name starts with an undersore or a lowercase letter are not alma-local but they act as instance variables. This is probably what you want as macros will act like methods.
Their use differentiates macros from other variables, They should be accessed by specific methods : A macro is invoked by its name, as a method. It consists of suppling arguments in a newly created local variable set and the recursive call of the parser to parse (what else could it do) the macro string.
2.4.3.1 Simple examples

Macro :

&# file  once.alma
&.macdef{;once;once upon a time;}&#
&#    test the once macro
&once{}
Result :
once upon a time
The 'once' macro is a kind of abbreviation.
Changing the macro definition, which may be placed in another file, allows the macro (and the document) to generate a different output. Note that for simple things like this, variable expansion is sufficient and recommended because faster.

Macro :

&# file  blah01.alma
&.macdef{;get_blah;`&${@blah}`;}&#
&#    test the get_blah macro
&.setv{;@blah;blah blah blah...;}&#
&get_blah{}
Result :
blah blah blah...
This example is only useful for me to explain some syntaxic and semantic issues.
Note the backquotes enclosing the variable expansion construct, they prevent the expansion at macro definition time. The expansion will be done at macro call time, allowing the variable to be set to different values for each call.
We use an instance variable '@blah', as alma-local variables are local to macros, the get_blah macro cannot access the local variable set of the caller.
Instance variables are useful to parametrize macros when they are set for the lifetime of a document or a part of a document.

Macro :

&# file  tale.alma
&.macdef{;tale;`Once upon a time in the &arg{;0;}, &arg{;1;}`;}&#
&#    test the tale macro
&tale{;west;blah...;}
Result :
Once upon a time in the west, blah...
The ability to receive arguments is the main interest of macros in relation to variables.
Positional arguments are retrieved using the 'arg' method, The arg method receive an argument, the argument number which must respond to the 'to_i' method (typically a string or an integer), in the example above the strings '0' and '1'.
2.4.3.2 Wrapping methods.
&# file  time.alma
&.macdef{;utc_time;&#
`&to_s{ ;&utc{ ;&mktime{
;&${Time};&#
&${year};&#
&${month};&#
&${day};&#
&${hour};&#
&${min};&#
&${sec};&#
&${usec};&#
utc;
}; }; } &#
`;}&#
&#    test the utc_time macro
&utc_time{ day=16 month=nov year=2005 hour=08 min=42 }
...the same as
&to_s{ ;&utc{;&mktime{ ;&${Time};2005;nov;16;08;42; }; }; }
Result :
Wed Nov 16 07:42:00 UTC 2005 
...the same as
Wed Nov 16 07:42:00 UTC 2005
This macro is less simple and it goes beyond what you'll have do with macros.
It translates a date given in local timezone to an UTC date printable string. Its interests are to allow the macro user to supply keyword arguments in any order and to increase the readibility of the Alma document. A look at the order the macro user supplied the arguments as well as his timezone let me think that he may be French.
A better approach is to write a Ruby method, as seen before a method can retrieve keyword arguments. Wrapping methods is interesting for already existing methods and for methods which must be able to work out of the Alma context, and wrapping can also be done by a method.
2.4.3.3 An example which comes from the real world.
&# file  almadoc.alma
&.macdef{ ;alma;`<div class="alma"><pre>&arg{ ;0; }</pre></div>`; }&#
&#    test the alma macro
&alma{;`&capitalize{ ;&${hi}; }`;}
Result :
<div class="alma"><pre>&capitalize{ ;&${hi}; }</pre></div>
This example is more realistic. You should not write much more complex macros. For this use, macros and Ruby methods are likely to be equivalent with regard to execution speed and readability. when things become more complex, Ruby is still readable as Alma macros become quickly obfuscating.
This macro produce XHTML text from an Alma dialect, later we'll see the way used to translate the '&' of &capitalize into '&amp;'.
I often use this macro to write this documentation, On your browser, it produces :
&capitalize{ ;&${hi}; }

I suggest not to use macros except for simple things especially symbolic substitution from keyword arguments.

2.4.3.4 Defining macros in separate files.
For reusability purpose, you can define macros in different files. As an Alternative to the 'hello' method, The Alma Hello team wrote the 'hello' macro :
&# file  hellom.alma
&# This is version 2.0 of the Alma Hello macro
&# use keyword arguments
&.macdef{ ;hello;`&capitalize{ ;&${hi}; }&#
 &capitalize{ ;&${you}; }!`; }&#
It can be used by the documents which used the method version and give the same results, But the command line to invoke the parser is sligthly different.
alma.rb -p hellom.alma -i hello05.alma
The '-p' ('--parse-files') option tells Alma to parse the macro.alma file before parsing the input files. As for the '-r' option, you can supply a list of comma-separated file names.
An alternative is to use &file_parse{;macro.alma;} in the document before the macro calls, it corresponds to the ruby method 'require'.

2.4.4 Built-in functions.

From an Alma document, you can access all the Ruby built-in methods, the methods of the Ruby standard library, or any method of the RAA (Ruby Application Archive) you have installed.

Some methods had been written to be used more specifically in Alma documents, they are described below.

They are methods of the AlmaSem class.
They can be accessed from Alma documents or from ruby scripts. We'll use the Alma syntax for there description, but you can derive the Ruby syntax.
In Alma :
&method{ ;arg1;arg2; etc.; }
In Ruby :
method( arg1, arg2, etc. )

Variables access methods have already been described.
Using other functions, you can access files to include or parse them, conditionaly or iteratively generate output.

2.4.4.1 The void method.
The void method has an empty name, it does nothing and is not implemented but its non-action is powerful, it returns its argument list just as the parser supplied it, as an array.
It is useful and cheap to build an array, empty or not :
&# file  void.alma
&inspect{ ;&{}; } == &inspect{ ; &new{ ; &${Array}; }; }
&inspect{;
    &{ ; usr; bin; alma.rb; };
}
which produces :
[] == []
["usr", "bin", "alma.rb"]
2.4.4.2 Iterators.

These methods iterates on container elements.

container should respond to the each method, str is parsed for each item in this container with variable var set to this item.

Example :

&# file  for01.alma
&.setv{ ; ary; &split{; /usr/local/bin; /; }; }&#
C:&#
&for_each{ ;
    &${ary};
    entry;
    `&${entry}\\\\`;
}
Which is equivalent to :
C:&#
&join{ ; &${ary}; \\; }\\
Result :
C:\usr\local\bin\
Which is equivalent to :
C:\usr\local\bin\

which does the same as &for_each{ ; _argv; var; str; }

Example :

&# file  for02.alma
&.macdef{ ;
    parg;
    arguments : `&each_arg{ ;
         entry;
        \`(&${entry}) \`;
    }`;
}&#
&parg{ ;aaa;bbb;ccc;}
Result :
arguments : (aaa) (bbb) (ccc) 
2.4.4.3 If / unless.

These methods evaluate expressions depending on the result of a conditional expression.

As in Ruby, a condition is considered as true when it does not evaluate as nil or false, and consideredas as false when it is not true.
In the case of the &if, when the condition evaluate as true, 'then_expr' is evaluated and returned by the if method. When the condition evaluate at false or nil, the else_expr is evaluated and returned.
In the case of the &unless, It is the opposite.
The following 2 method calls do the same.
&if{ cond=expr_cond then=expr}
&unless{ cond=expr_cond else=expr}

Any argument is optional, when cond is not specified, it is assumed false.
When the expression which should have been evaluated is not specified, an empty string is returned.

cond__expr does not generally need to be quoted, it will allways be true.
then_expr and else_expr generally need to be quoted, if not, they will always be evaluated.

Example :
The Alma Hello documentation uses Alma :

&# file  tycoon.alma
&# define the author macro
&.macdef{ ;author;`John Doe-Tycoon I.&#
&if{
    cond=&${@verbose}
    then=\`
    John Doe-Tycoon I is an overgraduate PhD
    of the Acme Institute of Technology.
    He is now reseach director at &${@company}.\`
}&#`
; }&# end of macdef
&#
&#
&.setv{ ;@company;Alma Hello Inc.; }&#
&.v_cp!{ ;@verbose;true; }&#
&#
Alma Hello  documentation Draft.

Author : &author{}

Alma Hello is a subtle implementation
of the 'Hello World!' paradigm. blah...

&.v_cp!{ ;@verbose;false; }&#
Copyright (C) &author{} All rights reserved.
. Result :
Alma Hello  documentation Draft.

Author : John Doe-Tycoon I.
    John Doe-Tycoon I is an overgraduate PhD
    of the Acme Institute of Technology.
    He is now reseach director at Alma Hello Inc..

Alma Hello is a subtle implementation
of the 'Hello World!' paradigm. blah...

Copyright (C) John Doe-Tycoon I. All rights reserved.
2.4.4.4 While / until.

These methods iterate on an expression evaluation.

Condition evaluates as for if, but as it may be evaluated several times, you will generally wants it to be quoted.
The result of evaluations are concatenated before being output.

Example :
A simple and ugly decrementation loop :
&# file  decr.alma
&# 9..0 loop
&.v_cp!{ ;num;9; }&#
&until{ 
    cond=`&v_lt{ ;num;0; }`
    do=`&#
&v_to_s{ ;num; } &#
&.i_decr!{ ;num; }&#
`&#
}
Which produces :
9 8 7 6 5 4 3 2 1 0 
Easier in Ruby : use Ruby.
These functions appears mostly for language completeness.
2.4.4.5 File Access.
You can include a non parsed text file using the file_read method. Useful to quote large text chunks.
The file is searched in this order : &ALMAINC may be specified in the command line, when it not specified in the command line, the environment variable ALMAINC is used when it is defined,

A file filter can be applied to the file content.

Ruby built-in kernel methods such as open, gets, readline, readlines etc. of class AlmaSem as long as class File and IO may also be useful to access files, pipes, sockets and other IO objects.

2.4.4.6 Parser calls.
The file_parse method lets us parse an Alma file. It can be used to parse a file containing macro definitions or to include text you want to be parsed.
It is something like 'require' in Ruby or '#include' in C.

The file is searched as for file_read except that &ALMALIB is used instead of &ALMAINC.

It is something like 'eval' in other languages.

The file_parse and str_parse methods do not create an Alma-local variable set, the file or the string is sourced, it is useful to parse files containing setting of variables.
If you do not want to be polluted by the setting of Alma-local variables, use the file-call end str-call methods instead.

These methods create an Alma-local variable set then delete it after parsing.
These methods also accept keyword arguments which can be used by the file or the string being parsed.
2.4.4.6.1 Simple examples
&# file  hellopf.alma
&.setv{ ; hi;  hello; }&#
&.setv{ ; you; world; }&#
&# file  hellop.alma
&.file_parse{ ; hellopf.alma; }&#
&capitalize{ ; &${hi}; } &capitalize{; &${you}; }!
The parsing of the second file gives a valid Hello World! result, This would not have been possible if file_call have been used instead of file_parse.

There is another way to get the same result using file_call :

&# file  hellocf.alma
&capitalize{ ; &${hi}; } &capitalize{; &${you}; }!&#
&# file  helloc.alma
&file_call{ _call=hellocf.alma hi=hello you=world }
The parsing of the second file gives a valid Hello World! result,

file_parse can be used to source a file as file_call can be used to parse a file in a new Alma-local environment.

We can do the same with strings.
&# file  hellosc.alma
&.setv{ ; str; &file_read{ ; hellocf.alma; }; }&#
&str_call{ _call=&${str} hi=hello you=world }
&str_call{ _call=&${str} hi=salut you=`le monde` }
&str_call{ _call=&${str} hi=ciao you=mondo }
Result :
Hello World!
Salut Le monde!
Ciao Mondo!
In this example, it is better to use str_call than file_call, this avoid to read the file three times.
2.4.4.6.2 Parser calls vs Macro calls.

You can do quite the same things using macros and xxx_parse and xxx_call.

The advantage of macros is the fact that they are called just like methods, thus you can rewrite them easily as methods without changing the Alma documents which invoke them.
This is not a true advantage as most of the time it is better to directly write a method.

The inconvenient of macros is the fact that they are global and remains allocated for the life of the Alma parser. Their name is also global. If, from your Alma document, you parse different documents from different origins, these documents should not define a macro with a same name as a macro you already defined. A solution would have been to push macro-definitions, the good solution is to avoid using them in most cases.

file_parse and file_call do not pollute the AlmaSem name-space nor the AlmaSem storage space, the file is read then parse and the storage needed for the file content is made available for the garbage collector.
You should use file_call to parse a file instead of calling a macro especially when this macro should have been big. If you need to parse iteratively a file, read it into a variable then parse the string variable using str_call. This is quite equivalent to the usage of macros but without waste of space and name_space pollution. The advantage of macros is the possibility to put many macro definitions in the same file, but generally you also have to quote the macro string, therefore quoting the quotes in the macro string.

file_parse can be used to set variables and maybe perform some operations but should not create variables for their own needs as its Alma-local variable set is the one of the document which invoke it.

It is unlikely that you often have to use str_parse.

2.4.4.6.3 Overriding functions.

The file_parse and the str_parse methods accept a second optional argument fover, the file_call and str_call accept a keyword argument _fover. fover stands for function override.
This argument, when supplied, should be the name of a function. This function will be called instead of any function to be called in the source to parse.
When function override is used, the _fname keyword agument to the function is the original function name (the one specified in the source) and the the _rname keyword agument the name of the function actually called. The Alma to XML example is a possible use of this feature.

2.4.4.7 Objects creation.

Classe String objects are likely to be the most often use, the input stream produces strings as arguments to functions but some class String methods allow you to create other types of objects.

Of course, you can directly access class constant names and instantiate any existing class responding to the new method.
I'm afraid you'll have to read yet another Hello Word! version which uses an Array.

&# file  arraynew.alma
&.setv{;    &# set array to a new array
    array;
    &new{;&${Array}; };
}&#
&.push{ ; &${array}; Hello ; }&#
&.push{ ; &${array}; World!; }&#
&join{ ; &${array}; }
Its result conforms to specifications.
2.4.4.8 Flavours.
Alma lets you change its lexical units as '&' '$' '{' '}' etc. and some syntax rules. A set of lexical units and syntax behaviours is called a flavour.
2.4.4.8.1 Defining a flavour.

You can define flavours adapted to your needs and tell Alma to use a flavour.

Here is a definition of a flavour which looks more classic as it uses parenthess to enclose function arguments and commas as arguments delimiters.

&# file  flav03.alma
&.flavour{
      name=classic
      quote1=\`     \` is backquote
      quote2='      single quote
      begin=(       parenthese
      end=)
      separator=,   comma
}&#
This flavour has another interest, as its opening and closing quotes are different, quoting strings may be nested.

The flavour method only accepts keyword arguments, one is mandatory, name, it is a variable name and the naming conventions apply to it, this name is use to store the flavour and can be used to refer to it. It may be an already existing flavour which is then overwritten.

Other arguments are optionnal, the argument 'from' specifies the name of an already existing flavour where the defaults for omitted arguments are taken. If 'from' is not specified, the defaults are taken in the current flavour.

Following arguments specify the lexical units, They should be supplied as character strings but only the first char of the string is taken into account.

The terminator argument, when specified, can be the string 'true' or 'false' which specify whether the last argument separator in the positional argument list is mandatory (the default) or not.

2.4.4.8.2 Changing flavours.
You can push up to 16 flavours.
2.4.4.8.3 Built-in flavours.
The standard flavour which is the default is defined under the name &FLAV_STD. The classic flavour as described above is defined under the name &FLAV_CLASSIC.

Here is an an Alma documents which outputs 2 'Hello World!' lines, the first using the classic flavour, the second using the standard flavour.

&.push_change{ flavour=&FLAV_CLASSIC }&#
&capitalize( , hello, ) &capitalize( , world, )!
&.pop_change()&#
&capitalize{ ; hello; } &capitalize{ ; world; }!
2.4.4.9 Stream hooks.

As not unusual in languages, functions arguments are evaluated before the function call, they are evaluated from left to right, when a function argument requires a function call to be evaluated, the arguments of this function are evaluated first and so on recursively.

It is probably what you expect in most cases, however sometimes it is useful to know the ordering of some functions in the input string. As an example, this is an unfinished document of an alma dialect designed to produce documentation.

&# file  hook0.alma
&init_doc{}&#
&document{ title=`Foobar revisited.`;
  &chapter{ title=Introduction.;
    &chapter{ title=Design;
        Foobar implements the foo pattern ...; 
    };
  };
}
We probably wants something like this :
FOOBAR REVISITED.

1 Introduction.

1.1 Design
Foobar implements the foo pattern ...
Probably with a better presentation, but it is not the topic.

To number the chapters, we cannot rely on an instance array and an incrementation in the chapter method because The '1.1 Design' chapter method is called before the '1 Introduction' chapter method. The chapter numbering depends on the chapters nesting depth and the order in which they appear in the input document not on the order the chapter functions are called.

Streams hooks are functions which are called in the order an associated function appears in the input stream. The output of a stream hook is passed to the associated function in the keyword argument '_sHb'.

In addition to this hook called 'stream hook before' as it is always called before the associated function call, there is an other kind of hook, so-called 'stream hook after', less useful, hooks of this kind are called after the associated function completion, they received the output of the associated function and whatever they return takes the place of the associated function output.

To associate a stream hook to a function : To dissociate a hook from a function, use the above functions with hookName as nil.
&s_hook_before{ ; funcName; &${nil}; }

The stream hooks receive the associated function name as first positional argument. In addition the 'stream hook after' receives the function output as second positional argument.

This is the Ruby source used to get the expected results from the Alma source above :
# file  hook0.rb
require 'alma/almasem'

def init_doc( )
  @chArray = [0]
  s_hook_before( 'chapter', 'chapter_hb' )
  ''
end

def document( content )
  "#{kparm( 'title' ).upcase}\n#{content}"
end

def chapter_hb( funcName )
  @chArray[ @chArray.size - 1 ] += 1
  value = @chArray.dup
  @chArray.push( 0 )
  value
end

def chapter( content )
  @chArray.pop
  "\n#{kparm('_sHB').join('.')} #{kparm( 'title' )}\n"\
  "#{content}"
end
We can notice that the use of the keyword argument _sHB might have been avoided in the chapter method by writing @chArray instead of kparm( '_sHB') .
The Alma document is parsed by the command :
alma.rb -r hook0.rb -i hook0.alma
The -r (--require) option tells Alma to require hook0.rb before parsing hook0.alma.

The init_doc method initialize the array corresponding to the chapters numbering and associate a stream hook to the chapter method.
The stream hook, chapter_hb, increment the number in the array item corresponding to the current nesting level, this will be the return value of the hook, then push a 0 in the array, for nested chapters, if any.
The chapter method pop the array and get the return value of the hook from the '_sHB' keyword argument.

2.4.4.10 Filters.

Filters are functions that receive a string as argument and return a string. Whatever they return is used instead of the original string.

Some simple filters are shipped with Alma, you can find them in the library file almafilters.rb. They are mostly examples of how to write a filter.

2.4.4.10.1 Text filters.
These filters receive the tokens <TEXT>, <CHAR>, <STRING> as defined in the Alma BNF description.
2.4.4.10.2 File filters.
These filters operate on a file being read by file_read
2.4.4.11 Termination, errors and debugging
This value is overriden when an error is encountered.
value should respond to the to_i method.

When level == -1, it is considered as an unrecoverable error, the exit value is set to 1 and the Alma parser raises AlmaGiveUpError which should be handled by Alma.rb by exiting..
When level == 0, it is considered as an error. The alma_error method returns and the exit value is set to 1.
When level > 0, it is considered as a warning.

A backtrace of Alma function calls is printed on the standard error. When more than 11 errors are encountered, the Alma parser stops by raising AlmaGiveUpError.

2.4.4.12 Parse_Array.

This method had been written to parse the non-option command line arguments which typically are arguments for the Alma documents to parse as the command line options are options to Alma.
Alma uses OptHash to parse its command line, Option arguments are returned in a hash and non-option arguments are returned in an array.

The parse_array method can parse any array of strings, when an array entry has the format 'x=y', it sets the variable x to the string 'y' by using the setv method (when a variable name starts whith a lowercase letter, the variable will be Alma_local, not local).
Other input array entries are returned in an array.

This method uses parse_array to set variables, it is located in the file 'parse_array.rb'
# file parse_array.rb
require 'alma/almasem'
def fun( args )

  p args
  result = parse_array( args )
  p getv( 'a' )
  p @foo
  p result
  
end
It can be executed by Alma using this command line :
alma.rb -r parse_array.rb -e fun blah a=b @foo=1 blurb,blob
The script parse_array.rb containing the fun method above is first required by the '-r' option., the '-e' option tells Alma to execute the fun method.
When executed with the '-e' option, a method receives the command line arguments array as argument, you can notice that 'blurb,blob' is a single command line argument, 'blurb' and 'blob' are put in a sub-array by OptHash.
this gives the following result :
["blah", "a=b", "@foo=1", ["blurb", "blob"]]
"b"
"1"
["blah", ["blurb", "blob"]]
2.4.4.13 Miscellaneous methods.

2.5 More examples.

2.5.1 Symbolic Substitution.

This is the first document I wrote in Alma for the first tests of the parser.
&# file  dog.alma
&.setv{ ; rex; dog; }&#
&.setv{ ; dog; animal; }&#
&.setv{ ; dog-food; bones; }&#
Rex is a &${rex}, a &${rex} is an &${&${rex}}.
&capitalize{ ; &${rex}; }s eat &${&${rex}-food}.
Result :
Rex is a dog, a dog is an animal.
Dogs eat bones.

Any expression can contain any expression.

2.5.2 Interactive Alma.

This command lets you use alma interactively :
alma.rb -e inter
The -e option tells Alma to execute a specified method.
A simple session :
pat@localhost$ alma.rb -e inter
Enter expression, &q to quit, &h for help
> &set_var{ ;foo;blah; }
blah
> &capitalize{ ;&${foo}; }
Blah
> &q
Bye.
pat@localhost$

The inter method uses the Readline::readline method.
In an Alma document you also can use this method to prompt the user for some information, for example :
&readline{ ; &${Readline}; Enter your name : ;&${true}; }

2.5.3 Alma to XML.

The Alma parser can translate an Alma document into XML, but since these two languages have some structural differences, Alma have to use, say, simple heuristics in some cases and the result may not be exactly what you expect.
Although it may be useful, it is mostly an example on how to use function overriding, filters the parse_array functionand the Alma command line arguments.
The simple Ruby methods to achieve this translation are shipped 'as is' in the Alma distribution.

As you can see the code of alma2xml.rb is quite simple :

#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
class AlmaSem
  
  def a2x( *args )   # make xml tags from a alma function call
    kparms= key_parms( )  # get keyword parameters set
    fname = get_kparm( kparms, '_fname' )
    outStr = '' << "\n<#{fname}"    # funcName as tag
    kparms.sort.each do | k |   # key parms as attribute
      ( k[0][0,1] == '_' ) || outStr << " #{k[0]}=\"#{k[1]}\""
    end
    if ( args.size == 0 ) ; outStr << " />"
    elsif ( args.size == 1 )
      outStr << ">\n#{args[0]}\n</#{fname}>"
    else # what to do ?
      outStr << ">\n<item>\n"\
      "#{args.join("\n</item>\n<item>\n")}"\
      "\n</item>\n</#{fname}>"
    end
  end
  
  def alma2xml( args )  # translate an alma file into a xml file
    parse_array( args )
    fileName = getv( 'file' ) # file to parse
    ( ( tr = getv( 'tr' ) ) != '' ) &&  set_text_filter( tr )
    root = ( ( root = getv( 'root' ) ) != '' ) ? root : 'root'
    "<#{root}>#{file_parse( fileName, 'a2x' )}\n</#{root}>\n"
  end
  
end
# That's all folks
It might have been shorter but I'm a Ruby newbie.

This is the command line to execute it :

alma.rb -r alma/alma2xml.rb \
        -r alma/almafilters.rb \
        -e alma2xml \
        -o res/xml00.xml \
        file=xml00.alma tr=f_xmlchrs
The Alma parser first require the 2 files alma2xml.rb and almafilters.rb which are shipped with Alma in the alma library directory.
It then handles the '-e' ('--execute') option and execute the alma2xml method which receives the non-option arguments (The last line of the command) array as argument.
The alma2xml method parses this array using parse_array which fills the file and tr Alma-local variables.
The file variable is the name of the file to parse, tr is a text filter, here f_xmlchrs is a simple filter shipped with Alma in the almafilters.rb file. This filter translates &, < etc. into &amp; &lt; etc.
Alma2xml then output the result of parsing the file with the overriding function a2x which transform function calls like &func{ ... } into <func> ... </func>.
The a2x method uses the _fname keyword argument to get the name of the original function.

Here is the Alma input document xml00.alma

&# file  hook0.alma
&init_doc{}&#
&document{ title=`Foobar revisited.`;
  &chapter{ title=Introduction.;
    &chapter{ title=Design;
        Foobar implements the foo pattern ...; 
    };
  };
}
And the resulting xml00.xml file :
<root>
  <init_doc />
  <document title="Foobar revisited.">
    <chapter title="Introduction.">
      <chapter title="Design">
        Foobar implements the foo pattern ...
      </chapter>
    </chapter>
  </document>
</root>
In fact, I indented it by hand after, since alma2xml do not indent.

2.5.4 A 'real world' example : tinyDoc.

TinyDoc is an Alma dialect, sets of methods for documentation generation. From a same Alma document, documentation may be produced different formats by changing the method set (for now, only the xhtml format is implemented). It is a kind of very very lightweight Docbook and produces what you are reading now.

One of its purposes is to test Alma with something wich looks like a real wold project and which can be reusable.
It's functionnalities are very limited, along with xhtml presentation stuff it only performs chapters numbering, table of contents genreration and footnotes handling (2).
It don't use macros anymore, TinyDoc is 300 lines of very simple Ruby code that a Ruby newbie like me wrote easily.

Maybe it will be released some day, but there are many other tools to make documentation which are likely to be better.
For now it is a dirty draft and cannot be shown as an example.
It produces what is on your screen, it uses stream hooks for chapter numbering and filters to translate some chars like &, < etc. into &amp; &lt; etc. These are the less trivial parts of its code.

2.5.5 What you should not do in Alma.

This is the unavoidable Alma factorial implementation :
&# file  fact.alma
&# Do not do this kind of thing in alma !
&# It is just for tests purpose.
&.macdef{ ;fact;`&#
  &if{ 
    cond=&v_eq{ ;num;1; }
    then=\`&${1}\`
    else=\`&i_mult{ ;num;&fact{ num=&i_decr{ ;num; } }; }; }\`
  }&# end if 
`;
}&# end macdef
fact( 5 ) = &to_s{ ;&fact{ num=&${5} }; }
Although it works, it is an example of Alma limitations.
It is far easier to use a Ruby method instead of a macro.
def fact( n ) 
  ( n == 1 ) ? 1 : n * fact( n - 1 )
end 

Alma is designed to manipulate text using Ruby and to structure documents.
If, by chance, you need something not trivial, write a Ruby method, Alma should be considered as a layer between document presentation and Ruby implementation, mostly to generate text.

3 The Alma API.

The Alma API lets you invoke the Alma parser from a Ruby script. This script parse an Alma file called 'hello01.alma'.
require "alma/almasem"
sem = AlmaSem.new( )
sem.run( { 'input' => 'hello01.alma' } )
The Alma module contains the AlmaSem class, along with the functions already described which can be used in an Alma document, this class can be instantiated and the parser run.

Running the parser involves creating an instance using AlmaSem.new and using the instance run method or methods like file_parse or file_call, str_parse or str_call. to parse files and strings, other builtin methods or AlmaSem class methods you have defined.

the new and run methods take 2 arguments, a hash which correspond to the options described in the invocation chapter and an Array corresponding to the remaining command line arguments.
An example is the alma.rb parser itself which can be run from the command line.

#!/usr/bin/ruby1.8
#  Copyright (C) 2005  Patrick Davalan
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.

class Aparser

require "alma/almasem"

  def initialize
    @verbose = 1
    @exitVal = -2
  end

  def run
    begin
      opt = AlmaSem.get_opt( ARGV )
      @verbose = (opt[0]['verbose']).to_i
    	alma = AlmaSem.new( opt[0], opt[1] )
      @exitVal = alma.run
    rescue => except
      $stderr.print(
        "Alma.rb : #{except.class} received: #{except.message}\n" )
      @exitVal = -1
    end
    ( @verbose > 0 ) &&\
      $stderr.print("Alma : exit code #{@exitVal}.\n" )
    @exitVal
  end

end

exit Aparser.new.run

# That's all folks

The AlmaSem class method get_opt transforms an Array in the ARGV format (aString.split ' ') into an array of the two arguments of new and run.
This method will exit the script when invalid options are supplied. it also exits, but successfully, when the --help or --version are supplied.
The argument is left unchanged.

When supplied to the run functions, these options override those supplied in new, but only for the call of a run.
Note that the --almalib and --almainc options are only taken into account in the new method.
The options and arguments to the new method (and the default options for non-supplied options can be found in the &OPT and &ARGV Alma variables during all the lifetime of the instance.
The options and arguments overriden (or not) by the last (or current) run call can be found in the &opt and &arg variables.

The methods described in this Alma documentation may be run after the creation of an AlmaSem instance, but the more important are likely to be file_parse and file_call, setting of hooks and filters.

4 Using Alma.

4.1 Invoking Alma.

This is the result of alma.rb --help produced by OptHash.
Usage   : alma.rb [--almalib [arg],...] [--almainc [arg],...] 
		[--require [arg],...] [--parse-files [arg],...] 
		[--execute [arg],...] [--input [arg],...] 
		[--output [arg]] [--trace] 
		[--verbose [arg]] [--help] 
		[--version] 
Options :
	--almalib -l [arg] ,... (default <>)
		Optional: Directories where to find alma files to parse.
	--almainc -a [arg] ,... (default <>)
		Optional: Directories where to find alma data files.
	--require -r [arg] ,... (default <>)
		Optional: Required ruby files.
	--parse-files -p [arg] ,... (default <>)
		Optional: Files to parse initially.
		(generally macro definitions).
	--execute -e [arg] ,... (default <>)
		Optional: Ruby files to execute by call.
	--input -i [arg] ,... (default <>)
		Optional: Input files to process
	--output -o [arg] 
		Optional: output file. Default to stdout
	--trace -t 
		Optional: Trace the function calls
	--verbose [arg] (default <0>)
		Optional: Specify verbosity level (0..4)
	[arg ...]
		Arguments.
	Or
	--help -h 
		Display these informations and exit successfully.
	Or
	--version -V 
		Display program version and exit successfully.
All the arguments are optional, but without any argument, The Alma parser will not do much.

The '--require', '--parse-file', '--execute', '--input' options are handled by Alma in this order. Their argument is a comma separated list of files.

The '--almalib' option tells Alma in what directories to find the files to be parsed, the option argument is a comma separated list of directories. those defined in the '--parse-files' and '--input' option, and by the file_parse and file_call methods.
When - and only when - this option is not set, the ALMALIB environment variable is used to find the files to be parsed.
When a file to be parsed is not found in these directories, the filename is used 'as is', in the current directory if it don't starts with a '/'.

The '--almainc' option and the ALMAINC environment variable are handled in the same way, it tells alma where to find the files to be read by file_read.

4.2 Installation.

Alma, its installation procedures and test suite are designed to work on any platform where Ruby works, but they have only been tested on a GNU/Linux system.
Alma had been developped with Ruby 1.8, but it may work on previous versions of Ruby.
You also need a C99 compiler, the Alma parser should compile without warning on GCC.
Alma needs OptHash to run.

Unzip the tarball with tar xzvf alma-version.tar.gz, it ceates the alma-version directory, then read the README file in that directory.

Download the latest version in the Download page

4.3 Bugs and deficiencies.

Alma version is now 0.0.9x, it is a test version with no known bug but with some well-known deficiencies listed below. However, this version seems to be usable.
The next unstable version will be 0.1.0, it should be released quite soon but no important deficiency will be fixed. They will be fixed in 0.1.x version.
The next stable version will be 1.0.0, it should be released in 2006. the deficiencies known at this time and maybe others will be fixed.

You can help by sending bugs reports and suggestions to almazz at wanadoo dot fr
Please, put '[Alma]' in the subject field, preferably at the beginning.
Any suggestion will be useful and I will spend more time developping Alma if I know that someone else uses it.

Deficiencies list :

5 Legal informations.

Alma is copyrighted : Copyright (C) 2005 Patrick Davalan. All rights reserved.

Alma is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.

Alma program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

The documents that you might produce by running the Alma parser are not subject to this license, you may choose any license for them or no license at all.

This software is OSI Certified Open Source Software.
"OSI Certified" is a certification mark of the Open Source Initiative.

Notes.

(1) Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright © 2001 by Addison Wesley Longman, Inc.
This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/).

(2) Thank you for testing my footnotes.

keywords : macro language, general purpose ,macro processor, Ruby, Alma, macro, meta programming,
1 Introduction.
1.1 Overview.
1.2 A note to 'Rubyists'.
1.3 About this document.
2 The Alma language.
2.1 Simple Examples.
2.2 Grammar.
2.3 Variables.
2.4 Functions.
2.5 More examples.
3 The Alma API.
4 Using Alma.
4.1 Invoking Alma.
4.2 Installation.
4.3 Bugs and deficiencies.
5 Legal informations.
Notes.
Alma inside !