local AST = require"ast" local ETypes = require"etype" local Target = require"target" local Logger = require"logger" local NEXT_LABEL_ID = 1 local Scope = {} Scope.__index = Scope function Scope.new(parent) return setmetatable({parent = parent, items = {}}, Scope) end function Scope:find(name) if self.items[name] then return self.items[name] end if self.parent then return self.parent:find(name) end return nil end function Scope:add(name) self.items[name] = AST.VReg(name) return self.items[name] end local Parser = {} Parser.__index = Parser function Parser:parse_root() local root = AST.root() while true do if self:peek(0).type == "eof" then break end local declaration, err = self:parse_declaration() if not declaration then self:log("err", err or "can't parse") break end table.insert(root.children, declaration) end return root end function Parser:parse_declaration() local old_i = self.i local export = self:maybe"export" if not self:maybe"ident" then self.i = old_i return nil, "expected identifier" end local name = self:last().data if not self:maybe":" then return nil, "expected :" end local expr, err = self:parse_expr(0) if not expr then return nil, err end return AST.decl(name, expr, export) end function Parser:parse_expr(precedence) local old_idx = self.i if precedence == 0 then local ret, err = self:parse_expr(precedence + 1) if not ret then self.i = old_idx return nil, err end while self:maybe">" or self:maybe"<" or self:maybe"==" or self:maybe"!=" or self:maybe">=" or self:maybe"<=" do local op = self:last().type local a = ret local b, err = self:parse_expr(precedence + 1) if not b then self.i = old_idx return nil, err end assert(a.etype == b.etype) ret = AST.binop(a.etype, a, op, b) end return ret elseif precedence == 1 then local ret, err = self:parse_expr(precedence + 1) if not ret then self.i = old_idx return nil, err end while self:maybe"+" or self:maybe"-" do local op = self:last().type local a = ret local b, err = self:parse_expr(precedence + 1) if not b then self.i = old_idx return nil, err end assert(a.etype == b.etype) ret = AST.binop(a.etype, a, op, b) end return ret elseif precedence == 2 then local ret, err = self:parse_expr(precedence + 1) if not ret then self.i = old_idx return nil, err end while self:maybe"*" or self:maybe"/" do local op = self:last().type local a = ret local b, err = self:parse_expr(precedence + 1) if not b then self.i = old_idx return nil, err end assert(a.etype == b.etype) ret = AST.binop(a.etype, a, op, b) end return ret elseif precedence == 3 then local ret, err while self:maybe"&" or self:maybe"*" do local op = self:last().type local a, err = self:parse_expr(precedence) if not a then self.i = old_idx return nil, err end ret = AST.unop(op == "&" and ETypes.ref(a.etype) or ETypes.deref(a.etype), op, a) end if not ret then ret, err = self:parse_expr(precedence + 1) end if not ret then self.i = old_idx return nil, err end return ret elseif precedence == 4 then local a, err = self:parse_expr(precedence + 1) if not a then self.i = old_idx return nil, err end while self:maybe"[" do if a.etype.kind == "pointer" then a = AST.unop(a.etype.to, "*", a) end assert(a.etype.kind == "array", "Indexing a non-array") local b, err = self:parse_expr(0) if not b then self.i = old_idx return nil, err end a = AST.unop(a.etype.element_etype, "*", AST.binop(nil, a, "+", b)) assert(self:maybe"]") end return a elseif precedence == 5 then local asdf = self.i -- Okay for etype to be nil local etype = self:parse_etype(0) if self:maybe"?" then return AST.unknown(etype) elseif self:maybe'string' then return AST.cast(AST.string(self:last().data), etype) elseif self:maybe"num" then local tok = self:last().data local base = tonumber(tok:match"^([0-9]+)r") or 10 local val = tok:match"r([0-9a-zA-Z]+)$" or tok return AST.int(etype, tonumber(val, base)) elseif self:maybe"ident" then assert(not etype) local name = self:last().data local vreg = self.scope:find(name) if not vreg then self.i = old_idx return nil, "Undeclared variable " .. name end return AST.var(vreg.etype, vreg) elseif self:maybe"[" then assert(etype) local children = {} if not self:maybe"]" then while true do local ex = self:parse_expr(0) assert(ex) if etype then ex = AST.cast(ex, etype.element_etype) end table.insert(children, ex) if self:maybe"]" then break end if not self:maybe"," then local last = self:last() self.i = old_idx return nil, "expected comma", last end end end local n = AST.array(etype, 0) for _, child in ipairs(children) do table.insert(n.children, child) end return n elseif self:maybe"{" then local n = AST.func(etype) while not self:maybe"}" do local status, err = self:parse_stmt(n) if not status then self.i = old_idx return nil, err end end return n end end self.i = old_idx return nil end function Parser:parse_stmt(chunk) local old_idx = self.i if self:maybe";" then return true elseif self:maybe"return" then local ex = self:parse_expr(0) -- ex can be null table.insert(chunk.children, AST.ret(ex)) return true elseif self:maybe"if" then local condition = self:parse_expr(0) local lbl_id = NEXT_LABEL_ID NEXT_LABEL_ID = NEXT_LABEL_ID + 1 table.insert(chunk.children, AST.jump(condition, lbl_id)) if not self:maybe"{" then self.i = old_idx return false, "expected {" end while not self:maybe"}" do local status, err = self:parse_stmt(chunk) if not status then self.i = old_idx return nil, err end end table.insert(chunk.children, AST.label(lbl_id)) return true elseif self:peek(0).type == "ident" and self:peek(1).type == "=" then local name = self:next().data self:next() local expr = self:parse_expr(0) local vreg = self.scope:find(name) if not vreg then assert(expr.etype) vreg = self.scope:add(name) vreg.etype = expr.etype else assert(vreg.etype == expr.etype, "Type mismatch in assignment") end table.insert(chunk.children, AST.assign(AST.var(vreg.etype, vreg), expr)) return true end -- Try parsing assignment local ex = self:parse_expr(0) if not ex then self.i = old_idx return nil, "expected expression" end if not self:maybe"=" then self.i = old_idx return nil, "expected =" end local ex2 = self:parse_expr(0) if not ex2 then self.i = old_idx return nil, "expected expression" end table.insert(chunk.children, AST.assign(ex, AST.cast(ex2, ex.etype))) return true end function Parser:parse_etype(precedence) local old_idx = self.i if precedence == 0 then local ret = self:parse_etype(precedence + 1) if not ret then return nil end while self:maybe"->" do local a = ret local b = self:parse_etype(precedence + 1) ret = ETypes.func(a, b) while self:maybe("mod") do local mod_type = self:last().data if mod_type == "@save" then if not self:maybe"(" then local last = self:last() self.i = old_idx return nil, "expected (", last end local ex = self:parse_expr(0) if not ex or ex.kind ~= "expr-array" then local last = self:last() self.i = old_idx return nil, "expected array", last end if not self:maybe")" then local last = self:last() self.i = old_idx return nil, "expected )", last end local items = {} for _, child in ipairs(ex.children) do assert(child.kind == "expr-string") if not require"target".REGS[child.value] then self:log("warn", "skipping unknown register " .. child.value) end items[child.value] = true end ret.modifiers["save"] = items else self:log("warn", "skipping unknown modifier " .. mod_type) if self:maybe"(" then local depth = 1 while true do local t = self:next() if t.type == "(" then depth = depth + 1 elseif t.type == ")" then depth = depth - 1 if depth == 0 then break end elseif t.type == "eof" then break end end end end end end return ret elseif precedence == 1 then local a = self:parse_etype(precedence + 1) if not a then return nil end while self:maybe"*" or self:maybe"[" do local old_idx = self.i if self:last().type == "*" then a = ETypes.ref(a) else local length_expr = self:parse_expr(0) if not length_expr or not self:maybe"]" or (length_expr.kind ~= "expr-unknown" and length_expr.kind ~= "expr-int") then -- This cannot be an array type, most likely a case like (string[?] [...]) self.i = old_idx - 1 break end local length if length_expr.kind == "expr-int" then length = length_expr.value end a = ETypes.array(a, length) end end return a elseif precedence == 2 then if self:maybe"(" then if self:maybe")" then return ETypes.struct({}) else self:go_back() end end if self:maybe"stringkw" then return ETypes.string() end if not self:maybe"ident" then self.i = old_idx return nil end local str = self:last().data local ret if str:match"[ui][0-9]+" then ret = ETypes.scalar(str:sub(1, 1) == "u", tonumber(str:sub(2))) elseif str:match"[ui]gpr" then ret = ETypes.scalar(str:sub(1, 1) == "u", Target.GPR_SIZE) else self.i = old_idx return nil end return ret end self.i = old_idx return nil end function Parser:go_back() if self.i <= 1 then error("Already at the beginning") end self.i = self.i - 1 end function Parser:last() return self.tokens[self.i - 1] end function Parser:peek(idx) if self.i + idx > #self.tokens then return {type = "eof"} end return self.tokens[self.i + idx] end function Parser:maybe(token_type) if self.i > #self.tokens then return false end if self.tokens[self.i].type == token_type then self.i = self.i + 1 return true end return false end function Parser:next() local ret = self:peek(0) if ret.type ~= "eof" then self.i = self.i + 1 end return ret end function Parser:log(msg_type, fmt, ...) local tok = self:last() Logger.log(msg_type, tok.y .. ":" .. tok.x .. ", " .. fmt, ...) end return function(tokens) return setmetatable({i = 1, tokens = tokens, scope = Scope.new(nil)}, Parser) end