Meta Lua Recipes |
|
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 }
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.
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.
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.
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 .
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}} }
mlp.lexer:add "!" mlp.expr.prefix:add { '!', prec = 80, builder = |_, a| +{not -{a}} }
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
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"
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.
Additional examples are included in Metalua: