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