Previous page Table of Contents Next page
An operator is a subclass of function. A name that has a known definition as an operator and is not denatured by a \ prefix has special syntactic meaning in expressions. An operator can potentially be used in prefix form and/or in infix form. Either form of an operator can be a function call, a macro call, or disallowed, depending on the definition of the operator. An operator function call has the semantics of an ordinary function call, just in a special syntax. An operator has precedence and associativity properties that control its parsing. The right-hand operand of an infix operator, and the only operand of a prefix-operator, will not include infix operators of lower precedence, and will only include infix operators of equal precedence if the operator is right-associative.
If an operator is a function call in both prefix and infix form, both forms invoke the same function. Methods that accept one argument are applicable to prefix calls. Methods that accept two arguments are applicable to infix calls.
defoperator defines an operator. You specify its name, precedence, associativity (default left), prefix usage, and infix usage. Each usage is either a macro definition or a function parameter list (which is only for documentation). If no usage is specified, that usage is disallowed.
Operator macros are a powerful language extension facility that can be used to define the () and [] syntaxes that would otherwise have to be built in. Other examples of operator macros include and, or, is, and :=.
The specification for an infix or prefix operator macro consists of an optional pattern, an =>, and a block whose value is the macro expansion. The pattern is specially modified so that if it ends with a pattern variable whose syntactic type is expression (even inside of a repeat), the parsing of that expression will respect operator precedence and associativity; parsing will stop short of the maximal length expression when an operator of lower precedence is encountered. Thus "a and b or c" parses as "(a and b) or c" rather than as "a and (b or c)", and "a or b and c" parses as "a or (b and c)". Similarly "not a or b" parses as "(not a) or b" rather than as "not (a or b)", while "not f(x)" parses as "not(f(x))" rather than "(not f)(x)".
In an infix operator macro's parser function, the name lhs is automatically defined to be the expression on the left hand side. The explicit pattern only applies to the right hand side that follows the operator.
Note that when an operator is a macro, the definition of its name is not a macro. The definition of its name is an operator. You can have an operator macro that expands into a call to the function with the same name. Infix [ is an example, which is a macro so it can match the ] and so it can allow multiple arguments separated by commas.
To define a postfix operator, just define an infix operator macro with an empty pattern for the right-hand side.
Operator overloading: If an operator is a function call, simply define methods for it that are applicable to the argument(s) of interest, the same as for a non-operator function.
The syntax of defoperator is
defmacro defoperator ?:name [ ^ precedence: ?precedence is literal | ^ associative: [ left | ??right-associative right ] | ^ [ prefix: ?prefix-parms is parameter-list | prefix-macro: ?prefix-pattern is pattern \=> ?prefix-body is block ] | ^ [ infix: ?infix-parms is parameter-list | infix-macro: ?infix-pattern is pattern \=> ?infix-body is block ] ]* => ...
Some examples of built-in operators:
defoperator + precedence: 120 prefix: (arg) infix: (lhs, rhs) defoperator - precedence: 120 prefix: (negatend) infix: (minuend, subtrahend) defoperator / precedence: 130 infix: (dividend, divisor) defoperator and precedence: 40 infix-macro: ?rhs => `if ?lhs then ?rhs else false` ;; This works because macros are hygienic, so the local variable ;; named temp does not conflict with anything else named temp ;; It wraps the whole thing in a block so the scope of temp cannot ;; become global defoperator or precedence: 30 infix-operator: ?rhs => `block if def temp = ?lhs temp else ?rhs` defoperator \( precedence: 200 ;; Prefix () is grouping prefix-macro: ?x \) => x ;; Infix () is function call ;;--- The real definition has additional support for spread-invocation and currying infix-macro: ?:argument-list \) => invocation@compiler(lhs, argument-list...) defoperator \[ precedence: 200 ;; Infix [] is subscripting - call the \[ function infix-macro: ?:argument-list \] => `\[( { ?argument-list & , }* )` ;; Prefix [] is list or dictionary constructor prefix-macro: { ?element [ \=> ?value ] & , ^^ }* \] => ;; If no arrows are present, it is a list if every?(\not, value) `list( { ?element & , }* )` ;; Otherwise arrows must be used consistently else if member?(false, value) error("Inconsistent use of arrows in [...] syntax") ;; With arrows it is a dictionary else `dictionary( { ?element , ?value & , }* )` ;; Construct a function whose name is specified by the optional variable ;; and whose parameters and result type are specified by the parameter-list ;; The name is only for debugging and has no other significance defmacro fun [ ?:variable ] ?:parameter-list [ ?:annotations ] ?:block => apply-annotations(annotations, functation@compiler(variable or #anonymous, get-local-compiler-scope(), parameter-list, block))
Previous page Table of Contents Next page