Meta Lua Recipes

lua-users home
wiki

These are examples of MetaLua syntax extensions.

Postfix if/unless

Syntax:

( `return´ explist? | `break´ ) ( ( `if´ | `unless´ ) expr )?

Example:

-{ extension 'ifpost' }

for _,v in ipairs(t) do
  break if v > 10
  break unless v <= 10       -- equivalent
  return 1,2 if v == 5
  return 1,2 unless v ~= 5   -- equivalent
end

Implementation: [*1]

-- metalua/extension/ifpost.mlua
-- Supports postfix if/unless syntax
--   ( return <exprlist>? | break ) ( ( if | unless ) <expr> )?
-- similar to as in Perl.
-- Note: this does not conflict with Lua syntax since return/break
-- must be at the end of a block.

local function returnif_builder (x)
  local r, cond = unpack(x)
  if cond then
    return +{stat: if -{cond[1]} then -{ `Return{unpack(r)} } end }
  else return `Return{unpack(r)} end
end

local function breakif_builder(x)
  local cond = unpack(x)
  if cond then return +{block: if -{cond[1]} then break end}
  else return +{block: break } end
end

local function unless_builder(x)
  local expr = unpack(x)
  return +{ not( -{expr} ) }
end

local return_expr_list_parser = gg.list { 
   mlp.expr, separators = ",", terminators = mlp.block.terminators
}

mlp.lexer:add 'unless'

mlp.stat:del 'return'
mlp.stat:add{
  "return", return_expr_list_parser,
  gg.multisequence {
    {"if", mlp.expr},
    {"unless", gg.sequence { mlp.expr, builder=unless_builder } },
  },
  builder = returnif_builder }

mlp.stat:del 'break'
mlp.stat:add{
  "break",
  gg.multisequence {
    {"if", mlp.expr},
    {"unless", gg.sequence { mlp.expr, builder=unless_builder } },
  },
  builder = breakif_builder }

Assignments in Expressions

Syntax:

exp ::= var `=´ exp

Example:

-{ extension 'assignmentexpressions' }

local x = t[k] or (t[k] = {})

-- equivalent to
local x = t[k]
if not x then x = {}; t[k] = x end

Implementation: [*1]

-- metalua/extension/assignmentexpressions.mlua
local function builder (op1, _, op2)
  local v = mlp.gensym()
  local s = `Set{ { op1 }, {v} }
  return `Stat{ +{block: local -{v} = -{op2}; -{s} }, v }
end
mlp.expr.infix:add{ '=', prec=10, assoc='right', builder = builder } 

See also StatementsInExpressions.

Expressions as Statements

Syntax:

stat ::= exp

Example:

-{ extension 'expressionstatements' }

f() or error 'failed!'

Implementation: [*1]

-- metalua/extension/expressionstatements.mlua

-- We will overwrite mlp.stat.default, which normally handles
-- assignments and function call statements (assign_or_call_stat_parser).
-- To avoid breaking assignments, we'll make assignments be
-- expressions (which are in turn here made statements).
-- Function calls, on the other hand, are already expressions.
extension 'assignmentexpressions'

local function builder (expr)
  local v = mlp.gensym()
  return +{block: local -{v} = -{expr[1]} }
end
mlp.stat.default = gg.sequence{mlp.expr, builder = builder }

See also ExpressionsAsStatements.

String Interpolation

Syntax:

`${´ expr `}´    (embedded in string literal)

Notice that this version of string interpolation has an edge over other solutions: interpolation is done at compile-time, not run-time, so interpolated stuff are compiled only once. -- FabienFleutot.

Example:

-{ extension 'stringinterpolation' }

local x = 5
print("test ${x+2} asdf")  --> 7

Implementation: [*1]

-- metalua/extension/stringinterpolation.mlua

local function makeparser(f)
  return function(...)
    local res = f(...)
    if res and res.tag == 'String' then
      local s = res[1]
      local expr
      -- note: left-associative. desirable?
      local function concat(o)
        if not expr then
          expr = o
        else
          expr = `Op{'concat', expr, o}
        end
      end
      local i = 1
      local _ = s:gsub('(.-)$(%b{})()',

         function(text, var, pos)
           var = var:sub(2, var:len()-1)
           if text ~= '' then concat(`String{text}) end
           local expr2 = mlp.expr:parse(mlp.lexer:newstream(var))
           concat( expr2 )
           i = pos
         end
      )
      local rest = s:sub(i)
      if rest ~= '' then concat(`String{rest}) end
      expr = expr or `String ''
      return expr
    end
    return res
  end
end

mlp.expr.primary.default = makeparser(mlp.expr.primary.default)
mlp.expr.suffix.default.parse  = makeparser(mlp.expr.suffix.default.parse)

See also StringInterpolation.

Multiline String Break Escapes

Syntax:

`$´ (`\r´ | `\n´) ... `$´     (embedded in string literal)

Example:

-{ extension 'stringbreaks' }

print [[This is a very long sentence $
       $that spans multiple lines and $
       $is very long and spans multiple $
       $lines.]]

Prints "This is a very long sentence that spans multiple lines and is very long and spans multiple lines." (on one line).

Implementation: [*1]

-- metalua/extension/stringbreaks.mlua
-- http://lua-users.org/lists/lua-l/2008-01/msg00790.html

local function makeparser(f)
  return function(...)
    local res = f(...)
    if res and res.tag == 'String' then
      local s = res[1]
      s = s:gsub("%$[\r\n].-%$", "")
      return `String{s}
    end
    return res
  end
end

mlp.expr.primary.default = makeparser(mlp.expr.primary.default)
mlp.expr.suffix.default.parse  = makeparser(mlp.expr.suffix.default.parse)

Based on suggestion in LuaList:2008-01/msg00790.html .

Operator Declarations

Syntax:

stat ::= `infixoperator´ Name

Example:

 -{ extension 'infixoperator' }

local function plus(x,y) return x+y end
infixoperator plus

print(2 plus 3)

Implementation: [*1]

-- metalua/extension/infixoperator.mlua
local function builder (id, prec)
  mlp.lexer:add(id[1][1]) -- turn infix opname into a keyword
  mlp.expr.infix:add {id[1][1], prec=50, assoc='left', builder = |op1, _, op2| `Call{id[1], op1, op2} }
  return +{block: }
end

mlp.lexer:add 'infixoperator'
mlp.stat:add {'infixoperator', mlp.id, builder = builder}

This example could be extended. See also CustomOperators.

(Fabien:) Metalua has a native way to use functions at infix positions, borrowed from Haskell: a function name framed between backquotes is an infix, left-associative operator. For instance:

function plus(a,b) return a+b end
c = 2 `plus` 2
assert(c==4)

Metalua already defines some useful operators from C: +=, -=. /=, *=. New ones can be added easily:

"!=" as a more familiar alias for "~="

mlp.lexer:add "!="
mlp.expr.infix:add {
 '!=',
 prec = 30,
 builder = |a, _, b| +{-{a} ~= -{b}}
}
"!" as an alias for "not"
mlp.lexer:add "!"
mlp.expr.prefix:add {
 '!',
 prec = 80,
 builder = |_, a| +{not -{a}}
}
"&&" and "||"
mlp.lexer:add "&&"
mlp.expr.infix:add {
 '&&',
 prec = 20,
 builder = |a, _, b| +{-{a} and -{b}}
}

mlp.lexer:add "||"
mlp.expr.infix:add {
 '||',
 prec = 10,
 builder = |a, _, b| +{-{a} or -{b}}
}

There is also a standard way to define new assignment operators: add an operator->builder entry in table mlp.stat.assignments:

mlp.keywords.add "|="
mlp.stat.assignments["|="] = function (left_expr_list, right_expr_list)
   assert (#left_expr_list==1 and #right_expr_list==1)
   local left, right = left_expr_list[1], right_expr_list[1]
   return -{stat: (-{left}) = -{left} or -{right} }
end

label and goto

mlp.lexer:add "label"
mlp.stat:add {
 "label",
 mlp.string,
 builder = |a| `Label{a[1]}
}

mlp.lexer:add "goto"
mlp.stat:add {
 "goto",
 mlp.string,
 builder = |a| `Goto{a[1]}
}

Example:

goto "foo"
print "you won't see this"
label "foo"

Constants

Syntax: identifiers that only contain capital letters and underscores are interpreted as constants that should not be writable to.

Example:

 -{ extension 'const' }

local y
local MAX_SIZE = 10
x,y = 1,2     -- ok
print(MAX_SIZE)
MAX_SIZE = 11  -- raises compile time error (writing to constant)

Implementation: [*1]

-- metalua/extension/const.mlua
local function check(o)
  if o and o.tag == 'Id' and o[1]:match('^[A-Z_]+$') then
    error('error: writing to constant ' .. o[1] .. ' at line ' .. o.line, 3)
  end
end

local function const_transformer(ast)
  if not ast then return end
  if ast.tag == 'Set' then
    for _,v in ipairs(ast[1]) do
      check(v)
    end
  end
end

mlp.stat.transformers:add(const_transformer)

This example is rudimentary and could be extended.

Other Examples in Metalua

Additional examples are included in Metalua:


[*1] licensed under the same terms as Lua itself (MIT license).--DavidManura

See Also


RecentChanges · preferences
edit · history
Last edited May 6, 2009 6:49 am GMT (diff)