Programming Language for Old Timers


by David A. Moon
February 2006 .. September 2008

Comments and criticisms to dave underscore moon atsign alum dot mit dot edu.


Previous page   Table of Contents   Next page


Operators

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 parsing of its infix form.

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)". "not a or b" parses as "(not a) or b" rather than as "not (a or b)", not because of operator precedence but because the argument to a prefix operator is always a unit.

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:

defoperator +
  precedence: 60
  prefix: (arg)
  infix: (lhs, rhs)

defoperator -
  precedence: 60
  prefix: (negatend)
  infix: (minuend, subtrahend)

defoperator /
  precedence: 70
  infix: (dividend, divisor)

defoperator and
  precedence: 20
  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
defoperator or
  precedence: 10
  infix-operator: ?rhs =>
        `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: { ?subscript & ~^ , }+ \] => `\[(?lhs { , ?subscript }+ )`

  ;; Prefix [] constructs a function with one method
  prefix-macro: =>
    with-new-compiler-scope
      parse-function-constructor(token-stream, true)

;; The prefix [ macro cannot quite be written with just a pattern so
;; it uses this helper function
defparser function-constructor
  [ name: ?:name & , ] ?:parameter-list \=> ?:block \] =>
  functation@compiler(name or #anonymous,
                      get-local-compiler-scope(),
                      parameter-list,
                      block)


Previous page   Table of Contents   Next page