]> git.lizzy.rs Git - metalua.git/blobdiff - metalua/compiler/parser/meta.lua
Merge branch 'master' of ssh://git.eclipse.org/gitroot/koneki/org.eclipse.koneki...
[metalua.git] / metalua / compiler / parser / meta.lua
diff --git a/metalua/compiler/parser/meta.lua b/metalua/compiler/parser/meta.lua
new file mode 100644 (file)
index 0000000..71eb3c3
--- /dev/null
@@ -0,0 +1,138 @@
+-------------------------------------------------------------------------------
+-- Copyright (c) 2006-2014 Fabien Fleutot and others.
+--
+-- All rights reserved.
+--
+-- This program and the accompanying materials are made available
+-- under the terms of the Eclipse Public License v1.0 which
+-- accompanies this distribution, and is available at
+-- http://www.eclipse.org/legal/epl-v10.html
+--
+-- This program and the accompanying materials are also made available
+-- under the terms of the MIT public license which accompanies this
+-- distribution, and is available at http://www.lua.org/license.html
+--
+-- Contributors:
+--     Fabien Fleutot - API and implementation
+--
+-------------------------------------------------------------------------------
+
+-- Compile-time metaprogramming features: splicing ASTs generated during compilation,
+-- AST quasi-quoting helpers.
+
+local gg       = require 'metalua.grammar.generator'
+
+return function(M)
+    local _M = gg.future(M)
+    M.meta={ }
+    local _MM = gg.future(M.meta)
+
+    --------------------------------------------------------------------------------
+    -- External splicing: compile an AST into a chunk, load and evaluate
+    -- that chunk, and replace the chunk by its result (which must also be
+    -- an AST).
+    --------------------------------------------------------------------------------
+
+    -- TODO: that's not part of the parser
+    function M.meta.eval (ast)
+        -- TODO: should there be one mlc per splice, or per parser instance?
+        local mlc = require 'metalua.compiler'.new()
+        local f = mlc :ast_to_function (ast, '=splice')
+        local result=f(M) -- splices act on the current parser
+        return result
+    end
+
+    ----------------------------------------------------------------------------
+    -- Going from an AST to an AST representing that AST
+    -- the only hash-part key being lifted is `"tag"`.
+    -- Doesn't lift subtrees protected inside a `Splice{ ... }.
+    -- e.g. change `Foo{ 123 } into
+    -- `Table{ `Pair{ `String "tag", `String "foo" }, `Number 123 }
+    ----------------------------------------------------------------------------
+    local function lift (t)
+        --print("QUOTING:", table.tostring(t, 60,'nohash'))
+        local cases = { }
+        function cases.table (t)
+            local mt = { tag = "Table" }
+            --table.insert (mt, { tag = "Pair", quote "quote", { tag = "True" } })
+            if t.tag == "Splice" then
+                assert (#t==1, "Invalid splice")
+                local sp = t[1]
+                return sp
+            elseif t.tag then
+                table.insert (mt, { tag="Pair", lift "tag", lift(t.tag) })
+            end
+            for _, v in ipairs (t) do
+                table.insert (mt, lift(v))
+            end
+            return mt
+        end
+        function cases.number  (t) return { tag = "Number", t, quote = true } end
+        function cases.string  (t) return { tag = "String", t, quote = true } end
+        function cases.boolean (t) return { tag = t and "True" or "False", t, quote = true } end
+        local f = cases [type(t)]
+        if f then return f(t) else error ("Cannot quote an AST containing "..tostring(t)) end
+    end
+    M.meta.lift = lift
+
+    --------------------------------------------------------------------------------
+    -- when this variable is false, code inside [-{...}] is compiled and
+    -- avaluated immediately. When it's true (supposedly when we're
+    -- parsing data inside a quasiquote), [-{foo}] is replaced by
+    -- [`Splice{foo}], which will be unpacked by [quote()].
+    --------------------------------------------------------------------------------
+    local in_a_quote = false
+
+    --------------------------------------------------------------------------------
+    -- Parse the inside of a "-{ ... }"
+    --------------------------------------------------------------------------------
+    function M.meta.splice_content (lx)
+        local parser_name = "expr"
+        if lx:is_keyword (lx:peek(2), ":") then
+            local a = lx:next()
+            lx:next() -- skip ":"
+            assert (a.tag=="Id", "Invalid splice parser name")
+            parser_name = a[1]
+        end
+        -- TODO FIXME running a new parser with the old lexer?!
+        local parser = require 'metalua.compiler.parser'.new()
+        local ast = parser [parser_name](lx)
+        if in_a_quote then -- only prevent quotation in this subtree
+            --printf("SPLICE_IN_QUOTE:\n%s", _G.table.tostring(ast, "nohash", 60))
+            return { tag="Splice", ast }
+        else -- convert in a block, eval, replace with result
+            if parser_name == "expr" then ast = { { tag="Return", ast } }
+            elseif parser_name == "stat"  then ast = { ast }
+            elseif parser_name ~= "block" then
+                error ("splice content must be an expr, stat or block") end
+            --printf("EXEC THIS SPLICE:\n%s", _G.table.tostring(ast, "nohash", 60))
+            return M.meta.eval (ast)
+        end
+    end
+
+    M.meta.splice = gg.sequence{ "-{", _MM.splice_content, "}", builder=unpack }
+
+    --------------------------------------------------------------------------------
+    -- Parse the inside of a "+{ ... }"
+    --------------------------------------------------------------------------------
+    function M.meta.quote_content (lx)
+        local parser
+        if lx:is_keyword (lx:peek(2), ":") then -- +{parser: content }
+            local parser_name = M.id(lx)[1]
+            parser = M[parser_name]
+            lx:next() -- skip ":"
+        else -- +{ content }
+            parser = M.expr
+        end
+
+        local prev_iq = in_a_quote
+        in_a_quote = true
+        --print("IN_A_QUOTE")
+        local content = parser (lx)
+        local q_content = M.meta.lift (content)
+        in_a_quote = prev_iq
+        return q_content
+    end
+
+    return M
+end
\ No newline at end of file