#!/usr/bin/env lua local function lookupify(tb, dst, not_val) if not dst then dst = tb end local val = not not_val for _, v in pairs(tb) do dst[v] = val end return tb end local WhiteChars = lookupify{' ', '\n', '\t', '\r'} local Spaces = lookupify{' ', '\t'} local EscapeLookup = {['\r'] = '\\r', ['\n'] = '\\n', ['\t'] = '\\t', ['"'] = '\\"', ["'"] = "\\'"} local LowerChars = lookupify{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'} local UpperChars = lookupify{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'} local Digits = lookupify{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'} local HexDigits = lookupify{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'a', 'B', 'b', 'C', 'c', 'D', 'd', 'E', 'e', 'F', 'f'} local BinDigits = lookupify{'0', '1'} local Symbols = lookupify{'+', '-', '*', '/', '^', '%', ',', '{', '}', '[', ']', '(', ')', ';', '#', '&', '|', '!'} local Keywords = lookupify{ 'and', 'break', 'do', 'else', 'elseif', 'end', 'false', 'for', 'function', 'goto', 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat', 'return', 'then', 'true', 'until', 'while', }; -------------------------------------------------------- 6502::begin local Keywords_control = { -- control keywords 'samepage', 'crosspage', } local Keywords_data = { 'dc', } local Keywords_6502 = { -- opcodes 'adc', 'and', 'asl', 'bcc', 'bcs', 'beq', 'bit', 'bmi', 'bne', 'bpl', 'brk', 'bvc', 'bvs', 'clc', 'cld', 'cli', 'clv', 'cmp', 'cpx', 'cpy', 'dec', 'dex', 'dey', 'eor', 'inc', 'inx', 'iny', 'jmp', 'jsr', 'lda', 'ldx', 'ldy', 'lsr', 'nop', 'ora', 'pha', 'php', 'pla', 'plp', 'rol', 'ror', 'rti', 'rts', 'sbc', 'sec', 'sed', 'sei', 'sta', 'stx', 'sty', 'tax', 'tay', 'tsx', 'txa', 'txs', 'tya', -- illegal opcodes 'anc', 'ane', 'arr', 'asr', 'dcp', 'isb', 'jam', 'las', 'lax', 'rla', 'rra', 'sax', 'sbx', 'sha', 'shs', 'shx', 'shy', 'slo', 'sre', } local Registers_6502 = { a=8, x=8, y=8, s=8 } local syntax6502_on local function syntax6502(on) syntax6502_on = on lookupify(Keywords_control, Keywords, not on) lookupify(Keywords_data, Keywords, not on) lookupify(Keywords_6502, Keywords, not on) end syntax6502(true) local syntax6502k_on local function syntax6502k(on) syntax6502k_on = on end local opcode_arg_encapsulate_on local function opcode_arg_encapsulate(on) opcode_arg_encapsulate_on = not opcode_arg_encapsulate_on end opcode_arg_encapsulate(true) local opcode_encapsulate = {} -- additionnal opcode, to have basic encapsulation (function(a) return a end) local opcode_alias = {} -- alternate user names for opcodes local opcode_implied = lookupify{ 'asl', 'brk', 'clc', 'cld', 'cli', 'clv', 'dex', 'dey', 'inx', 'iny', 'lsr', 'nop', 'pha', 'php', 'pla', 'plp', 'rol', 'ror', 'rti', 'rts', 'sec', 'sed', 'sei', 'tax', 'tay', 'tsx', 'txa', 'txs', 'tya', -- illegal opcodes 'jam', } local opcode_immediate = lookupify{ 'adc', 'and', 'cmp', 'cpx', 'cpy', 'eor', 'lda', 'ldx', 'ldy', 'ora', 'sbc', -- illegal opcodes 'anc', 'ane', 'arr', 'asr', 'jam', 'lax', 'nop', 'sbx', } local opcode_zeropage = lookupify{ 'adc', 'and', 'asl', 'bit', 'cmp', 'cpy', 'cpx', 'dec', 'eor', 'inc', 'lda', 'ldx', 'ldy', 'lsr', 'nop', 'ora', 'rol', 'ror', 'sbc', 'sta', 'stx', 'sty', -- illegal opcodes 'dcp', 'isb', 'jam', 'lax', 'rla', 'rra', 'sax', 'slo', 'sre', } local opcode_zeropage_x = lookupify{ 'adc', 'and', 'asl', 'cmp', 'dec', 'eor', 'inc', 'lda', 'ldy', 'lsr', 'ora', 'rol', 'ror', 'sbc', 'sta', 'sty', -- illegal opcodes 'dcp', 'isb', 'jam', 'nop', 'rla', 'rra', 'slo', 'sre', } local opcode_zeropage_y = lookupify{ 'ldx', 'stx', -- illegal opcodes 'lax', 'sax', } local opcode_absolute = lookupify{ 'adc', 'and', 'asl', 'bit', 'cmp', 'cpy', 'cpx', 'dec', 'eor', 'inc', 'jmp', 'jsr', 'lda', 'ldx', 'ldy', 'lsr', 'nop', 'ora', 'rol', 'ror', 'sbc', 'sta', 'stx', 'sty', -- illegal opcodes 'dcp', 'isb', 'jam', 'lax', 'rla', 'rra', 'sax', 'slo', 'sre', } local opcode_absolute_x = lookupify{ 'adc', 'and', 'asl', 'cmp', 'dec', 'eor', 'inc', 'lda', 'ldy', 'lsr', 'ora', 'rol', 'ror', 'sbc', 'sta', -- illegal opcodes 'dcp', 'isb', 'jam', 'nop', 'rla', 'rra', 'shy', 'slo', 'sre', } local opcode_absolute_y = lookupify{ 'adc', 'and', 'cmp', 'eor', 'lda', 'ldx', 'ora', 'sbc', 'sta', -- illegal opcodes 'dcp', 'isb', 'jam', 'las', 'lax', 'rla', 'rra', 'sax', 'sha', 'shx', 'slo', 'shs', 'sre', } local opcode_indirect = lookupify{ 'jmp', -- illegal opcodes 'jam', } local opcode_indirect_x = lookupify{ 'adc', 'and', 'cmp', 'eor', 'lda', 'ora', 'sbc', 'sta', -- illegal opcodes 'dcp', 'isb', 'jam', 'lax', 'rla', 'rra', 'sax', 'slo', 'sre', } local opcode_indirect_y = lookupify{ 'adc', 'and', 'cmp', 'eor', 'lda', 'ora', 'sbc', 'sta', -- illegal opcodes 'dcp', 'isb', 'jam', 'lax', 'rla', 'rra', 'sha', 'slo', 'sre', } local opcode_relative = lookupify{ 'bcc', 'bcs', 'beq', 'bmi', 'bne', 'bpl', 'bvc', 'bvs', } local addressing_map = { imp = opcode_implied, imm = opcode_immediate, zpg = opcode_zeropage, zpx = opcode_zeropage_x, zpy = opcode_zeropage_y, abs = opcode_absolute, abx = opcode_absolute_x, aby = opcode_absolute_y, ind = opcode_indirect, inx = opcode_indirect_x, iny = opcode_indirect_y, rel = opcode_relative, } -------------------------------------------------------- 6502::end local Scope = { new = function(self, parent) local s = { Parent = parent, Locals = { }, Globals = { }, Children = { }, } if parent then table.insert(parent.Children, s) end return setmetatable(s, { __index = self }) end, AddLocal = function(self, v) table.insert(self.Locals, v) end, AddGlobal = function(self, v) table.insert(self.Globals, v) end, CreateLocal = function(self, name) local v v = self:GetLocal(name) if v then return v end v = { } v.Scope = self v.Name = name v.IsGlobal = false self:AddLocal(v) return v end, GetLocal = function(self, name) for k, var in pairs(self.Locals) do if var.Name == name then return var end end if self.Parent then return self.Parent:GetLocal(name) end end, CreateGlobal = function(self, name) local v v = self:GetGlobal(name) if v then return v end v = { } v.Scope = self v.Name = name v.IsGlobal = true self:AddGlobal(v) return v end, GetGlobal = function(self, name) for k, v in pairs(self.Globals) do if v.Name == name then return v end end if self.Parent then return self.Parent:GetGlobal(name) end end, } local function LexLua(src) --token dump local tokens = {} local st, err = pcall(function() --line / char / pointer tracking local p = 1 local line = 1 local char = 1 --get / peek functions local function get() local c = src:sub(p,p) if c == '\n' then char = 1 line = line + 1 else char = char + 1 end p = p + 1 return c end local function get_n(count) for i=1,count do get() end end local function peek(n) n = n or 0 return src:sub(p+n,p+n) end local function peek_n(sz) return src:sub(p, p+sz-1) end local function consume(chars) local c = peek() for i = 1, #chars do if c == chars:sub(i,i) then return get() end end end local function get_word(spaces) if not spaces then spaces = Spaces end local c repeat get() c = peek() until not spaces[c] local start = p repeat get() c = peek() until not (UpperChars[c] or LowerChars[c] or Digits[c] or c == '_') return src:sub(start, p-1) end local function peek_ident(i, spaces) if not spaces then spaces = Spaces end local c while true do c=peek(i) if not spaces[c] then break end i=i+1 end if not (UpperChars[c] or LowerChars[c] or c == '_') then return end local start = p+i repeat i=i+1 c = peek(i) until not (UpperChars[c] or LowerChars[c] or Digits[c] or c == '_') return src:sub(start, p+i-1),i,c end --shared stuff local function generateError(err) return error(">> :"..line..":"..char..": "..err, 0) end local function tryGetLongString() local start = p if peek() == '[' then local equalsCount = 0 local depth = 1 while peek(equalsCount+1) == '=' do equalsCount = equalsCount + 1 end if peek(equalsCount+1) == '[' then --start parsing the string. Strip the starting bit for _ = 0, equalsCount+1 do get() end --get the contents local contentStart = p while true do --check for eof if peek() == '' then generateError("Expected ']"..string.rep('=', equalsCount).."]' near .", 3) end --check for the end local foundEnd = true if peek() == ']' then for i = 1, equalsCount do if peek(i) ~= '=' then foundEnd = false end end if peek(equalsCount+1) ~= ']' then foundEnd = false end else --[[ if peek() == '[' then -- is there an embedded long string? local embedded = true for i = 1, equalsCount do if peek(i) ~= '=' then embedded = false break end end if peek(equalsCount + 1) == '[' and embedded then -- oh look, there was depth = depth + 1 for i = 1, (equalsCount + 2) do get() end end end ]] foundEnd = false end -- if foundEnd then depth = depth - 1 if depth == 0 then break else for i = 1, equalsCount + 2 do get() end end else get() end end --get the interior string local contentString = src:sub(contentStart, p-1) --found the end. Get rid of the trailing bit for i = 0, equalsCount+1 do get() end --get the exterior string local longString = src:sub(start, p-1) --return the stuff return contentString, longString else return nil end else return nil end end --main token emitting loop while true do --get leading whitespace. The leading whitespace will include any comments --preceding the token. This prevents the parser needing to deal with comments --separately. local leading = { } local leadingWhite = '' local longStr = false while true do local c = peek() if c == '#' and peek(1) == '!' and line == 1 then -- #! shebang for linux scripts get() get() leadingWhite = "#!" while peek() ~= '\n' and peek() ~= '' do leadingWhite = leadingWhite .. get() end local token = { Type = 'Comment', CommentType = 'Shebang', Data = leadingWhite, Line = line, Char = char } token.Print = function() return "<"..(token.Type .. string.rep(' ', 7-#token.Type)).." "..(token.Data or '').." >" end leadingWhite = "" table.insert(leading, token) end if c == ' ' or c == '\t' then --whitespace --leadingWhite = leadingWhite..get() local c2 = get() -- ignore whitespace table.insert(leading, { Type = 'Whitespace', Line = line, Char = char, Data = c2 }) elseif c == '\n' or c == '\r' then local nl = get() if leadingWhite ~= "" then local token = { Type = 'Comment', CommentType = longStr and 'LongComment' or 'Comment', Data = leadingWhite, Line = line, Char = char, } token.Print = function() return "<"..(token.Type .. string.rep(' ', 7-#token.Type)).." "..(token.Data or '').." >" end table.insert(leading, token) leadingWhite = "" end table.insert(leading, { Type = 'Whitespace', Line = line, Char = char, Data = nl }) elseif c == '-' and peek(1) == '-' then --comment get() get() leadingWhite = leadingWhite .. '--' local _, wholeText = tryGetLongString() if wholeText then leadingWhite = leadingWhite..wholeText longStr = true else while peek() ~= '\n' and peek() ~= '' do leadingWhite = leadingWhite..get() end end else break end end if leadingWhite ~= "" then local token = { Type = 'Comment', CommentType = longStr and 'LongComment' or 'Comment', Data = leadingWhite, Line = line, Char = char, } token.Print = function() return "<"..(token.Type .. string.rep(' ', 7-#token.Type)).." "..(token.Data or '').." >" end table.insert(leading, token) end --get the initial char local thisLine = line local thisChar = char local errorAt = ":"..line..":"..char..":> " local c = peek() --symbol to emit local toEmit = nil --pragma if char == 1 and peek_n(7) == '#pragma' then get_n(7) local dat,opt = get_word() local onoff = function(f, noerr) opt = get_word() if opt == 'on' then f(true) elseif opt == 'off' then f(false) elseif not noerr then generateError("invalid option for pragma " .. dat .. ", expected: [on,off]") end return opt end if dat == 'syntax6502' then onoff(syntax6502) toEmit = {Type = 'Symbol', Data = ';'} elseif dat == 'syntax6502k' then onoff(function() end) toEmit = {Type = 'Keyword', Data = 'syntax6502k_' .. opt} elseif dat == 'encapsulate' then local opcode = onoff(function() end, true) if opcode then opcode_encapsulate[opcode] = get_word() table.insert(Keywords_6502, opcode) Keywords[opcode] = syntax6502_on toEmit = {Type = 'Symbol', Data = ';'} else toEmit = {Type = 'Keyword', Data = 'encapsulate_' .. opt} end elseif dat == 'add_opcode' then local opcode,addressing = get_word(),get_word() local map = addressing_map[addressing] if not map then generateError("invalid addressing for pragma add_opcode: " .. addressing .. " (opcode: " .. opcode .. ")") end map[opcode] = true table.insert(Keywords_6502, opcode) Keywords[opcode] = syntax6502_on toEmit = {Type = 'Symbol', Data = ';'} elseif dat == 'alias' then local org,new = get_word(),get_word() opcode_alias[new] = org table.insert(Keywords_6502, new) Keywords[new] = syntax6502_on toEmit = {Type = 'Symbol', Data = ';'} else generateError("unknown pragma: " .. dat) end --branch on type elseif c == '' then --eof toEmit = { Type = 'Eof' } elseif UpperChars[c] or LowerChars[c] or c == '_' then --ident or keyword local start = p repeat get() c = peek() until not (UpperChars[c] or LowerChars[c] or Digits[c] or c == '_') local dat = src:sub(start, p-1) if Keywords[dat] then toEmit = {Type = 'Keyword', Data = dat} else toEmit = {Type = 'Ident', Data = dat} end elseif Digits[c] or (peek() == '.' and Digits[peek(1)]) then --number const local start = p local data_override if c == '0' and peek(1) == 'x' then get();get() while HexDigits[peek()] do get() end if consume('Pp') then consume('+-') while Digits[peek()] do get() end end elseif c == '0' and peek(1) == 'b' then get();get() while BinDigits[peek()] do get() end local pstart, val_s, val = p, src:sub(start+2, p-1), 0 for i=1,#val_s do if val_s:sub(i,i) == '1' then val = val + (1<<(#val_s-i)) end end if consume('Pp') then consume('+-') while Digits[peek()] do get() end end data_override = val .. src:sub(pstart, p-1) else while Digits[peek()] do get() end if consume('.') then while Digits[peek()] do get() end end if consume('Ee') then consume('+-') while Digits[peek()] do get() end end end toEmit = {Type = 'Number', Data = data_override or src:sub(start, p-1)} elseif c == '\'' or c == '\"' then local start = p --string const local delim = get() local contentStart = p while true do local c = get() if c == '\\' then get() --get the escape char elseif c == delim then break elseif c == '' then generateError("Unfinished string near ") end end local content = src:sub(contentStart, p-2) local constant = src:sub(start, p-1) toEmit = {Type = 'String', Data = constant, Constant = content} elseif c == '[' then local content, wholetext = tryGetLongString() if wholetext then toEmit = {Type = 'String', Data = wholetext, Constant = content} else get() toEmit = {Type = 'Symbol', Data = '['} end elseif c == '<' and peek(1) == c then get() get() toEmit = {Type = 'Symbol', Data = '<<'} elseif c == '>' and peek(1) == c then get() get() toEmit = {Type = 'Symbol', Data = '>>'} elseif consume('>=<') then toEmit = {Type = 'Symbol', Data = consume('=') and c..'=' or c} elseif consume('~') then toEmit = {Type = 'Symbol', Data = consume('=') and '~=' or '~'} elseif consume('.') then if consume('.') then if consume('.') then toEmit = {Type = 'Symbol', Data = '...'} else toEmit = {Type = 'Symbol', Data = '..'} end else toEmit = {Type = 'Symbol', Data = '.'} end elseif consume(':') then toEmit = {Type = 'Symbol', Data = consume(':') and '::' or ':'} elseif consume('/') then toEmit = {Type = 'Symbol', Data = consume('/') and '//' or '/'} elseif syntax6502_on and consume('@') then if consume('@') then toEmit = {Type = 'Symbol', Data = '@@'} else toEmit = {Type = 'Symbol', Data = '@'} end elseif syntax6502_on and consume('\\') then toEmit = {Type = 'Symbol', Data = '\\'} elseif Symbols[c] then get() toEmit = {Type = 'Symbol', Data = c} else local contents, all = tryGetLongString() if contents then toEmit = {Type = 'String', Data = all, Constant = contents} else generateError("Unexpected Symbol '"..c.."' in source.", 2) end end if toEmit[1] then toEmit[1].LeadingWhite = leading for k,v in ipairs(toEmit) do v.Line = thisLine v.Char = thisChar v.Print = function() return "<"..(v.Type..string.rep(' ', 7-#v.Type)).." "..(v.Data or '').." >" end tokens[#tokens+1] = v end else --add the emitted symbol, after adding some common data toEmit.LeadingWhite = leading -- table of leading whitespace/comments --for k, tok in pairs(leading) do -- tokens[#tokens + 1] = tok --end toEmit.Line = thisLine toEmit.Char = thisChar toEmit.Print = function() return "<"..(toEmit.Type..string.rep(' ', 7-#toEmit.Type)).." "..(toEmit.Data or '').." >" end tokens[#tokens+1] = toEmit --halt after eof has been emitted if toEmit.Type == 'Eof' then break end end end end) if not st then return false, err end --public interface: local tok = {} local savedP = {} local p = 1 function tok:getp() return p end function tok:setp(n) p = n end function tok:getTokenList() return tokens end --getters function tok:Peek(n) n = n or 0 return tokens[math.min(#tokens, p+n)] end function tok:Get(tokenList) local t = tokens[p] p = math.min(p + 1, #tokens) if tokenList then table.insert(tokenList, t) end return t end function tok:Is(t) return tok:Peek().Type == t end --save / restore points in the stream function tok:Save() savedP[#savedP+1] = p end function tok:Commit() savedP[#savedP] = nil end function tok:Restore() p = savedP[#savedP] savedP[#savedP] = nil end --either return a symbol if there is one, or return true if the requested --symbol was gotten. function tok:ConsumeSymbol(symb, tokenList) local t = self:Peek() if t.Type == 'Symbol' then if symb then if t.Data == symb then self:Get(tokenList) return true else return nil end else self:Get(tokenList) return t end else return nil end end function tok:ConsumeKeyword(kw, tokenList) local t = self:Peek() if t.Type == 'Keyword' and t.Data == kw then self:Get(tokenList) return true else return nil end end function tok:IsKeyword(kw) local t = tok:Peek() return t.Type == 'Keyword' and t.Data == kw end function tok:IsSymbol(s) local t = tok:Peek() return t.Type == 'Symbol' and t.Data == s end function tok:IsEof() return tok:Peek().Type == 'Eof' end return true, tok end local function ParseLua(src, src_name) local st, tok if type(src) ~= 'table' then st, tok = LexLua(src) else st, tok = true, src end if not st then return false, tok end -- local function GenerateError(msg) local err = (src_name or '=(string)') .. ":"..tok:Peek().Line..":"..tok:Peek().Char..": "..msg.."\n " --find the line local lineNum = 0 if type(src) == 'string' then for line in src:gmatch("[^\n]*\n?") do if line:sub(-1,-1) == '\n' then line = line:sub(1,-2) end lineNum = lineNum+1 if lineNum == tok:Peek().Line then err = err..line:gsub('\t',' ').."\n" for i = 1, tok:Peek().Char do local c = line:sub(i,i) if c == '\t' then err = err..' ' else err = err..' ' end end err = err.."^^^^" break end end end return err end -- local VarUid = 0 local function CreateScope(parent) local scope = Scope:new(parent) scope.Print = function() return "" end return scope end local ParseExpr local ParseStatementList local ParseSimpleExpr, ParseSubExpr, ParsePrimaryExpr, ParseSuffixedExpr local function ParseFunctionArgsAndBody(scope, tokenList) local funcScope = CreateScope(scope) if not tok:ConsumeSymbol('(', tokenList) then return false, GenerateError("'(' expected") end --arg list local argList = {} local isVarArg = false while not tok:ConsumeSymbol(')', tokenList) do if tok:Is('Ident') then local arg = funcScope:CreateLocal(tok:Get(tokenList).Data) argList[#argList+1] = arg if not tok:ConsumeSymbol(',', tokenList) then if tok:ConsumeSymbol(')', tokenList) then break else return false, GenerateError("')' expected") end end elseif tok:ConsumeSymbol('...', tokenList) then isVarArg = true if not tok:ConsumeSymbol(')', tokenList) then return false, GenerateError("'...' must be the last argument of a function") end break else return false, GenerateError("Argument name or '...' expected") end end --body local st, body = ParseStatementList(funcScope) if not st then return false, body end --end if not tok:ConsumeKeyword('end', tokenList) then return false, GenerateError("'end' expected after function body") end local nodeFunc = {} nodeFunc.AstType = 'Function' nodeFunc.Scope = funcScope nodeFunc.Arguments = argList nodeFunc.Body = body nodeFunc.VarArg = isVarArg nodeFunc.Tokens = tokenList -- return true, nodeFunc end function ParsePrimaryExpr(scope) local tokenList = {} if tok:ConsumeSymbol('(', tokenList) then local st, ex = ParseExpr(scope) if not st then return false, ex end if not tok:ConsumeSymbol(')', tokenList) then return false, GenerateError("')' expected") end if false then --save the information about parenthesized expressions somewhere ex.ParenCount = (ex.ParenCount or 0) + 1 return true, ex else local parensExp = {} parensExp.AstType = 'Parentheses' parensExp.Inner = ex parensExp.Tokens = tokenList return true, parensExp end elseif tok:Is('Ident') then local id = tok:Get(tokenList) local var = scope:GetLocal(id.Data) if not var then var = scope:GetGlobal(id.Data) if not var then var = scope:CreateGlobal(id.Data) end end -- local nodePrimExp = {} nodePrimExp.AstType = 'VarExpr' nodePrimExp.Name = id.Data nodePrimExp.Variable = var nodePrimExp.Tokens = tokenList -- return true, nodePrimExp else return false, GenerateError("Primary expression expected") end end function ParseSuffixedExpr(scope, onlyDotColon) --base primary expression local st, prim = ParsePrimaryExpr(scope) if not st then return false, prim end -- while true do local tokenList = {} if tok:IsSymbol('.') or tok:IsSymbol(':') then local symb = tok:Get(tokenList).Data if not tok:Is('Ident') then return false, GenerateError("Identifier expected") end local id = tok:Get(tokenList) local nodeIndex = {} nodeIndex.AstType = 'MemberExpr' nodeIndex.Base = prim nodeIndex.Indexer = symb nodeIndex.Ident = id nodeIndex.Tokens = tokenList -- prim = nodeIndex elseif not onlyDotColon and tok:ConsumeSymbol('[', tokenList) then local st, ex = ParseExpr(scope) if not st then return false, ex end if not tok:ConsumeSymbol(']', tokenList) then return false, GenerateError("']' expected") end local nodeIndex = {} nodeIndex.AstType = 'IndexExpr' nodeIndex.Base = prim nodeIndex.Index = ex nodeIndex.Tokens = tokenList -- prim = nodeIndex elseif not onlyDotColon and tok:ConsumeSymbol('(', tokenList) then local args = {} while not tok:ConsumeSymbol(')', tokenList) do local st, ex = ParseExpr(scope) if not st then return false, ex end args[#args+1] = ex if not tok:ConsumeSymbol(',', tokenList) then if tok:ConsumeSymbol(')', tokenList) then break else return false, GenerateError("')' expected") end end end local nodeCall = {} nodeCall.AstType = 'CallExpr' nodeCall.Base = prim nodeCall.Arguments = args nodeCall.Tokens = tokenList -- prim = nodeCall elseif not onlyDotColon and tok:Is('String') then --string call local nodeCall = {} nodeCall.AstType = 'StringCallExpr' nodeCall.Base = prim nodeCall.Arguments = { tok:Get(tokenList) } nodeCall.Tokens = tokenList -- prim = nodeCall elseif not onlyDotColon and tok:IsSymbol('{') then --table call local st, ex = ParseSimpleExpr(scope) -- FIX: ParseExpr(scope) parses the table AND and any following binary expressions. -- We just want the table if not st then return false, ex end local nodeCall = {} nodeCall.AstType = 'TableCallExpr' nodeCall.Base = prim nodeCall.Arguments = { ex } nodeCall.Tokens = tokenList -- prim = nodeCall else break end end return true, prim end function ParseSimpleExpr(scope) local tokenList = {} if tok:Is('Number') then local nodeNum = {} nodeNum.AstType = 'NumberExpr' nodeNum.Value = tok:Get(tokenList) nodeNum.Tokens = tokenList return true, nodeNum elseif tok:Is('String') then local nodeStr = {} nodeStr.AstType = 'StringExpr' nodeStr.Value = tok:Get(tokenList) nodeStr.Tokens = tokenList return true, nodeStr elseif tok:ConsumeKeyword('nil', tokenList) then local nodeNil = {} nodeNil.AstType = 'NilExpr' nodeNil.Tokens = tokenList return true, nodeNil elseif tok:IsKeyword('false') or tok:IsKeyword('true') then local nodeBoolean = {} nodeBoolean.AstType = 'BooleanExpr' nodeBoolean.Value = (tok:Get(tokenList).Data == 'true') nodeBoolean.Tokens = tokenList return true, nodeBoolean elseif tok:ConsumeSymbol('...', tokenList) then local nodeDots = {} nodeDots.AstType = 'DotsExpr' nodeDots.Tokens = tokenList return true, nodeDots elseif tok:ConsumeSymbol('{', tokenList) then local v = {} v.AstType = 'ConstructorExpr' v.EntryList = {} -- while true do if tok:IsSymbol('[', tokenList) then --key tok:Get(tokenList) local st, key = ParseExpr(scope) if not st then return false, GenerateError("Key expression expected") end if not tok:ConsumeSymbol(']', tokenList) then return false, GenerateError("']' expected") end if not tok:ConsumeSymbol('=', tokenList) then return false, GenerateError("'=' expected") end local st, value = ParseExpr(scope) if not st then return false, GenerateError("Value expression expected") end v.EntryList[#v.EntryList+1] = { Type = 'Key'; Key = key; Value = value; } elseif tok:Is('Ident') then --value or key local lookahead = tok:Peek(1) if lookahead.Type == 'Symbol' and lookahead.Data == '=' then --we are a key local key = tok:Get(tokenList) if not tok:ConsumeSymbol('=', tokenList) then return false, GenerateError("'=' expected") end local st, value = ParseExpr(scope) if not st then return false, GenerateError("Value expression expected") end v.EntryList[#v.EntryList+1] = { Type = 'KeyString'; Key = key.Data; Value = value; } else --we are a value local st, value = ParseExpr(scope) if not st then return false, GenerateError("Value expected") end v.EntryList[#v.EntryList+1] = { Type = 'Value'; Value = value; } end elseif tok:ConsumeSymbol('}', tokenList) then break else --value local st, value = ParseExpr(scope) v.EntryList[#v.EntryList+1] = { Type = 'Value'; Value = value; } if not st then return false, GenerateError("Value expected") end end if tok:ConsumeSymbol(';', tokenList) or tok:ConsumeSymbol(',', tokenList) then --all is good elseif tok:ConsumeSymbol('}', tokenList) then break else return false, GenerateError("'}' or table entry expected") end end v.Tokens = tokenList return true, v elseif tok:ConsumeKeyword('function', tokenList) then local st, func = ParseFunctionArgsAndBody(scope, tokenList) if not st then return false, func end -- func.IsLocal = true return true, func elseif tok:ConsumeSymbol('\\', tokenList) then -- short lambdas \params(expr) local funcScope = CreateScope(scope) local argList = {} if not tok:ConsumeSymbol('(', tokenList) then while true do if not tok:Is('Ident') then return false, GenerateError("Identifier expected") end argList[#argList+1] = funcScope:CreateLocal(tok:Get(tokenList).Data) if tok:ConsumeSymbol('(', tokenList) then break end if not tok:ConsumeSymbol(',', tokenList) then return false, GenerateError("'(' expected") end end end local st, body = ParseExpr(funcScope) if not st then return false, body end if not tok:ConsumeSymbol(')', tokenList) then return false, GenerateError("')' expected after lambda body") end local last_tok = body.Tokens[1] local last_char,last_line = last_tok.Char,last_tok.Line local space = { Char=last_char, Line=last_line, Data=' ', Type='Whitespace' } local ret = { AstType='ReturnStatement', Arguments={body}, TrailingWhite=true, Tokens={ { Char=last_char, Line=last_line, Print=function() return '' end, LeadingWhite={space}, Type='Keyword', Data='return' } }} local statList = { AstType='Statlist', Scope=CreateScope(funcScope), Tokens={}, Body={ret} } local tok_first = tokenList[1] tok_first.Type = 'Keyword' tok_first.Data = 'function' tok_first.Print=function() return '' end local ntokl = {} local paren_ix = 2 + math.max(0, #argList*2-1) table.insert(ntokl, tokenList[1]) table.insert(ntokl, tokenList[paren_ix]) for i=2,paren_ix-1 do table.insert(ntokl, tokenList[i]) end table.insert(ntokl, tokenList[#tokenList]) table.insert(ntokl, { Char=last_char, Line=last_line, Type='Keyword', Data='end', Print=function() return '' end, LeadingWhite={space} }) local func = { AstType='Function', Scope=funcScope, Arguments=argList, Body=statList, VarArg=false, Tokens=ntokl, isLocal=true } return true, func else return ParseSuffixedExpr(scope) end end local unops = lookupify{'-', 'not', '#', '~'} local unopprio = 12 local priority = { ['^'] = {14,13}; ['%'] = {11,11}; ['//'] = {11,11}; ['/'] = {11,11}; ['*'] = {11,11}; ['+'] = {10,10}; ['-'] = {10,10}; ['..'] = {9,8}; ['>>'] = {7,7}; ['<<'] = {7,7}; ['&'] = {6,6}; ['~'] = {5,5}; ['|'] = {4,4}; ['=='] = {3,3}; ['<'] = {3,3}; ['<='] = {3,3}; ['~='] = {3,3}; ['>'] = {3,3}; ['>='] = {3,3}; ['and'] = {2,2}; ['or'] = {1,1}; } function ParseSubExpr(scope, level) --base item, possibly with unop prefix local st, exp if unops[tok:Peek().Data] then local tokenList = {} local op = tok:Get(tokenList).Data st, exp = ParseSubExpr(scope, unopprio) if not st then return false, exp end local nodeEx = {} nodeEx.AstType = 'UnopExpr' nodeEx.Rhs = exp nodeEx.Op = op nodeEx.OperatorPrecedence = unopprio nodeEx.Tokens = tokenList exp = nodeEx else st, exp = ParseSimpleExpr(scope) if not st then return false, exp end end --next items in chain while true do local prio = priority[tok:Peek().Data] if prio and prio[1] > level then local tokenList = {} local op = tok:Get(tokenList).Data local st, rhs = ParseSubExpr(scope, prio[2]) if not st then return false, rhs end local nodeEx = {} nodeEx.AstType = 'BinopExpr' nodeEx.Lhs = exp nodeEx.Op = op nodeEx.OperatorPrecedence = prio[1] nodeEx.Rhs = rhs nodeEx.Tokens = tokenList -- exp = nodeEx else break end end return true, exp end ParseExpr = function(scope) return ParseSubExpr(scope, 0) end local pragma_map = { encapsulate_on = function() opcode_arg_encapsulate(true) end, encapsulate_off = function() opcode_arg_encapsulate(false) end, syntax6502k_on = function() syntax6502k(true) end, syntax6502k_off = function() syntax6502k(false) end, } local function ParseStatement(scope) local stat = nil local tokenList = {} local commaTokenList = {} local l,c = function() return tokenList[1].Line end, function() return tokenList[1].Char end local p = function(t,n) return function() return '<' .. t .. string.rep(' ', 7-#t) .. ' ' .. n .. ' >' end end local t = function(t,s,w) return { Type=t, Data=s, Print=p(t,s), Char=c(), Line=l(), LeadingWhite=w or {} } end local no_encapsulation = { Function=true, NumberExpr=true, StringExpr=true } local reg = function(e) local t=e.Tokens[1] if t then return Registers_6502[t.Data] end end local function emit_call(params) local name,args,inverse_encapsulate = params.name, params.args or {}, params.inverse_encapsulate if not params.func_white then params.func_white = tokenList[1].LeadingWhite end local space = { Char=c(), Line=l(), Data=' ', Type='Whitespace' } local op_var = { AstType='VarExpr', Name=name, Variable={ IsGlobal=true, Name=name, Scope=CreateScope(scope) }, Tokens = { t('Ident', name, params.func_white) } } local encapsulate = params.encapsulate if encapsulate == nil then encapsulate = opcode_arg_encapsulate_on end if type(inverse_encapsulate) == 'table' then -- this is a list of arguments, where each can be encapsulated for arg_ix,arg in ipairs(args) do if ( (encapsulate and not inverse_encapsulate[arg_ix]) or (not encapsulate and inverse_encapsulate[arg_ix]) ) and not no_encapsulation[arg.AstType] then local inner_call_scope = CreateScope(op_var.Variable.Scope) local inner_call_body = { AstType='StatList', Scope=CreateScope(inner_call_scope), Tokens={}, Body={ { AstType='ReturnStatement', Arguments={arg}, Tokens={ t('Keyword', 'return', {space}) } } } } local inner_call = { AstType='Function', VarArg=false, IsLocal=true, Scope=inner_call_scope, Body=inner_call_body, Arguments={}, Tokens={ t('Keyword', 'function'), t('Symbol', '('), t('Symbol', ')'), t('Keyword', 'end', {space}) } } if #arg.Tokens[1].LeadingWhite == 0 then table.insert(arg.Tokens[1].LeadingWhite, space) end args[arg_ix] = inner_call end end elseif #args > 0 and ( (encapsulate and not inverse_encapsulate) or (not encapsulate and inverse_encapsulate) ) and not no_encapsulation[args[1].AstType] then -- opcode arguments of type (late, early), where only late is to be encapsulated with _o parameter to be set to early and added to _f(late) local inner_call_scope,inner_call = CreateScope(op_var.Variable.Scope) if params.basic then local inner_call_body = { AstType='StatList', Scope=CreateScope(inner_call_scope), Tokens={}, Body={ { AstType='ReturnStatement', Arguments={args[1]}, Tokens={ t('Keyword', 'return', {space}) } } } } inner_call = { AstType='Function', VarArg=false, IsLocal=true, Scope=inner_call_scope, Body=inner_call_body, Arguments={}, Tokens={ t('Keyword', 'function'), t('Symbol', '('), t('Symbol', ')'), t('Keyword', 'end', {space}) } } else local fvarexpr = { AstType='VarExpr', Name='_f', Variable={ Name='_f', Scope=CreateScope(inner_call_scope) }, Tokens = { t('Ident', '_f') } } local inner_add = { AstType='BinopExpr', Op='+', OperatorPrecedence=10, Tokens={ t('Symbol', '+') }, Lhs = { AstType='VarExpr', Name='_o', Variable={ IsGlobal=false, Name='_o', Scope=inner_call_scope }, Tokens={ t('Ident', '_o', {space}) } }, Rhs = { AstType = 'CallExpr', Base = fvarexpr, Arguments = {args[1]}, Tokens = { t('Symbol', '('), t('Symbol', ')') } } } local inner_call_body = { AstType='StatList', Scope=CreateScope(inner_call_scope), Tokens={}, Body={ { AstType='ReturnStatement', Arguments={inner_add}, Tokens={ t('Keyword', 'return', {space}) } } } } inner_call = { AstType='Function', VarArg=false, IsLocal=true, Scope=inner_call_scope, Body=inner_call_body, Arguments={ { IsGlobal=false, Name='_o', Scope=inner_call_scope }, { IsGlobal=false, Name='_f', Scope=inner_call_scope }, }, Tokens={ t('Keyword', 'function'), t('Symbol', '('), t('Ident', '_o'), t('Symbol', ','), t('Ident', '_f'), t('Symbol', ')'), t('Keyword', 'end', {space}) } } end args[1] = inner_call end local exp_call = { AstType = 'CallExpr', Base = op_var, Arguments = args, Tokens = { t('Symbol', '(', params.paren_open_white), t('Symbol', ')', params.paren_close_white) } } do local j=#commaTokenList for i=2,#args do assert(j > 0) table.insert(exp_call.Tokens, i, commaTokenList[j]) j = j - 1 end end return { AstType = 'CallStatement', Expression = exp_call, Tokens = {} } end local function as_string_expr(expr, s) local ss = '"'..s..'"' local lw = expr.Tokens and #expr.Tokens > 0 and expr.Tokens[1].LeadingWhite or {} local p = function() return '' end local v = { LeadingWhite=lw, Type='String', Data=ss, Constant=s, Char=c(), Line=l(), Print=p } return { AstType = 'StringExpr', Value = v, Tokens = {v} } end -- parser pragmas do ::search_pragma:: for k,v in pairs(pragma_map) do if tok:ConsumeKeyword(k) then v() goto search_pragma end end end -- label declarations if not stat then if tok:ConsumeSymbol('@@', tokenList) then if not tok:Is('Ident') then return false, GenerateError("Identifier expected") end local label_name = tok:Get(tokenList) label_name = as_string_expr(label_name, label_name.Data) stat = emit_call{name = 'section', args = {label_name}, encapsulate=false} elseif tok:ConsumeSymbol('@', tokenList) then if not tok:Is('Ident') then return false, GenerateError("Identifier expected") end local label_name = tok:Get(tokenList) label_name = as_string_expr(label_name, label_name.Data) stat = emit_call{name = 'label', args = {label_name}, encapsulate=false} end end -- new statements if not stat then local pagestat = function(fpage) local st, nodeBlock = ParseStatementList(scope) if not st then return false, nodeBlock end if not tok:ConsumeKeyword('end', tokenList) then return false, GenerateError("'end' expected") end local nodeDoStat = {} nodeDoStat.AstType = 'DoStatement' nodeDoStat.Body = nodeBlock nodeDoStat.Tokens = tokenList stat = nodeDoStat tokenList[1].Data = 'do' local space = {{ Char=c(), Line=l(), Data=' ', Type='Whitespace' }} local opencall,closecall = emit_call{name=fpage,func_white=space},emit_call{name='endpage',func_white=space} table.insert(nodeBlock.Body, 1, opencall) table.insert(nodeBlock.Body, closecall) end if tok:ConsumeKeyword('samepage', tokenList) then pagestat('samepage') elseif tok:ConsumeKeyword('crosspage', tokenList) then pagestat('crosspage') end end -- declare data if not stat then if tok:ConsumeKeyword('dc', tokenList) then if not tok:ConsumeSymbol('.', tokenList) then return false, GenerateError("'.' expected") end local suffix_list = { b='byte', w='word', l='long' } local func = suffix_list[tok:Get(tokenList).Data] if not func then return false, GenerateError("'b', 'w' or 'l' expected") end local inverse_encapsulate={} inverse_encapsulate[1] = tok:ConsumeSymbol('!', tokenList) local st, expr = ParseExpr(scope) if not st then return false, expr end if inverse_encapsulate[1] then local d = expr.Tokens[1].LeadingWhite for _,v in ipairs(tokenList[#tokenList].LeadingWhite) do table.insert(d, v) end end local exprs = { expr } while tok:ConsumeSymbol(',', tokenList) do commaTokenList[#commaTokenList+1] = tokenList[#tokenList] inverse_encapsulate[#exprs+1] = tok:ConsumeSymbol('!', tokenList) local st, expr = ParseExpr(scope) if not st then return false, expr end if inverse_encapsulate[#exprs+1] then local d = expr.Tokens[1].LeadingWhite for _,v in ipairs(tokenList[#tokenList].LeadingWhite) do table.insert(d, v) end end exprs[#exprs+1] = expr end stat = emit_call{name=func, args=exprs, inverse_encapsulate=inverse_encapsulate} end end -- k65 style syntax if not stat and syntax6502k_on then -- *=[a,x,y,s]=? do tok:Save() local exprs = {} while true do local st, expr = ParseExpr(scope) if not st then break end table.insert(exprs, expr) if not tok:ConsumeSymbol('=', tokenList) then break end end if #exprs < 2 then tok:Restore() else local hasreg for e in exprs do hasreg=reg(e) if hasreg then break end end if not hasreg then tok:Restore() else tok:Commit() local skip for i=#exprs-1,1,-1 do if skip then skip = false else local src,dst = exprs[i+1],exprs[i] local src_reg,dst_reg = reg(src),reg(dst) local op,arg if src_reg=='s' then if dst_reg=='x' then op = 'tsximp' end elseif dst_reg=='s' then if src_reg=='x' then op = 'txsimp' end elseif src_reg and not dst_reg then arg = i op = 'st'..src_reg elseif dst_reg and not src_reg then arg = i+1 if dst_reg=='x' and i > 1 and reg(exprs[i-1])=='a' then op='lax' skip=true else op = 'ld'..src_reg end end if not op then return false, GenerateError("no opcode for assignment") end if arg then arg = exprs[arg] end end end end local t,r,rexpr,rexpr_ix rexpr_ix=#exprs rexpr=exprs[rexprs_ix] t=#rexpr.Tokens[1] if t then r=Registers_6502[t.Data] end if not r then rexpr_ix=#exprs-1 rexpr=exprs[rexprs_ix] t=#rexpr.Tokens[1] if t then r=Registers_6502[t.Data] end end if r then end end end if tok:Is('Ident') and tok:Peek(1).Data == '=' then local reg = tok:Peek(2).Data if Registers_6502[reg] then local st, arg = ParseExpr(scope) tok:Get(tokenList) tok:Get(tokenList) arg.Tokens[1].LeadingWhite,tokenList[1].LeadingWhite = tokenList[1].LeadingWhite,arg.Tokens[1].LeadingWhite stat = emit_call{name = 'st'..reg..'zab', args = {arg}} end end end -- 6502 opcodes if not stat then local mod_st, mod_expr, inverse_encapsulate for _,op in pairs(Keywords_6502) do if tok:ConsumeKeyword(op, tokenList) then if opcode_alias[op] then op = opcode_alias[op] end if opcode_encapsulate[op] then inverse_encapsulate = tok:ConsumeSymbol('!', tokenList) local st, expr = ParseExpr(scope) if not st then return false, expr end local paren_open_whites = {} if inverse_encapsulate then for _,v in ipairs(tokenList[#tokenList-1].LeadingWhite) do table.insert(paren_open_whites, v) end end for _,v in ipairs(tokenList[#tokenList].LeadingWhite) do table.insert(paren_open_whites, v) end stat = emit_call{name=opcode_encapsulate[op], args={expr}, inverse_encapsulate=inverse_encapsulate, paren_open_white=paren_open_whites, basic=true} break elseif opcode_relative[op] then local st, expr = ParseExpr(scope) if not st then return false, expr end if expr.AstType == 'VarExpr' and expr.Variable.IsGlobal then expr = as_string_expr(expr, expr.Name) end stat = emit_call{name=op .. "rel", args={expr}, encapsulate=false} break end if opcode_immediate[op] and tok:ConsumeSymbol('#', tokenList) then inverse_encapsulate = tok:ConsumeSymbol('!', tokenList) local st, expr = ParseExpr(scope) if not st then return false, expr end local paren_open_whites = {} if inverse_encapsulate then for _,v in ipairs(tokenList[#tokenList-1].LeadingWhite) do table.insert(paren_open_whites, v) end end for _,v in ipairs(tokenList[#tokenList].LeadingWhite) do table.insert(paren_open_whites, v) end if tok:ConsumeSymbol(',', tokenList) then commaTokenList[1] = tokenList[#tokenList] mod_st, mod_expr = ParseExpr(scope) if not mod_st then return false, mod_expr end end stat = emit_call{name=op .. "imm", args={expr, mod_expr}, inverse_encapsulate=inverse_encapsulate, paren_open_white=paren_open_whites} break end if (opcode_indirect[op] or opcode_indirect_x[op] or opcode_indirect_y[op]) and tok:ConsumeSymbol('(', tokenList) then inverse_encapsulate = tok:ConsumeSymbol('!', tokenList) local st, expr = ParseExpr(scope) if not st then return false, expr end local paren_open_whites,paren_close_whites,mod_st,mod_expr = {},{} if inverse_encapsulate then for _,v in ipairs(tokenList[#tokenList-1].LeadingWhite) do table.insert(paren_open_whites, v) end end for _,v in ipairs(tokenList[#tokenList].LeadingWhite) do table.insert(paren_open_whites, v) end if tok:IsSymbol(',') and tok:Peek(1).Data ~= 'x' then tok:Get(tokenList) commaTokenList[1] = tokenList[#tokenList] mod_st, mod_expr = ParseExpr(scope) if not mod_st then return false, mod_expr end end if tok:ConsumeSymbol(',', tokenList) then if not opcode_indirect_x[op] or not tok:Get(tokenList).Data == 'x' or not tok:ConsumeSymbol(')', tokenList) then return false, expr end for _,v in ipairs(tokenList[#tokenList-1].LeadingWhite) do table.insert(paren_close_whites, v) end for _,v in ipairs(tokenList[#tokenList].LeadingWhite) do table.insert(paren_close_whites, v) end stat = emit_call{name=op .. "inx", args={expr, mod_expr}, inverse_encapsulate=inverse_encapsulate, paren_open_white=paren_open_whites, paren_close_white=paren_close_whites} break elseif not tok:ConsumeSymbol(')', tokenList) then return false, expr else if tok:ConsumeSymbol(',', tokenList) then if not opcode_indirect_y[op] or not tok:Get(tokenList).Data == 'y' then return false, expr end for _,v in ipairs(tokenList[#tokenList-1].LeadingWhite) do table.insert(paren_close_whites, v) end for _,v in ipairs(tokenList[#tokenList].LeadingWhite) do table.insert(paren_close_whites, v) end stat = emit_call{name=op .. "iny", args={expr, mod_expr}, inverse_encapsulate=inverse_encapsulate, paren_open_white=paren_open_whites, paren_close_white=paren_close_whites} break else if not opcode_indirect[op] then return false, expr end stat = emit_call{name=op .. "ind", args={expr, mod_expr}, inverse_encapsulate=inverse_encapsulate, paren_open_white=paren_open_whites, paren_close_white=paren_close_whites} break end end end if opcode_absolute[op] or opcode_absolute_x[op] or opcode_absolute_y[op] or opcode_zeropage[op] or opcode_zeropage_x[op] or opcode_zeropage_y[op] then local suffix = '' tok:Save() if tok:ConsumeSymbol('.', tokenList) then local t = tok:Get(tokenList).Data if t == 'w' then suffix = 'w' elseif t == 'b' then suffix = 'b' else tok:Restore() tok:Save() end end local paren_open_whites = {} if tok:ConsumeSymbol('!', tokenList) then inverse_encapsulate = true for _,v in ipairs(tokenList[#tokenList].LeadingWhite) do table.insert(paren_open_whites, v) end end local st, expr = ParseExpr(scope) if not st then tok:Restore() else tok:Commit() if not tok:ConsumeSymbol(',', tokenList) then if not opcode_absolute[op] and not opcode_zeropage[op] then return false, expr end suffix = suffix=='b' and "zpg" or suffix=='w' and "abs" or "zab" if suffix == 'zab' then if not opcode_zeropage[op] then suffix='abs' elseif not opcode_absolute[op] then suffix='zpg' end end if suffix == 'zpg' and not opcode_zeropage[op] then return false, GenerateError("Opcode " .. op " doesn't support zeropage addressing mode") end if suffix == 'abs' and not opcode_absolute[op] then return false, GenerateError("Opcode " .. op " doesn't support absolute addressing mode") end stat = emit_call{name=op .. suffix, args={expr}, inverse_encapsulate=inverse_encapsulate} break end if tok:Peek().Data == 'x' then if not opcode_absolute_x[op] and not opcode_zeropage_x[op] then return false, expr end tok:Get(tokenList) local paren_close_whites = {} for _,v in ipairs(tokenList[#tokenList-1].LeadingWhite) do table.insert(paren_close_whites, v) end for _,v in ipairs(tokenList[#tokenList].LeadingWhite) do table.insert(paren_close_whites, v) end suffix = suffix=='b' and "zpx" or suffix=='w' and "abx" or "zax" if suffix == 'zax' then if not opcode_zeropage_x[op] then suffix='abx' elseif not opcode_absolute_x[op] then suffix='zpx' end end if suffix == 'zpx' and not opcode_zeropage_x[op] then return false, GenerateError("Opcode " .. op " doesn't support zeropage,x addressing mode") end if suffix == 'abx' and not opcode_absolute_x[op] then return false, GenerateError("Opcode " .. op " doesn't support absolute,x addressing mode") end stat = emit_call{name=op .. suffix, args={expr}, inverse_encapsulate=inverse_encapsulate, paren_open_white=paren_open_whites, paren_close_white=paren_close_whites} break end if tok:Peek().Data == 'y' then if not opcode_absolute_y[op] and not opcode_zeropage_y[op] then return false, expr end tok:Get(tokenList) local paren_close_whites = {} for _,v in ipairs(tokenList[#tokenList-1].LeadingWhite) do table.insert(paren_close_whites, v) end for _,v in ipairs(tokenList[#tokenList].LeadingWhite) do table.insert(paren_close_whites, v) end suffix = suffix=='b' and "zpy" or suffix=='w' and "aby" or "zay" if suffix == 'zay' then if not opcode_zeropage_y[op] then suffix='aby' elseif not opcode_absolute_y[op] then suffix='zpy' end end if suffix == 'zpy' and not opcode_zeropage_y[op] then return false, GenerateError("Opcode " .. op " doesn't support zeropage,y addressing mode") end if suffix == 'aby' and not opcode_absolute_y[op] then return false, GenerateError("Opcode " .. op " doesn't support absolute,y addressing mode") end stat = emit_call{name=op .. suffix, args={expr}, inverse_encapsulate=inverse_encapsulate, paren_open_white=paren_open_whites, paren_close_white=paren_close_whites} break end commaTokenList[1] = tokenList[#tokenList] local mod_st, mod_expr = ParseExpr(scope) if not mod_st then return false, mod_expr end if not tok:ConsumeSymbol(',', tokenList) then if not opcode_absolute[op] and not opcode_zeropage[op] then return false, expr end suffix = suffix=='b' and "zpg" or suffix=='w' and "abs" or "zab" if suffix == 'zab' then if not opcode_zeropage[op] then suffix='abs' elseif not opcode_absolute[op] then suffix='zpg' end end if suffix == 'zpg' and not opcode_zeropage[op] then return false, GenerateError("Opcode " .. op " doesn't support zeropage addressing mode") end if suffix == 'abs' and not opcode_absolute[op] then return false, GenerateError("Opcode " .. op " doesn't support absolute addressing mode") end stat = emit_call{name=op .. suffix, args={expr, mod_expr}, inverse_encapsulate=inverse_encapsulate} break end if tok:Peek().Data == 'x' then if not opcode_absolute_x[op] and not opcode_zeropage_x[op] then return false, expr end tok:Get(tokenList) local paren_close_whites = {} for _,v in ipairs(tokenList[#tokenList-1].LeadingWhite) do table.insert(paren_close_whites, v) end for _,v in ipairs(tokenList[#tokenList].LeadingWhite) do table.insert(paren_close_whites, v) end suffix = suffix=='b' and "zpx" or suffix=='w' and "abx" or "zax" if suffix == 'zax' then if not opcode_zeropage_x[op] then suffix='abx' elseif not opcode_absolute_x[op] then suffix='zpx' end end if suffix == 'zpx' and not opcode_zeropage_x[op] then return false, GenerateError("Opcode " .. op " doesn't support zeropage,x addressing mode") end if suffix == 'abx' and not opcode_absolute_x[op] then return false, GenerateError("Opcode " .. op " doesn't support absolute,x addressing mode") end stat = emit_call{name=op .. suffix, args={expr, mod_expr}, inverse_encapsulate=inverse_encapsulate, paren_open_white=paren_open_whites, paren_close_white=paren_close_whites} break end if tok:Peek().Data == 'y' then if not opcode_absolute_y[op] and not opcode_zeropage_y[op] then return false, expr end tok:Get(tokenList) local paren_close_whites = {} for _,v in ipairs(tokenList[#tokenList-1].LeadingWhite) do table.insert(paren_close_whites, v) end for _,v in ipairs(tokenList[#tokenList].LeadingWhite) do table.insert(paren_close_whites, v) end suffix = suffix=='b' and "zpy" or suffix=='w' and "aby" or "zay" if suffix == 'zay' then if not opcode_zeropage_y[op] then suffix='aby' elseif not opcode_absolute_y[op] then suffix='zpy' end end if suffix == 'zpy' and not opcode_zeropage_y[op] then return false, GenerateError("Opcode " .. op " doesn't support zeropage,y addressing mode") end if suffix == 'aby' and not opcode_absolute_y[op] then return false, GenerateError("Opcode " .. op " doesn't support absolute,y addressing mode") end stat = emit_call{name=op .. suffix, args={expr, mod_expr}, inverse_encapsulate=inverse_encapsulate, paren_open_white=paren_open_whites, paren_close_white=paren_close_whites} break end return false, expr end end if opcode_implied[op] then stat = emit_call{name=op .. "imp"} break end error("internal error: unable to find addressing of valid opcode " .. op) -- should not happen end end end if stat then -- nothing elseif tok:ConsumeKeyword('if', tokenList) then --setup local nodeIfStat = {} nodeIfStat.AstType = 'IfStatement' nodeIfStat.Clauses = {} --clauses repeat local st, nodeCond = ParseExpr(scope) if not st then return false, nodeCond end if not tok:ConsumeKeyword('then', tokenList) then return false, GenerateError("'then' expected") end local st, nodeBody = ParseStatementList(scope) if not st then return false, nodeBody end nodeIfStat.Clauses[#nodeIfStat.Clauses+1] = { Condition = nodeCond; Body = nodeBody; } until not tok:ConsumeKeyword('elseif', tokenList) --else clause if tok:ConsumeKeyword('else', tokenList) then local st, nodeBody = ParseStatementList(scope) if not st then return false, nodeBody end nodeIfStat.Clauses[#nodeIfStat.Clauses+1] = { Body = nodeBody; } end --end if not tok:ConsumeKeyword('end', tokenList) then return false, GenerateError("'end' expected") end nodeIfStat.Tokens = tokenList stat = nodeIfStat elseif tok:ConsumeKeyword('while', tokenList) then --setup local nodeWhileStat = {} nodeWhileStat.AstType = 'WhileStatement' --condition local st, nodeCond = ParseExpr(scope) if not st then return false, nodeCond end --do if not tok:ConsumeKeyword('do', tokenList) then return false, GenerateError("'do' expected") end --body local st, nodeBody = ParseStatementList(scope) if not st then return false, nodeBody end --end if not tok:ConsumeKeyword('end', tokenList) then return false, GenerateError("'end' expected") end --return nodeWhileStat.Condition = nodeCond nodeWhileStat.Body = nodeBody nodeWhileStat.Tokens = tokenList stat = nodeWhileStat elseif tok:ConsumeKeyword('do', tokenList) then --do block local st, nodeBlock = ParseStatementList(scope) if not st then return false, nodeBlock end if not tok:ConsumeKeyword('end', tokenList) then return false, GenerateError("'end' expected") end local nodeDoStat = {} nodeDoStat.AstType = 'DoStatement' nodeDoStat.Body = nodeBlock nodeDoStat.Tokens = tokenList stat = nodeDoStat elseif tok:ConsumeKeyword('for', tokenList) then --for block if not tok:Is('Ident') then return false, GenerateError("Identifier expected") end local baseVarName = tok:Get(tokenList) if tok:ConsumeSymbol('=', tokenList) then --numeric for local forScope = CreateScope(scope) local forVar = forScope:CreateLocal(baseVarName.Data) -- local st, startEx = ParseExpr(scope) if not st then return false, startEx end if not tok:ConsumeSymbol(',', tokenList) then return false, GenerateError("',' expected") end local st, endEx = ParseExpr(scope) if not st then return false, endEx end local st, stepEx; if tok:ConsumeSymbol(',', tokenList) then st, stepEx = ParseExpr(scope) if not st then return false, stepEx end end if not tok:ConsumeKeyword('do', tokenList) then return false, GenerateError("'do' expected") end -- local st, body = ParseStatementList(forScope) if not st then return false, body end if not tok:ConsumeKeyword('end', tokenList) then return false, GenerateError("'end' expected") end -- local nodeFor = {} nodeFor.AstType = 'NumericForStatement' nodeFor.Scope = forScope nodeFor.Variable = forVar nodeFor.Start = startEx nodeFor.End = endEx nodeFor.Step = stepEx nodeFor.Body = body nodeFor.Tokens = tokenList stat = nodeFor else --generic for local forScope = CreateScope(scope) -- local varList = { forScope:CreateLocal(baseVarName.Data) } while tok:ConsumeSymbol(',', tokenList) do if not tok:Is('Ident') then return false, GenerateError("for variable expected.") end varList[#varList+1] = forScope:CreateLocal(tok:Get(tokenList).Data) end if not tok:ConsumeKeyword('in', tokenList) then return false, GenerateError("'in' expected") end local generators = {} local st, firstGenerator = ParseExpr(scope) if not st then return false, firstGenerator end generators[#generators+1] = firstGenerator while tok:ConsumeSymbol(',', tokenList) do local st, gen = ParseExpr(scope) if not st then return false, gen end generators[#generators+1] = gen end if not tok:ConsumeKeyword('do', tokenList) then return false, GenerateError("'do' expected") end local st, body = ParseStatementList(forScope) if not st then return false, body end if not tok:ConsumeKeyword('end', tokenList) then return false, GenerateError("'end' expected") end -- local nodeFor = {} nodeFor.AstType = 'GenericForStatement' nodeFor.Scope = forScope nodeFor.VariableList = varList nodeFor.Generators = generators nodeFor.Body = body nodeFor.Tokens = tokenList stat = nodeFor end elseif tok:ConsumeKeyword('repeat', tokenList) then local st, body = ParseStatementList(scope) if not st then return false, body end -- if not tok:ConsumeKeyword('until', tokenList) then return false, GenerateError("'until' expected") end -- FIX: Used to parse in parent scope -- Now parses in repeat scope local st, cond = ParseExpr(body.Scope) if not st then return false, cond end -- local nodeRepeat = {} nodeRepeat.AstType = 'RepeatStatement' nodeRepeat.Condition = cond nodeRepeat.Body = body nodeRepeat.Tokens = tokenList stat = nodeRepeat elseif tok:ConsumeKeyword('function', tokenList) then if not tok:Is('Ident') then return false, GenerateError("function name expected") end local st, name = ParseSuffixedExpr(scope, true) --true => only dots and colons if not st then return false, name end -- local st, func = ParseFunctionArgsAndBody(scope, tokenList) if not st then return false, func end -- func.IsLocal = false func.Name = name stat = func elseif tok:ConsumeKeyword('local', tokenList) then if tok:Is('Ident') then local varList = { tok:Get(tokenList).Data } while tok:ConsumeSymbol(',', tokenList) do if not tok:Is('Ident') then return false, GenerateError("local var name expected") end varList[#varList+1] = tok:Get(tokenList).Data end local initList = {} if tok:ConsumeSymbol('=', tokenList) then repeat local st, ex = ParseExpr(scope) if not st then return false, ex end initList[#initList+1] = ex until not tok:ConsumeSymbol(',', tokenList) end --now patch var list --we can't do this before getting the init list, because the init list does not --have the locals themselves in scope. for i, v in pairs(varList) do varList[i] = scope:CreateLocal(v) end local nodeLocal = {} nodeLocal.AstType = 'LocalStatement' nodeLocal.LocalList = varList nodeLocal.InitList = initList nodeLocal.Tokens = tokenList -- stat = nodeLocal elseif tok:ConsumeKeyword('function', tokenList) then if not tok:Is('Ident') then return false, GenerateError("function name expected") end local name = tok:Get(tokenList).Data local localVar = scope:CreateLocal(name) -- local st, func = ParseFunctionArgsAndBody(scope, tokenList) if not st then return false, func end -- func.Name = localVar func.IsLocal = true stat = func else return false, GenerateError("local var or function def expected") end elseif tok:ConsumeSymbol('::', tokenList) then if not tok:Is('Ident') then return false, GenerateError('Label name expected') end local label = tok:Get(tokenList).Data if not tok:ConsumeSymbol('::', tokenList) then return false, GenerateError("'::' expected") end local nodeLabel = {} nodeLabel.AstType = 'LabelStatement' nodeLabel.Label = label nodeLabel.Tokens = tokenList stat = nodeLabel elseif tok:ConsumeKeyword('return', tokenList) then local exList = {} if not tok:IsKeyword('end') then local st, firstEx = ParseExpr(scope) if st then exList[1] = firstEx while tok:ConsumeSymbol(',', tokenList) do local st, ex = ParseExpr(scope) if not st then return false, ex end exList[#exList+1] = ex end end end local nodeReturn = {} nodeReturn.AstType = 'ReturnStatement' nodeReturn.Arguments = exList nodeReturn.Tokens = tokenList stat = nodeReturn elseif tok:ConsumeKeyword('break', tokenList) then local nodeBreak = {} nodeBreak.AstType = 'BreakStatement' nodeBreak.Tokens = tokenList stat = nodeBreak elseif tok:ConsumeKeyword('goto', tokenList) then if not tok:Is('Ident') then return false, GenerateError("Label expected") end local label = tok:Get(tokenList).Data local nodeGoto = {} nodeGoto.AstType = 'GotoStatement' nodeGoto.Label = label nodeGoto.Tokens = tokenList stat = nodeGoto elseif tok:IsSymbol(';') then stat = { AstType='SemicolonStatement', Tokens={} } else --statementParseExpr local st, suffixed = ParseSuffixedExpr(scope) if not st then return false, suffixed end --assignment or call? if tok:IsSymbol(',') or tok:IsSymbol('=') then --check that it was not parenthesized, making it not an lvalue if (suffixed.ParenCount or 0) > 0 then return false, GenerateError("Can not assign to parenthesized expression, is not an lvalue") end --more processing needed local lhs = { suffixed } while tok:ConsumeSymbol(',', tokenList) do local st, lhsPart = ParseSuffixedExpr(scope) if not st then return false, lhsPart end lhs[#lhs+1] = lhsPart end --equals if not tok:ConsumeSymbol('=', tokenList) then return false, GenerateError("'=' expected") end --rhs local rhs = {} local st, firstRhs = ParseExpr(scope) if not st then return false, firstRhs end rhs[1] = firstRhs while tok:ConsumeSymbol(',', tokenList) do local st, rhsPart = ParseExpr(scope) if not st then return false, rhsPart end rhs[#rhs+1] = rhsPart end --done local nodeAssign = {} nodeAssign.AstType = 'AssignmentStatement' nodeAssign.Lhs = lhs nodeAssign.Rhs = rhs nodeAssign.Tokens = tokenList stat = nodeAssign elseif suffixed.AstType == 'CallExpr' or suffixed.AstType == 'TableCallExpr' or suffixed.AstType == 'StringCallExpr' then --it's a call statement local nodeCall = {} nodeCall.AstType = 'CallStatement' nodeCall.Expression = suffixed nodeCall.Tokens = tokenList stat = nodeCall else return false, GenerateError("Assignment statement expected") end end if tok:IsSymbol(';') then stat.Semicolon = tok:Get( stat.Tokens ) end return true, stat end local statListCloseKeywords = lookupify{'end', 'else', 'elseif', 'until'} ParseStatementList = function(scope) local nodeStatlist = {} nodeStatlist.Scope = CreateScope(scope) nodeStatlist.AstType = 'Statlist' nodeStatlist.Body = { } nodeStatlist.Tokens = { } -- --local stats = {} -- while not statListCloseKeywords[tok:Peek().Data] and not tok:IsEof() do local st, nodeStatement = ParseStatement(nodeStatlist.Scope) if not st then return false, nodeStatement end --stats[#stats+1] = nodeStatement nodeStatlist.Body[#nodeStatlist.Body + 1] = nodeStatement end if tok:IsEof() then local nodeEof = {} nodeEof.AstType = 'Eof' nodeEof.Tokens = { tok:Get() } nodeStatlist.Body[#nodeStatlist.Body + 1] = nodeEof end -- --nodeStatlist.Body = stats return true, nodeStatlist end local function mainfunc() local topScope = CreateScope() return ParseStatementList(topScope) end local st, main = mainfunc() return st, main end local function Format65(ast) local function splitLines(str) if str:match("\n") then local lines = {} for line in str:gmatch("[^\n]*") do table.insert(lines, line) end assert(#lines > 0) return lines else return { str } end end local formatStatlist, formatExpr local out = { rope = {}, -- List of strings line = 1, char = 1, appendStr = function(self, str) table.insert(self.rope, str) local lines = splitLines(str) if #lines == 1 then self.char = self.char + #str else self.line = self.line + #lines - 1 local lastLine = lines[#lines] self.char = #lastLine end end, appendToken = function(self, token) self:appendWhite(token) self:appendStr(token.Data) end, appendTokens = function(self, tokens) for _,token in ipairs(tokens) do self:appendToken( token ) end end, appendWhite = function(self, token) if token.LeadingWhite then self:appendTokens( token.LeadingWhite ) end end } formatExpr = function(expr) local tok_it = 1 local function appendNextToken(str) local tok = expr.Tokens[tok_it]; if str and tok.Data ~= str then error("Expected token '" .. str .. "'.") end out:appendToken( tok ) tok_it = tok_it + 1 end local function appendToken(token) out:appendToken( token ) tok_it = tok_it + 1 end local function appendWhite() local tok = expr.Tokens[tok_it]; if not tok then error("Missing token") end out:appendWhite( tok ) tok_it = tok_it + 1 end local function appendStr(str) appendWhite() out:appendStr(str) end local function peek() if tok_it < #expr.Tokens then return expr.Tokens[tok_it].Data end end local function appendComma(mandatory, seperators) seperators = seperators or { "," } seperators = lookupify( seperators ) if not mandatory and not seperators[peek()] then return end assert(seperators[peek()], "Missing comma or semicolon") appendNextToken() end if expr.AstType == 'VarExpr' then if expr.Variable then appendStr( expr.Variable.Name ) else appendStr( expr.Name ) end elseif expr.AstType == 'NumberExpr' then appendToken( expr.Value ) elseif expr.AstType == 'StringExpr' then appendToken( expr.Value ) elseif expr.AstType == 'BooleanExpr' then appendNextToken( expr.Value and "true" or "false" ) elseif expr.AstType == 'NilExpr' then appendNextToken( "nil" ) elseif expr.AstType == 'BinopExpr' then formatExpr(expr.Lhs) appendStr( expr.Op ) formatExpr(expr.Rhs) elseif expr.AstType == 'UnopExpr' then appendStr( expr.Op ) formatExpr(expr.Rhs) elseif expr.AstType == 'DotsExpr' then appendNextToken( "..." ) elseif expr.AstType == 'CallExpr' then formatExpr(expr.Base) appendNextToken( "(" ) for i,arg in ipairs( expr.Arguments ) do formatExpr(arg) appendComma( i ~= #expr.Arguments ) end appendNextToken( ")" ) elseif expr.AstType == 'TableCallExpr' then formatExpr( expr.Base ) formatExpr( expr.Arguments[1] ) elseif expr.AstType == 'StringCallExpr' then formatExpr(expr.Base) appendToken( expr.Arguments[1] ) elseif expr.AstType == 'IndexExpr' then formatExpr(expr.Base) appendNextToken( "[" ) formatExpr(expr.Index) appendNextToken( "]" ) elseif expr.AstType == 'MemberExpr' then formatExpr(expr.Base) appendNextToken() -- . or : appendToken(expr.Ident) elseif expr.AstType == 'Function' then -- anonymous function appendNextToken( "function" ) appendNextToken( "(" ) if #expr.Arguments > 0 then for i = 1, #expr.Arguments do appendStr( expr.Arguments[i].Name ) if i ~= #expr.Arguments then appendNextToken(",") elseif expr.VarArg then appendNextToken(",") appendNextToken("...") end end elseif expr.VarArg then appendNextToken("...") end appendNextToken(")") formatStatlist(expr.Body) appendNextToken("end") elseif expr.AstType == 'ConstructorExpr' then appendNextToken( "{" ) for i = 1, #expr.EntryList do local entry = expr.EntryList[i] if entry.Type == 'Key' then appendNextToken( "[" ) formatExpr(entry.Key) appendNextToken( "]" ) appendNextToken( "=" ) formatExpr(entry.Value) elseif entry.Type == 'Value' then formatExpr(entry.Value) elseif entry.Type == 'KeyString' then appendStr(entry.Key) appendNextToken( "=" ) formatExpr(entry.Value) end appendComma( i ~= #expr.EntryList, { ",", ";" } ) end appendNextToken( "}" ) elseif expr.AstType == 'Parentheses' then appendNextToken( "(" ) formatExpr(expr.Inner) appendNextToken( ")" ) else error(string.format("Unknown AST Type: %s\n", tostring(statement.AstType))) end assert(tok_it == #expr.Tokens + 1) end local formatStatement = function(statement) local tok_it = 1 local function appendNextToken(str) local tok = statement.Tokens[tok_it]; assert(tok, string.format("Not enough tokens for %q. First token at %i:%i", str, statement.Tokens[1].Line, statement.Tokens[1].Char)) assert(tok.Data == str, string.format('Expected token %q, got %q', str, tok.Data)) out:appendToken( tok ) tok_it = tok_it + 1 end local function appendToken(token) out:appendToken( str ) tok_it = tok_it + 1 end local function appendWhite() local tok = statement.Tokens[tok_it]; out:appendWhite( tok ) tok_it = tok_it + 1 end local function appendStr(str) appendWhite() out:appendStr(str) end local function appendComma(mandatory) if mandatory or (tok_it < #statement.Tokens and statement.Tokens[tok_it].Data == ",") then appendNextToken( "," ) end end if statement.AstType == 'AssignmentStatement' then for i,v in ipairs(statement.Lhs) do formatExpr(v) appendComma( i ~= #statement.Lhs ) end if #statement.Rhs > 0 then appendNextToken( "=" ) for i,v in ipairs(statement.Rhs) do formatExpr(v) appendComma( i ~= #statement.Rhs ) end end elseif statement.AstType == 'CallStatement' then formatExpr(statement.Expression) elseif statement.AstType == 'LocalStatement' then appendNextToken( "local" ) for i = 1, #statement.LocalList do appendStr( statement.LocalList[i].Name ) appendComma( i ~= #statement.LocalList ) end if #statement.InitList > 0 then appendNextToken( "=" ) for i = 1, #statement.InitList do formatExpr(statement.InitList[i]) appendComma( i ~= #statement.InitList ) end end elseif statement.AstType == 'IfStatement' then appendNextToken( "if" ) formatExpr( statement.Clauses[1].Condition ) appendNextToken( "then" ) formatStatlist( statement.Clauses[1].Body ) for i = 2, #statement.Clauses do local st = statement.Clauses[i] if st.Condition then appendNextToken( "elseif" ) formatExpr(st.Condition) appendNextToken( "then" ) else appendNextToken( "else" ) end formatStatlist(st.Body) end appendNextToken( "end" ) elseif statement.AstType == 'WhileStatement' then appendNextToken( "while" ) formatExpr(statement.Condition) appendNextToken( "do" ) formatStatlist(statement.Body) appendNextToken( "end" ) elseif statement.AstType == 'DoStatement' then appendNextToken( "do" ) formatStatlist(statement.Body) appendNextToken( "end" ) elseif statement.AstType == 'ReturnStatement' then appendNextToken( "return" ) if statement.TrailingWhite then out:appendStr(' ') end for i = 1, #statement.Arguments do formatExpr(statement.Arguments[i]) appendComma( i ~= #statement.Arguments ) end elseif statement.AstType == 'BreakStatement' then appendNextToken( "break" ) elseif statement.AstType == 'RepeatStatement' then appendNextToken( "repeat" ) formatStatlist(statement.Body) appendNextToken( "until" ) formatExpr(statement.Condition) elseif statement.AstType == 'Function' then if statement.IsLocal then appendNextToken( "local" ) end appendNextToken( "function" ) if statement.IsLocal then appendStr(statement.Name.Name) else formatExpr(statement.Name) end appendNextToken( "(" ) if #statement.Arguments > 0 then for i = 1, #statement.Arguments do appendStr( statement.Arguments[i].Name ) appendComma( i ~= #statement.Arguments or statement.VarArg ) if i == #statement.Arguments and statement.VarArg then appendNextToken( "..." ) end end elseif statement.VarArg then appendNextToken( "..." ) end appendNextToken( ")" ) formatStatlist(statement.Body) appendNextToken( "end" ) elseif statement.AstType == 'GenericForStatement' then appendNextToken( "for" ) for i = 1, #statement.VariableList do appendStr( statement.VariableList[i].Name ) appendComma( i ~= #statement.VariableList ) end appendNextToken( "in" ) for i = 1, #statement.Generators do formatExpr(statement.Generators[i]) appendComma( i ~= #statement.Generators ) end appendNextToken( "do" ) formatStatlist(statement.Body) appendNextToken( "end" ) elseif statement.AstType == 'NumericForStatement' then appendNextToken( "for" ) appendStr( statement.Variable.Name ) appendNextToken( "=" ) formatExpr(statement.Start) appendNextToken( "," ) formatExpr(statement.End) if statement.Step then appendNextToken( "," ) formatExpr(statement.Step) end appendNextToken( "do" ) formatStatlist(statement.Body) appendNextToken( "end" ) elseif statement.AstType == 'LabelStatement' then appendNextToken( "::" ) appendStr( statement.Label ) appendNextToken( "::" ) elseif statement.AstType == 'GotoStatement' then appendNextToken( "goto" ) appendStr( statement.Label ) elseif statement.AstType == 'Eof' then appendWhite() elseif statement.AstType == 'SemicolonStatement' then else error(string.format("Unknown AST Type: %s\n", tostring(statement.AstType))) end if statement.Semicolon then appendNextToken(";") end assert(tok_it == #statement.Tokens + 1) end formatStatlist = function(statList) for _, stat in ipairs(statList.Body) do formatStatement(stat) end end formatStatlist(ast) return table.concat(out.rope) end local dirsep = package.config:sub(1,1) local dirl65 = (string.match(arg[0], "(.*[\\/]).*") or ''):gsub('/',dirsep) local searchl65 = '' if #dirl65 > 0 then searchl65 = string.format(";%s?;%s?.l65", dirl65, dirl65) package.path = package.path .. string.format(";%s?.lua", dirl65) end l65_def = { parse = ParseLua, format = Format65, searcher_index = 2, search_path = string.format(".%s?;.%s?.l65%s", dirsep, dirsep, searchl65), load_org = load, loadfile_org = loadfile, dofile_org = dofile, } if not l65 then l65 = l65_def else for k,v in pairs(l65_def) do l65[k]=v end end l65.report = function(success, ...) if success then return success,... end local message=... io.stderr:write(tostring(message)..'\n') os.exit(-1) end l65.msghandler = function(msg) local o = function(s) io.stderr:write(s) end msg = tostring(msg) msg = msg:gsub('%[string "(.-%.l65)"%]', '%1') -- [string "xxx.l65"] -> xxx.l65 local trace_cur = debug.traceback(nil, 2) trace_cur = trace_cur:gsub('%[string "(.-%.l65)"%]', '%1') -- [string "xxx.l65"] -> xxx.l65 trace_cur = trace_cur:gsub('stack traceback:', '') local i=2 while debug.getinfo(i) do local j = 1 while true do local n,v = debug.getlocal(i, j) if not n then break end if n == 'l65dbg' then o(string.format("%s\n", msg)) if trace_cur:find("in local 'late'") then local lines = {} for line in trace_cur:gmatch("[^\n]+") do if line:find("in local 'late'") then break end table.insert(lines, line) end o(table.concat(lines,'\n')) end local trace = v.trace:match(".-\n(.*)\n.-'xpcall'") trace = trace:gsub('%[string "(.-%.l65)"%]', '%1') trace = trace:gsub('stack traceback:', '') o(trace .. '\n') os.exit(-2) end j = j + 1 end i = i + 1 end trace_cur = trace_cur:match(".-\n(.*)\n.-'xpcall'") o(string.format("%s\n%s\n", msg, trace_cur)) os.exit(-3) end do local getembedded = type(arg[-1]) == 'function' and arg[-1] l65.load_embedded = function(name) if not getembedded then return end local src,isl65 = getembedded(name) if not src then return end if isl65 then name = name .. '.l65' local st, ast = l65.report(l65.parse(src, name)) src = l65.format(ast) else name = name .. '.lua' end local bc = assert(l65.load_org(src, name)) return bc, name end end l65.searcher = function(name) local filename,err = package.searchpath(name, l65.search_path, '.', '.') if not filename then return err end local file = assert(io.open(filename, 'rb')) local src = file:read('*a') file:close() local st, ast = l65.report(l65.parse(src, filename)) local bc = assert(l65.load_org(l65.format(ast), filename)) return bc, filename end l65.load = function(chunk, chunkname, mode, ...) local chunk_t,s = type(chunk) if chunk_t == 'string' then s = chunk elseif chunk_t == 'function' then s={} for x in chunk do if #x==0 then break end s[#s+1]=x end s = table.concat(s) else return nil, string.format("invalid type for chunk %s: %s", chunkname or "=(load)", chunk_t) end if s:sub(1,4) == "\x1bLua" then -- a binary file return l65.load_org(s, chunkname, mode, ...) end local st, ast = l65.report(l65.parse(s, chunkname or "=(load)")) return l65.load_org(l65.format(ast), chunkname, 't', ...) end l65.loadfile = function(filename, mode, ...) local s if not filename then s = io.read('*a') else local file = io.open(filename, 'rb') if not file then return nil,"failed to open " .. filename .. " for reading" end s = file:read('*a') file:close() end return l65.load(s, filename, mode, ...) end l65.dofile = function(filename) local f = l65.report(l65.loadfile(filename)) return f() end l65.installhooks = function() if package.searchers[l65.searcher_index] ~= l65.searcher then table.insert(package.searchers, l65.searcher_index, l65.searcher) end if not l65.hooks_installed then l65.hooks_installed = true load = l65.load loadfile = l65.loadfile dofile = l65.dofile end end l65.uninstallhooks = function() for k,v in ipairs(package.searchers) do if v == l65.searcher then table.remove(package.searchers, k) break end end if l65.hooks_installed then l65.hooks_installed = nil load = l65.load_org loadfile = l65.loadfile_org dofile = l65.dofile_org end end table.insert(package.searchers, l65.searcher_index, l65.load_embedded) l65.installhooks() function getopt(optstring, ...) local opts = { } local args = { ... } for optc, optv in optstring:gmatch"(%a)(:?)" do opts[optc] = { hasarg = optv == ":" } end return coroutine.wrap(function() local yield = coroutine.yield local i = 1 while i <= #args do local arg,argi = args[i], i i = i + 1 if arg == "--" then break elseif arg:sub(1, 1) == "-" then for j = 2, #arg do local opt = arg:sub(j, j) if opts[opt] then if opts[opt].hasarg then if j == #arg then if args[i] then yield(opt, args[i], argi) i=i+1 elseif optstring:sub(1, 1) == ":" then yield(':', opt, argi) else yield('?', opt, argi) end else yield(opt, arg:sub(j + 1), argi) end break else yield(opt, false, argi) end else yield('?', opt, argi) end end else yield(false, arg, argi) end end for i = i, #args do yield(false, args[i], i) end end) end local cfg=require"l65cfg" l65.cfg=cfg local version = function() print(string.format("l65 %s", cfg.version)) end local usage = function(f) if not f then f = io.stdout end f:write(string.format([[ Usage: %s [options] file [args] Options: -d Dump the Lua code after l65 parsing into file -h Display this information -v Display the release version ]], arg[0])) end local invalid_usage = function() io.stderr:write("Invalid usage.\n") usage(io.stderr) end local inf,dump,optix for opt,arg,i in getopt("d:hv", ...) do if opt == '?' then return invalid_usage() end if opt == 'h' then return usage() end if opt == 'v' then return version() end if opt == 'd' then dump = arg end if opt == false then inf=arg optix=i+1 break end end if not inf then return invalid_usage() end if dump then l65.format = function(ast) local s=Format65(ast) l65.format = Format65 local f = assert(io.open(dump, 'wb')) f:write(s) f:close() return s end end local fn='' for i=#inf,1,-1 do local c=inf:sub(i,i) if c==dirsep or c=='/' then break end fn=c..fn if c=='.' then fn='' end end filename=fn local f = l65.report(l65.loadfile(inf)) return xpcall(f, l65.msghandler, select(optix, ...))