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.
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.
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.
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.
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.
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.
hello world
alma.rb -i hello.alma
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!
&.setv{ ;hi;hello; }&# set hi to 'hello' &.setv{ ;you;world; }&# set you to 'world' &capitalize{ ;&${hi}; } &capitalize{ ;&${you}; }!
Hello World!
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.
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; }
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 }&#
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; }&#
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.
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.
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 :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.
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.
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; }
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'.
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.
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.
&[.][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.
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} }
["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; )&#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.
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.
&split{ ;/usr/local/bin;/; }
'/usr/local/bin'.split( '/' )
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
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 }
alma.rb -r hello.rb -i hello05.alma
Have a look at the results :
Hello World! Salut le Monde!
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 :
Macro :
&# file once.alma &.macdef{;once;once upon a time;}&# &# test the once macro &once{}
once upon a time
Macro :
&# file blah01.alma &.macdef{;get_blah;`&${@blah}`;}&# &# test the get_blah macro &.setv{;@blah;blah blah blah...;}&# &get_blah{}
blah blah blah...
Macro :
&# file tale.alma &.macdef{;tale;`Once upon a time in the &arg{;0;}, &arg{;1;}`;}&# &# test the tale macro &tale{;west;blah...;}
Once upon a time in the west, blah...
&# 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; }; }; }
Wed Nov 16 07:42:00 UTC 2005 ...the same as Wed Nov 16 07:42:00 UTC 2005
&# file almadoc.alma &.macdef{ ;alma;`<div class="alma"><pre>&arg{ ;0; }</pre></div>`; }&# &# test the alma macro &alma{;`&capitalize{ ;&${hi}; }`;}
<div class="alma"><pre>&capitalize{ ;&${hi}; }</pre></div>
&capitalize{ ;&${hi}; }
I suggest not to use macros except for simple things especially symbolic substitution from keyword arguments.
&# file hellom.alma &# This is version 2.0 of the Alma Hello macro &# use keyword arguments &.macdef{ ;hello;`&capitalize{ ;&${hi}; }&# &capitalize{ ;&${you}; }!`; }&#
alma.rb -p hellom.alma -i hello05.alma
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.Variables access methods have already been
described.
Using other functions, you can access files to include or parse
them, conditionaly or iteratively generate output.
&# file void.alma &inspect{ ;&{}; } == &inspect{ ; &new{ ; &${Array}; }; } &inspect{; &{ ; usr; bin; alma.rb; }; }
[] == [] ["usr", "bin", "alma.rb"]
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}; \\; }\\
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;}
arguments : (aaa) (bbb) (ccc)
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.
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.
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.
&# file decr.alma &# 9..0 loop &.v_cp!{ ;num;9; }&# &until{ cond=`&v_lt{ ;num;0; }` do=`&# &v_to_s{ ;num; } &# &.i_decr!{ ;num; }&# `&# }
9 8 7 6 5 4 3 2 1 0
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.
The file is searched as for file_read except that &ALMALIB is used instead of &ALMAINC.
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.
&# file hellopf.alma &.setv{ ; hi; hello; }&# &.setv{ ; you; world; }&#
&# file hellop.alma &.file_parse{ ; hellopf.alma; }&# &capitalize{ ; &${hi}; } &capitalize{; &${you}; }!
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 }
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.
&# 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 }
Hello World! Salut Le monde! Ciao Mondo!
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.
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.
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}; }
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 }&#
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.
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; }!
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 ...; }; }; }
FOOBAR REVISITED. 1 Introduction. 1.1 Design Foobar implements the foo pattern ...
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.
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
alma.rb -r hook0.rb -i 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.
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.
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.
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.
# file parse_array.rb require 'alma/almasem' def fun( args ) p args result = parse_array( args ) p getv( 'a' ) p @foo p result end
alma.rb -r parse_array.rb -e fun blah a=b @foo=1 blurb,blob
["blah", "a=b", "@foo=1", ["blurb", "blob"]] "b" "1" ["blah", ["blurb", "blob"]]
&# 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}.
Rex is a dog, a dog is an animal. Dogs eat bones.
Any expression can contain any expression.
alma.rb -e inter
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}; }
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
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
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 ...; }; }; }
<root> <init_doc /> <document title="Foobar revisited."> <chapter title="Introduction."> <chapter title="Design"> Foobar implements the foo pattern ... </chapter> </chapter> </document> </root>
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 & < etc. These are the less trivial parts of its code.
&# 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} }; }
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.
require "alma/almasem" sem = AlmaSem.new( ) sem.run( { 'input' => 'hello01.alma' } )
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.
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.
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.
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
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 :
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.
(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.