/*, */, // | comments |
Prog, [, ] | block of statements |
Bodied, Infix, Postfix, Prefix | define function syntax |
IsBodied, IsInfix, IsPostfix, IsPrefix | check for function syntax |
OpPrecedence, OpLeftPrecedence, OpRightPrecedence | get operator precedence |
RightAssociative | declare associativity |
LeftPrecedence, RightPrecedence | set operator precedence |
RuleBase | define function with a fixed number of arguments |
RuleBaseListed | define function with variable number of arguments |
Rule | define a rewrite rule |
HoldArg | mark argument as not evaluated |
Retract | erase rules for a function |
UnFence | change local variable scope for a function |
HoldArgNr | specify argument as not evaluated |
RuleBaseArgList | obtain list of arguments |
MacroSet, MacroClear, MacroLocal, MacroRuleBase, MacroRuleBaseListed, MacroRule | define rules in functions |
Backquoting | macro expansion (LISP-style backquoting) |
DefMacroRuleBase | define a function as a macro |
DefMacroRuleBaseListed | define macro with variable number of arguments |
SetExtraInfo, GetExtraInfo | annotate objects with additional information |
GarbageCollect | do garbage collection on unused memory |
FindFunction | find the library file where a function is defined |
Secure | guard the host OS |
/* comment */ // comment |
a+b; // get result a + /* add them */ b; |
Prog(statement1, statement2, ...) [ statement1; statement2; ... ] |
Prog(a,b); is the same as typing [a;b;]; and is very useful for writing out function bodies. The [ ... ] construct is a syntactically nicer version of the Prog call; it is converted into Prog(...) during the parsing stage.
Bodied("op", precedence) Infix("op") Infix("op", precedence) Postfix("op") Postfix("op", precedence) Prefix("op") Prefix("op", precedence) |
precedence -- nonnegative integer (evaluated)
"Bodied" functions have all arguments except the first one inside parentheses and the last argument outside, for example:
For(pre, condition, post) statement; |
D(x) Sin(x)+Cos(x) |
"Infix" functions must have two arguments and are syntactically placed between their arguments. Names of infix functions can be arbitrary, although for reasons of readability they are usually made of non-alphabetic characters.
"Prefix" functions must have one argument and are syntactically placed before their argument. "Postfix" functions must have one argument and are syntactically placed after their argument.
Function name can be any string but meaningful usage and readability would require it to be either made up entirely of letters or entirely of non-letter characters (such as "+", ":" etc.). Precedence is optional (will be set to 0 by default).
In> YY x := x+1; CommandLine(1) : Error parsing expression In> Prefix("YY", 2) Out> True; In> YY x := x+1; Out> True; In> YY YY 2*3 Out> 12; In> Infix("##", 5) Out> True; In> a ## b ## c Out> a##b##c; |
Note that, due to a current parser limitation, a function atom that is declared prefix cannot be used by itself as an argument.
In> YY CommandLine(1) : Error parsing expression |
IsBodied("op") IsInfix("op") IsPostfix("op") IsPrefix("op") |
In> IsInfix("+"); Out> True; In> IsBodied("While"); Out> True; In> IsBodied("Sin"); Out> False; In> IsPostfix("!"); Out> True; |
OpPrecedence("op") OpLeftPrecedence("op") OpRightPrecedence("op") |
For infix operators, right precedence can differ from left precedence. Bodied functions and prefix operators cannot have left precedence, while postfix operators cannot have right precedence; for these operators, there is only one value of precedence.
In> OpPrecedence("+") Out> 6; In> OpLeftPrecedence("!") Out> 0; |
RightAssociative("op") |
RightAssociative("*") |
LeftPrecedence("op",precedence) RightPrecedence("op",precedence) |
precedence -- nonnegative integer
This functionality was required in order to display expressions like a-(b-c) correctly. Thus, a+b+c is the same as a+(b+c), but a-(b-c) is not the same as a-b-c.
Note that the left and right precedence of an infix operator does not affect the way Yacas interprets expressions typed by the user. You cannot make Yacas parse a-b-c as a-(b-c) unless you declare the operator "-" to be right-associative.
RuleBase(name,params) |
params -- list of arguments to function
In the context of the transformation rule declaration facilities this is a useful function in that it allows the stating of argument names that can he used with HoldArg.
Functions can be overloaded: the same function can be defined with different number of arguments.
RuleBaseListed("name", params) |
params -- list of arguments to function
A function defined using RuleBaseListed will appear to have the arity equal to the number of parameters in the param list, and it can accept any number of arguments greater or equal than that. As a consequence, it will be impossible to define a new function with the same name and with a greater arity.
The function body will know that the function is passed more arguments than the length of the param list, because the last argument will then be a list. The rest then works like a RuleBase-defined function with a fixed number of arguments. Transformation rules can be defined for the new function as usual.
RuleBaseListed("f",{a,b,c}) 10 # f(_a,_b,{_c,_d}) <-- Echo({"four args",a,b,c,d}); 20 # f(_a,_b,c_IsList) <-- Echo({"more than four args",a,b,c}); 30 # f(_a,_b,_c) <-- Echo({"three args",a,b,c}); |
In> f(A) Out> f(A); In> f(A,B) Out> f(A,B); In> f(A,B,C) three args A B C Out> True; In> f(A,B,C,D) four args A B C D Out> True; In> f(A,B,C,D,E) more than four args A B {C,D,E} Out> True; In> f(A,B,C,D,E,E) more than four args A B {C,D,E,E} Out> True; |
The function f now appears to occupy all arities greater than 3:
In> RuleBase("f", {x,y,z,t}); CommandLine(1) : Rule base with this arity already defined |
Rule("operator", arity, precedence, predicate) body |
arity, precedence -- integers
predicate -- function returning boolean
body -- expression, body of rule
The arity for a rules database equals the number of arguments. Different rules data bases can be built for functions with the same name but with a different number of arguments.
Rules with a low precedence value will be tried before rules with a high value, so a rule with precedence 0 will be tried before a rule with precedence 1.
HoldArg("operator",parameter) |
parameter -- atom, symbolic name of parameter
The parameter must be an atom from the list of symbolic arguments used when calling RuleBase.
Retract("function",arity) |
arity -- positive integer
Assignment := of a function does this to the function being (re)defined.
UnFence("operator",arity) |
arity -- positive integers
The standard library functions For and ForEach use UnFence.
HoldArgNr("function", arity, argNum) |
arity, argNum -- positive integers
RuleBaseArgList("operator", arity) |
arity -- integer
Make sure that the arguments of Macro... commands evaluate to expressions that would normally be used in the non-macro versions!
`(expression) |
To invoke this functionality, a backquote ` needs to be placed in front of an expression. Parentheses around the expression are needed because the backquote binds tighter than other operators.
The expression should contain some variables (assigned atoms) with the special prefix operator @. Variables prefixed by @ will be evaluated even if they are inside function arguments that are normally not evaluated (e.g. functions declared with HoldArg). If the @var pair is in place of a function name, e.g. "@f(x)", then at the first stage of evaluation the function name itself is replaced, not the return value of the function (see example); so at the second stage of evaluation, a new function may be called.
One way to view backquoting is to view it as a parametric expression generator. @var pairs get substituted with the value of the variable var even in contexts where nothing would be evaluated. This effect can be also achieved using UnList and Hold but the resulting code is much more difficult to read and maintain.
This operation is relatively slow since a new expression is built before it is evaluated, but nonetheless backquoting is a powerful mechanism that sometimes allows to greatly simplify code.
In> Decl(f1,f2) := \ In> `(@f1(x_IsNumber) <-- N(@f2(x))); Out> True; In> Decl(nSin,Sin) Out> True; In> Sin(1) Out> Sin(1); In> nSin(1) Out> 0.8414709848; |
This example assigns the expression func(value) to variable var. Normally the first argument of Set would be unevaluated.
In> SetF(var,func,value) := \ In> `(Set(@var,@func(@value))); Out> True; In> SetF(a,Sin,x) Out> True; In> a Out> Sin(x); |
DefMacroRuleBase(name,params) |
params -- list of arguments
With the usual functions, the evaluation model is that of the applicative-order model of substitution, meaning that first the arguments are evaluated, and then the function is applied to the result of evaluating these arguments. The function is entered, and the code inside the function can not access local variables outside of its own local variables.
With macros, the evaluation model is that of the normal-order model of substitution, meaning that all occurrences of variables in an expression are first substituted into the body of the macro, and only then is the resulting expression evaluated in its calling environment. This is important, because then in principle a macro body can access the local variables from the calling environment, whereas functions can not do that.
As an example, suppose there is a function square, which squares its argument, and a function add, which adds its arguments. Suppose the definitions of these functions are:
add(x,y) <-- x+y; |
square(x) <-- x*x; |
add(square(2),square(3)) |
add(4,9) |
In contrast, when add is a macro, the arguments to add are first expanded. So
add(square(2),square(3)) |
square(2) + square(3) |
Macros are useful for customizing syntax, and compilers can potentially greatly optimize macros, as they can be inlined in the calling environment, and optimized accordingly.
There are disadvantages, however. In interpreted mode, macros are slower, as the requirement for substitution means that a new expression to be evaluated has to be created on the fly. Also, when one of the parameters to the macro occur more than once in the body of the macro, it is evaluated multiple times.
When defining transformation rules for macros, the variables to be substituted need to be preceded by the @ operator, similar to the back-quoting mechanism. Apart from that, the two are similar, and all transformation rules can also be applied to macros.
Macros can co-exist with functions with the same name but different arity. For instance, one can have a function foo(a,b) with two arguments, and a macro foo(a,b,c) with three arguments.
In> DefMacroRuleBase("myfor",{init,pred,inc,body}) Out> True; In> myfor(_init,_pred,_inc,_body)<--[@init;While(@pred)[@body;@inc;];True;]; Out> True; In> a:=10 Out> 10; In> myfor(i:=1,i<10,i++,Echo(a*i)) 10 20 30 40 50 60 70 80 90 Out> True; In> i Out> 10; |
DefMacroRuleBaseListed("name", params) |
params -- list of arguments to function
SetExtraInfo(expr,tag) GetExtraInfo(expr) |
tag -- tag information (any other expression)
The function SetExtraInfo returns the tagged expression, leaving the original expression alone. This means there is a common pitfall: be sure to assign the returned value to a variable, or the tagged expression is lost when the temporary object is destroyed.
The original expression is left unmodified, and the tagged expression returned, in order to keep the atomic objects small. To tag an object, a new type of object is created from the old object, with one added property (the tag). The tag can be any expression whatsoever.
The function GetExtraInfo(x) retrieves this tag expression from an object x. If an object has no tag, it looks the same as if it had a tag with value False.
No part of the Yacas core uses tags in a way that is visible to the outside world, so for specific purposes a programmer can devise a format to use for tag information. Association lists (hashes) are a natural fit for this, although it is not required and a tag can be any object (except the atom False because it is indistinguishable from having no tag information). Using association lists is highly advised since it is most likely to be the format used by other parts of the library, and one needs to avoid clashes with other library code. Typically, an object will either have no tag or a tag which is an associative list (perhaps empty). A script that uses tagged objects will check whether an object has a tag and if so, will add or modify certain entries of the association list, preserving any other tag information.
Note that FlatCopy currently does not copy the tag information (see examples).
In> a:=2*b Out> 2*b; In> a:=SetExtraInfo(a,{{"type","integer"}}) Out> 2*b; In> a Out> 2*b; In> GetExtraInfo(a) Out> {{"type","integer"}}; In> GetExtraInfo(a)["type"] Out> "integer"; In> c:=a Out> 2*b; In> GetExtraInfo(c) Out> {{"type","integer"}}; In> c Out> 2*b; In> d:=FlatCopy(a); Out> 2*b; In> GetExtraInfo(d) Out> False; |
GarbageCollect() |
Reference counting refers to bookkeeping where in each object a counter is held, keeping track of the number of parts in the system using that object. When this count drops to zero, the object is automatically removed. Reference counting is not the fastest way of doing garbage collection, but it can be implemented in a very clean way with very little code.
Among the most important objects that are not reference counted are the strings. GarbageCollect collects these and disposes of them when they are not used any more.
GarbageCollect is useful when doing a lot of text processing, to clean up the text buffers. It is not highly needed, but it keeps memory use low.
FindFunction(function) |
In> FindFunction("Sum") Out> "sums.rep/code.ys"; In> FindFunction("Integrate") Out> "integrate.rep/code.ys"; |
Secure(body) |