]> git.lizzy.rs Git - metalua.git/blob - src/compiler/mlp_stat.lua
b959d96551bc3908b46d8c9faf7c1c874a35c451
[metalua.git] / src / compiler / mlp_stat.lua
1 ----------------------------------------------------------------------
2 -- Metalua:  $Id: mlp_stat.lua,v 1.7 2006/11/15 09:07:50 fab13n Exp $
3 --
4 -- Summary: metalua parser, statement/block parser. This is part of
5 --   the definition of module [mlp].
6 --
7 ----------------------------------------------------------------------
8 --
9 -- Copyright (c) 2006, Fabien Fleutot <metalua@gmail.com>.
10 --
11 -- This software is released under the MIT Licence, see licence.txt
12 -- for details.
13 --
14 ----------------------------------------------------------------------
15 --
16 ----------------------------------------------------------------------
17
18 --------------------------------------------------------------------------------
19 --
20 -- Exports API:
21 -- * [mlp.stat()]
22 -- * [mlp.block()]
23 -- * [mlp.for_header()]
24 --
25 --------------------------------------------------------------------------------
26
27 --------------------------------------------------------------------------------
28 -- eta-expansions to break circular dependency
29 --------------------------------------------------------------------------------
30 local expr      = function (lx) return mlp.expr     (lx) end
31 local func_val  = function (lx) return mlp.func_val (lx) end
32 local expr_list = function (lx) return mlp.expr_list(lx) end
33
34 module ("mlp", package.seeall)
35
36 --------------------------------------------------------------------------------
37 -- List of all keywords that indicate the end of a statement block. Users are
38 -- likely to extend this list when designing extensions.
39 --------------------------------------------------------------------------------
40
41
42 local block_terminators = { "else", "elseif", "end", "until", ")", "}", "]" }
43
44 -- FIXME: this must be handled from within GG!!!
45 function block_terminators:add(x) 
46    if type (x) == "table" then for _, y in ipairs(x) do self:add (y) end
47    else _G.table.insert (self, x) end
48 end
49
50 --------------------------------------------------------------------------------
51 -- list of statements, possibly followed by semicolons
52 --------------------------------------------------------------------------------
53 block = gg.list {
54    name        = "statements block",
55    terminators = block_terminators,
56    primary     = function (lx)
57       -- FIXME use gg.optkeyword()
58       local x = stat (lx)
59       if lx:is_keyword (lx:peek(), ";") then lx:next() end
60       return x
61    end }
62
63 --------------------------------------------------------------------------------
64 -- Helper function for "return <expr_list>" parsing.
65 -- Called when parsing return statements.
66 -- The specific test for initial ";" is because it's not a block terminator,
67 -- so without itgg.list would choke on "return ;" statements.
68 -- We don't make a modified copy of block_terminators because this list
69 -- is sometimes modified at runtime, and the return parser would get out of
70 -- sync if it was relying on a copy.
71 --------------------------------------------------------------------------------
72 local return_expr_list_parser = gg.multisequence{
73    { ";" , builder = function() return { } end }, 
74    default = gg.list { 
75       expr, separators = ",", terminators = block_terminators } }
76
77 --------------------------------------------------------------------------------
78 -- for header, between [for] and [do] (exclusive).
79 -- Return the `Forxxx{...} AST, without the body element (the last one).
80 --------------------------------------------------------------------------------
81 function for_header (lx)
82    local var = mlp.id (lx)
83    if lx:is_keyword (lx:peek(), "=") then 
84       -- Fornum: only 1 variable
85       lx:next() -- skip "="
86       local e = expr_list (lx)
87       assert (2 <= #e and #e <= 3, "2 or 3 values in a fornum")
88       return { tag="Fornum", var, unpack (e) }
89    else
90       -- Forin: there might be several vars
91       local a = lx:is_keyword (lx:next(), ",", "in")
92       if a=="in" then var_list = { var, lineinfo = var.lineinfo } else
93          -- several vars; first "," skipped, read other vars
94          var_list = gg.list{ 
95             primary = id, separators = ",", terminators = "in" } (lx)
96          _G.table.insert (var_list, 1, var) -- put back the first variable
97          lx:next() -- skip "in"
98       end
99       local e = expr_list (lx)
100       return { tag="Forin", var_list, e }
101    end
102 end
103
104 --------------------------------------------------------------------------------
105 -- Function def parser helper: id ( . id ) *
106 --------------------------------------------------------------------------------
107 local function fn_builder (list)
108    local r = list[1]
109    for i = 2, #list do r = { tag="Index", r, id2string(list[i]) } end
110    return r
111 end
112 local func_name = gg.list{ id, separators = ".", builder = fn_builder }
113
114 --------------------------------------------------------------------------------
115 -- Function def parser helper: ( : id )?
116 --------------------------------------------------------------------------------
117 local method_name = gg.onkeyword{ name = "method invocation", ":", id, 
118    transformers = { function(x) return x and id2string(x) end } }
119
120 --------------------------------------------------------------------------------
121 -- Function def builder
122 --------------------------------------------------------------------------------
123 local function funcdef_builder(x)
124    local name, method, func = x[1], x[2], x[3]
125    if method then 
126       name = { tag="Index", name, method, lineinfo = {
127          first = name.lineinfo.first,
128          last  = method.lineinfo.last } }
129       _G.table.insert (func[1], 1, {tag="Id", "self"}) 
130    end
131    local r = { tag="Set", {name}, {func} } 
132    r[1].lineinfo = name.lineinfo
133    r[2].lineinfo = func.lineinfo
134    return r
135 end 
136
137
138 --------------------------------------------------------------------------------
139 -- if statement builder
140 --------------------------------------------------------------------------------
141 local function if_builder (x)
142    local cb_pairs, else_block, r = x[1], x[2], {tag="If"}
143    for i=1,#cb_pairs do r[2*i-1]=cb_pairs[i][1]; r[2*i]=cb_pairs[i][2] end
144    if else_block then r[#r+1] = else_block end
145    return r
146 end 
147
148 --------------------------------------------------------------------------------
149 -- produce a list of (expr,block) pairs
150 --------------------------------------------------------------------------------
151 local elseifs_parser = gg.list {
152    gg.sequence { expr, "then", block },
153    separators  = "elseif",
154    terminators = { "else", "end" } }
155
156 --------------------------------------------------------------------------------
157 -- assignments and calls: statements that don't start with a keyword
158 --------------------------------------------------------------------------------
159 local function assign_or_call_stat_parser (lx)
160    local e = expr_list (lx)
161    local a = lx:is_keyword(lx:peek())
162    local op = a and stat.assignments[a]
163    if op then
164       --FIXME: check that [e] is a LHS
165       lx:next()
166       local v = expr_list (lx)
167       if type(op)=="string" then return { tag=op, e, v }
168       else return op (e, v) end
169    else 
170       assert (#e > 0)
171       if #e > 1 then 
172          gg.parse_error (lx, "comma is not a valid statement separator") end
173       if e[1].tag ~= "Call" and e[1].tag ~= "Invoke" then
174          gg.parse_error (lx, "This expression is of type '%s'; "..
175             "only function and method calls make valid statements", 
176             e[1].tag or "<list>")
177       end
178       return e[1]
179    end
180 end
181
182 local_stat_parser = gg.multisequence{
183    -- local function <name> <func_val>
184    { "function", id, func_val, builder = 
185       function(x) 
186          local vars = { x[1], lineinfo = x[1].lineinfo }
187          local vals = { x[2], lineinfo = x[2].lineinfo }
188          return { tag="Localrec", vars, vals } 
189       end },
190    -- local <id_list> ( = <expr_list> )?
191    default = gg.sequence{ id_list, gg.onkeyword{ "=", expr_list },
192       builder = function(x) return {tag="Local", x[1], x[2] or { } } end } }
193
194 --------------------------------------------------------------------------------
195 -- statement
196 --------------------------------------------------------------------------------
197 stat = gg.multisequence { 
198    name="statement",
199    { "do", block, "end", builder = 
200       function (x) return { tag="Do", unpack (x[1]) } end },
201    { "for", for_header, "do", block, "end", builder = 
202       function (x) x[1][#x[1]+1] = x[2]; return x[1] end },
203    { "function", func_name, method_name, func_val, builder=funcdef_builder },
204    { "while", expr, "do", block, "end", builder = "While" },
205    { "repeat", block, "until", expr, builder = "Repeat" },
206    { "local", local_stat_parser, builder = fget (1) },
207    { "return", return_expr_list_parser, builder = fget (1, "Return") },
208    { "break", builder = function() return { tag="Break" } end },
209    { "-{", splice_content, "}", builder = fget(1) },
210    { "if", elseifs_parser, gg.onkeyword{ "else", block }, "end", 
211      builder = if_builder },
212    default = assign_or_call_stat_parser }
213
214 stat.assignments = {
215    ["="] = "Set" }
216
217 function stat.assignments:add(k, v) self[k] = v end