Meta Lua Recipes


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 12:49 am GMT (diff)

AltStyle によって変換されたページ (->オリジナル) /