Meta Lua Recipes

lua-users home
wiki

Difference (from prior major revision) (minor diff, author diff)

Changed: 412c412
=== Examples in Metalua ===
=== Other Examples in Metalua ===

Added: 417a418
* MethodChainingWrapper - includes MetaLua example that basically makes the __index metamethod lexically scoped by replacing all index operations with a user-defined function. One purpose is to allow injection of custom functions into the string table so that those injections don't conflict with any other modules.

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