From 25fb9d0f5eb37fb4db81239342fea4655ad72c6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=BCseyin=20Er?= Date: Sat, 24 Dec 2022 21:34:15 +0300 Subject: [PATCH 01/25] step0 --- Makefile.impls | 3 ++- impls/lua.2/readline.lua | 23 +++++++++++++++++++++++ impls/lua.2/run | 2 ++ impls/lua.2/step0_repl.lua | 23 +++++++++++++++++++++++ 4 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 impls/lua.2/readline.lua create mode 100755 impls/lua.2/run create mode 100644 impls/lua.2/step0_repl.lua diff --git a/Makefile.impls b/Makefile.impls index 6ac35b23e5..4bb0e11894 100644 --- a/Makefile.impls +++ b/Makefile.impls @@ -36,7 +36,7 @@ wasm_MODE = wasmtime IMPLS = ada ada.2 awk bash basic bbc-basic c c.2 chuck clojure coffee common-lisp cpp crystal cs d dart \ elisp elixir elm erlang es6 factor fantom fennel forth fsharp go groovy gnu-smalltalk \ - guile haskell haxe hy io janet java java-truffle js jq julia kotlin livescript logo lua make mal \ + guile haskell haxe hy io janet java java-truffle js jq julia kotlin livescript logo lua lua.2 make mal \ matlab miniMAL nasm nim objc objpascal ocaml perl perl6 php picolisp pike plpgsql \ plsql powershell prolog ps purs python python.2 r racket rexx rpython ruby ruby.2 rust scala scheme skew sml \ swift swift3 swift4 swift5 tcl ts vala vb vhdl vimscript wasm wren yorick xslt zig @@ -149,6 +149,7 @@ kotlin_STEP_TO_PROG = impls/kotlin/$($(1)).jar livescript_STEP_TO_PROG = impls/livescript/$($(1)).js logo_STEP_TO_PROG = impls/logo/$($(1)).lg lua_STEP_TO_PROG = impls/lua/$($(1)).lua +lua.2_STEP_TO_PROG = impls/lua.2/$($(1)).lua make_STEP_TO_PROG = impls/make/$($(1)).mk mal_STEP_TO_PROG = impls/mal/$($(1)).mal matlab_STEP_TO_PROG = impls/matlab/$($(1)).m diff --git a/impls/lua.2/readline.lua b/impls/lua.2/readline.lua new file mode 100644 index 0000000000..6a861b4d82 --- /dev/null +++ b/impls/lua.2/readline.lua @@ -0,0 +1,23 @@ +local history = {} +assert(nil, "Not implemented module") +function readline(prompt) + io.write(prompt) + io.stdin:setvbuf("no") + char = io.read("n", 1) + line = char + while char do + print('char is ' .. char) + line = line .. char + if char == 'x' then + line = line[#line-1] + end + if char == 'k' then + line = history[#history] + end + char = io.read(1) + end + if line then + table.insert(history, line) + end + return line +end diff --git a/impls/lua.2/run b/impls/lua.2/run new file mode 100755 index 0000000000..f73e5b6f8f --- /dev/null +++ b/impls/lua.2/run @@ -0,0 +1,2 @@ +#!/bin/bash +exec lua $(dirname $0)/${STEP:-stepA_mal}.lua "${@}" diff --git a/impls/lua.2/step0_repl.lua b/impls/lua.2/step0_repl.lua new file mode 100644 index 0000000000..fa257fbd95 --- /dev/null +++ b/impls/lua.2/step0_repl.lua @@ -0,0 +1,23 @@ +function READ(prompt) + io.write(prompt) + return io.read() +end +function EVAL(a) + return a +end +function PRINT(a) + print(a) +end + + +function main() + while true do + line = READ('user> ') + if not line then + break + end + PRINT(EVAL(line)) + end +end + +main() From 5eb29b446f046241130522d5bc1c3447e69cad07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=BCseyin=20Er?= Date: Tue, 27 Dec 2022 22:53:52 +0300 Subject: [PATCH 02/25] scanner working --- impls/lua.2/reader.lua | 20 ++++++ impls/lua.2/scanner.lua | 138 +++++++++++++++++++++++++++++++++++++ impls/lua.2/step1_repl.lua | 26 +++++++ impls/lua.2/token.lua | 24 +++++++ 4 files changed, 208 insertions(+) create mode 100644 impls/lua.2/reader.lua create mode 100644 impls/lua.2/scanner.lua create mode 100644 impls/lua.2/step1_repl.lua create mode 100644 impls/lua.2/token.lua diff --git a/impls/lua.2/reader.lua b/impls/lua.2/reader.lua new file mode 100644 index 0000000000..6c73769543 --- /dev/null +++ b/impls/lua.2/reader.lua @@ -0,0 +1,20 @@ +local reader = {} +Scanner = require "scanner" + + + + +function reader.print_tokens(tokens) + for i,v in ipairs(tokens) do print(i,v:tostring()) end +end + +function reader.read_str(a) + s = Scanner(a) + print(string.format("Scanner source: %s", s.source)) + toks = s:scanTokens() + return toks +end + + + +return reader diff --git a/impls/lua.2/scanner.lua b/impls/lua.2/scanner.lua new file mode 100644 index 0000000000..a163d068b7 --- /dev/null +++ b/impls/lua.2/scanner.lua @@ -0,0 +1,138 @@ +local Token = require('token') +local Scanner = {} +Scanner.__index = Scanner + +setmetatable(Scanner, { + __call = function (cls, ...) + return cls.new(...) +end, +}) + +function Scanner.new(source) + local self = setmetatable({}, Scanner) + self.source = source + self.line = 1 + self.index = 1 + self.start = 1 + self.tokens = {} + return self +end + +function Scanner.advance(self) + ch = string.sub(self.source, self.index, self.index) + self.index = self.index + 1 + return ch +end + +function Scanner.peek(self) + if self:isAtEnd() then + return '\0' + end + return string.sub(self.source, self.index, self.index) +end + +function Scanner.isAtEnd(self) + return self.index > #self.source +end + +function Scanner.is_special(char) + return char == '(' or char == ')' or + char == '[' or char == ']' or + char == '{' or char == '}' or + char == '\'' or char == '`' or char == '"' or + char == '@' or char == '~' or + char == '^' or char == '\0' +end + +function Scanner.scanTokens(self) + while not self:isAtEnd() do + self.start = self.index + self:scanToken() + end + table.insert(self.tokens,Token("EOF", "", self.line)) + return self.tokens +end + +function Scanner.match(self, char) + if self:peek() ~= char then + return false + end + self:advance() + return true +end + +function Scanner.string(self) + while self:peek() ~= '"' and not(self:isAtEnd()) do + self:advance() + end + print(string.format("peeeked %s", self:peek())) + if self:isAtEnd() then + print("Error unterminated string at line %d", self.line) + return + end + + self:advance() -- closing " + + val = string.sub(self.source, self.start+1, self.index-2) -- trimmed opening and closing " + + table.insert(self.tokens, Token("STR", val, self.line)) + +end + +function Scanner.scanToken(self) + char = self:advance() + + -- print(string.format("b c:%s, i:%d", char, self.index)) + if char == ' ' or char == ',' or char == '\n' then + if char == '\n' then + self.line = self.line + 1 + end + elseif char == '~' then + if self:match('@') then + table.insert(self.tokens, Token("TILDE_AT", "", self.line)) + else + table.insert(self.tokens, Token("TILDE", "", self.line)) + end + elseif char == '[' then + table.insert(self.tokens, Token("L_BRA", "", self.line)) + elseif char == ']' then + table.insert(self.tokens, Token("R_BRA", "", self.line)) + elseif char == '(' then + table.insert(self.tokens, Token("L_PAR", "", self.line)) + elseif char == ')' then + table.insert(self.tokens, Token("R_PAR", "", self.line)) + elseif char == '{' then + table.insert(self.tokens, Token("L_CURLY", "", self.line)) + elseif char == '}' then + table.insert(self.tokens, Token("R_CURLY", "", self.line)) + elseif char == '\'' then + table.insert(self.tokens, Token("TICK", "", self.line)) + elseif char == '`' then + table.insert(self.tokens, Token("BACKTICK", "", self.line)) + elseif char == '^' then + table.insert(self.tokens, Token("CARROT", "", self.line)) + elseif char == '@' then + table.insert(self.tokens, Token("AT", "", self.line)) + elseif char == '"' then + self:string() + + elseif char == ';' then + while self:peek() ~= '\n' and not(self:isAtEnd()) do + self:advance() + end + val = string.sub(self.source, self.start + 1, self.current) + table.insert(self.tokens, Token("CMT", val, self.line)) + + elseif not self.is_special(char) then + while not(self.is_special(self:peek())) do + self:advance() + end + val = string.sub(self.source,self.start, self.index - 1) + table.insert(self.tokens, Token("SYM", val, self.line)) + else + print(string.format("Error: unknown char: %s at %d, %d", char , line , idx) ) + end + +end + +return Scanner diff --git a/impls/lua.2/step1_repl.lua b/impls/lua.2/step1_repl.lua new file mode 100644 index 0000000000..65ee36f854 --- /dev/null +++ b/impls/lua.2/step1_repl.lua @@ -0,0 +1,26 @@ +reader = require "reader" +function READ(prompt) + io.write(prompt) + local v = io.read() + print(v) + return reader.read_str( v) +end +function EVAL(a) + return a +end +function PRINT(a) + reader.print_tokens(a) +end + + +function main() + while true do + line = READ('user> ') + if not line then + break + end + PRINT(EVAL(line)) + end +end + +main() diff --git a/impls/lua.2/token.lua b/impls/lua.2/token.lua new file mode 100644 index 0000000000..86f3da4627 --- /dev/null +++ b/impls/lua.2/token.lua @@ -0,0 +1,24 @@ +Token = {} +Token.__index = Token + +setmetatable(Token, { + __call = function (cls, ...) + return cls.new(...) + end, +}) + +function Token.new(typeof, val, line) + local self = setmetatable({}, Token) + self.typeof = typeof + self.val = val + self.line = line + return self +end + +function Token.tostring(self) + -- https://youtu.be/S4eNl1rA1Ns?t=1435 + return string.format("Token: %s:%s at %d", self.typeof, self.val, self.line) +end + + +return Token From e36c8cd5461e2ca5fb5b8ca58266995f9987131a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=BCseyin=20Er?= Date: Thu, 5 Jan 2023 20:27:46 +0300 Subject: [PATCH 03/25] type module added, everything re-written wrt to it --- impls/lua.2/reader.lua | 198 +++++++++++++++++- impls/lua.2/scanner.lua | 107 +++++++--- .../{step1_repl.lua => step1_read_print.lua} | 14 +- impls/lua.2/types.lua | 81 +++++++ 4 files changed, 363 insertions(+), 37 deletions(-) rename impls/lua.2/{step1_repl.lua => step1_read_print.lua} (55%) create mode 100644 impls/lua.2/types.lua diff --git a/impls/lua.2/reader.lua b/impls/lua.2/reader.lua index 6c73769543..ac6b76e3a7 100644 --- a/impls/lua.2/reader.lua +++ b/impls/lua.2/reader.lua @@ -1,20 +1,202 @@ -local reader = {} +local Reader = {} +Reader.__index = Reader + +local types = require "types" + +local List = types.MalList +local Vector = types.MalVector +local Nil = types.Nil +local HashMap = types.MalHashMap +local Sym = types.Sym +local is_instanceOf = types.isinstanceof +local Err = types.Err + +setmetatable(Reader, { + __call = function (cls, ...) + return cls.new(...) + end, +}) + +function Reader.new(tokens) + local self = setmetatable({}, Reader) + self.tokens = tokens + self.index = 1 + return self +end + +function Reader.peek(self) + return self.tokens[self.index] +end + +function Reader.advance(self) + tok = self.tokens[self.index] + self.index = self.index + 1 + return tok +end + + + Scanner = require "scanner" +function Reader.read_form(self) + local tok = self:peek() + if tok.typeof == '(' then + return List.new( self:read_seq('(', ')', { '}', ']' })) + elseif tok.typeof == '[' then + return Vector.new( self:read_seq('[', ']', { ')' , '}'})) + elseif tok.typeof == '{' then + return HashMap.new(table.unpack(self:read_seq('{', '}', { ')' , ']'}))) + elseif tok.typeof == 'CMT' then + self:advance() + return Nil + elseif tok.typeof == '@' then + self:advance() + return List.new({Sym.new('deref'), self:read_form()}) + elseif tok.typeof == '~@' then + self:advance() + return List.new({Sym.new('splice-unquote'), self:read_form()}) + elseif tok.typeof == '`' then + self:advance() + return List.new({Sym.new('quasiquote'), self:read_form()}) + elseif tok.typeof == '~' then + self:advance() + return List.new({Sym.new('unquote'), self:read_form()}) + elseif tok.typeof == "'" then + self:advance() + return List.new({Sym.new('quote'), self:read_form()}) + elseif tok.typeof == '^' then + self:advance() + local meta = self:read_form() + return List.new({Sym.new('with-meta'), self:read_form(), meta}) + elseif tok.typeof == ')' or tok.typeof == ']' or tok.typeof == '}' then + return Err.new("Syntax error") + else + return self:read_atom() + end +end +-- fix read atom with types module +function Reader.read_atom(self) + local token = self:advance() + if token.typeof == "STR" then + if token.val == nil then + return Err.new("unterminated string") + end + return token.val + elseif token.typeof == "SYM" then + if token.val == "true" then + return true + elseif token.val == "false" then + return false + elseif token.val == 'nil' then + return Nil + elseif string.match(token.val, '^-?%d+%.?%d*$') then + return tonumber(token.val) + else + return Sym.new(token.val) + end + + else + print(string.format("Error: is not atomic %s", token.typeof)) + return Err.new("internal error") + end +end + +function Reader.read_seq(self, opening, closing, invalids) + local tok = self:advance() -- consume opening + if tok.typeof ~= opening then + error("Error: expected '" .. opening .. "' got '" .. tok.typeof .. "'.") + end + tok = self:peek() + local res = {} + while tok.typeof ~= closing do + if tok.typeof == "EOF" then + print("Error: unexpected EOF before matching '" .. closing .. "'.") + return {} + end + + for i= 1, #invalids do + if tok.typeof == invalids[i] then + return Err.new("Error: invalid syntax") + end + end + table.insert(res, self:read_form()) + tok = self:peek() + end + self:advance() -- consume closing + return res +end + + +function Reader.stringfy_val(val, readably) + local res = '' + if is_instanceOf(val, Vector) then + res = res .. '[' + for i=1, #val do + res = res .. Reader.stringfy_val(val[i],readably) + if i ~= #val then + res = res .. " " + end + end + res = res .. ']' + elseif is_instanceOf(val, List) then + res = res .. '(' + for i=1, #val do + res = res .. Reader.stringfy_val(val[i],readably) + if i ~= #val then + res = res .. " " + end + end + res = res .. ')' + elseif is_instanceOf(val, HashMap) then + res = res .. '{' + for i,v in pairs(val) do + res = res .. Reader.stringfy_val(i, readably) .. " " .. Reader.stringfy_val(v,readably) + res = res .. " " + end + if #res > 1 then + res = string.sub(res, 1, #res-1) -- trim last space + end + res = res .. '}' + + elseif is_instanceOf(val, Sym) then + return val.val + elseif is_instanceOf(val, Err) then + return "Error:" .. Scanner.unescape(val.val) + elseif type(val) == "string" then + if readably then + res = Scanner.unescape(val) + else + res = Scanner.escape(val) + end + elseif type(val) == "number" then + res = tostring(val) + elseif val == Nil then + res = "nil" + elseif type(val) == "boolean" then + res = tostring(val) + else + error(string.format("Error: unknown type %s", val)) + + end + return res +end -function reader.print_tokens(tokens) +function Reader.print_tokens(tokens) for i,v in ipairs(tokens) do print(i,v:tostring()) end end -function reader.read_str(a) - s = Scanner(a) - print(string.format("Scanner source: %s", s.source)) - toks = s:scanTokens() - return toks +function Reader.read_str(a) + local s = Scanner(a) + local toks = s:scanTokens() + if not toks then + return Err.new("No token") + end + local r = Reader(toks) + return r:read_form() end -return reader +return Reader diff --git a/impls/lua.2/scanner.lua b/impls/lua.2/scanner.lua index a163d068b7..0627013de9 100644 --- a/impls/lua.2/scanner.lua +++ b/impls/lua.2/scanner.lua @@ -36,14 +36,62 @@ function Scanner.isAtEnd(self) end function Scanner.is_special(char) - return char == '(' or char == ')' or - char == '[' or char == ']' or - char == '{' or char == '}' or - char == '\'' or char == '`' or char == '"' or - char == '@' or char == '~' or - char == '^' or char == '\0' + return char == '(' or char == ')' or char == '[' or char == ']' or + char == '{' or char == '}' or char == '\'' or char == '`' or + char == '"' or char == '@' or char == '~' or char == '^' or + char == '\0'or char == ' ' or char == '\t' or char == '\n' or char == ',' end +function Scanner.escape(str) + local target = string.byte('\\') + local dq = string.byte('"') + local nl = string.byte('n') + local res = '' + local idx = 1 + while idx <= #str do + if str:byte(idx) == target and str:byte(idx+1) == dq then + res = res .. '"' + idx = idx + 1 + elseif str:byte(idx) == target and str:byte(idx+1) == nl then + res = res .. '\n' + idx = idx + 1 + elseif str:byte(idx) == target and str:byte(idx+1) == target then + res = res .. '\\' + idx = idx + 1 + else + res = res .. str:sub(idx,idx) + end + + idx = idx + 1 + + end + for idx = 1, #str do + end + return res +end +function Scanner.unescape(str) + local nl = string.byte('\n') + local bs = string.byte('\\') + local dq = string.byte('"') + local res = '"' + local idx = 1 + while idx <= #str do + if str:byte(idx) == nl then + res = res .. '\\n' + elseif str:byte(idx) == bs then + res = res .. '\\\\' + elseif str:byte(idx) == dq then + res = res .. '\\"' + else + res = res .. str:sub(idx,idx) + end + idx = idx + 1 + end + res = res .. '"' + return res +end + + function Scanner.scanTokens(self) while not self:isAtEnd() do self.start = self.index @@ -63,18 +111,22 @@ end function Scanner.string(self) while self:peek() ~= '"' and not(self:isAtEnd()) do + if self:peek() == '\\' then + self:advance() + end self:advance() end - print(string.format("peeeked %s", self:peek())) if self:isAtEnd() then - print("Error unterminated string at line %d", self.line) + print(string.format("Error unbalanced string at line %d", self.line)) + table.insert(self.tokens, Token("STR", nil, self.line)) return end self:advance() -- closing " - - val = string.sub(self.source, self.start+1, self.index-2) -- trimmed opening and closing " - + + -- trimmed opening and closing " + val = self.escape(string.sub(self.source, self.start+1, self.index-2)) + table.insert(self.tokens, Token("STR", val, self.line)) end @@ -83,36 +135,36 @@ function Scanner.scanToken(self) char = self:advance() -- print(string.format("b c:%s, i:%d", char, self.index)) - if char == ' ' or char == ',' or char == '\n' then + if char == ' ' or char == ',' or char == '\n' or char =='\t' then if char == '\n' then self.line = self.line + 1 end elseif char == '~' then if self:match('@') then - table.insert(self.tokens, Token("TILDE_AT", "", self.line)) + table.insert(self.tokens, Token("~@", "", self.line)) else - table.insert(self.tokens, Token("TILDE", "", self.line)) + table.insert(self.tokens, Token("~", "", self.line)) end elseif char == '[' then - table.insert(self.tokens, Token("L_BRA", "", self.line)) + table.insert(self.tokens, Token("[", "", self.line)) elseif char == ']' then - table.insert(self.tokens, Token("R_BRA", "", self.line)) + table.insert(self.tokens, Token("]", "", self.line)) elseif char == '(' then - table.insert(self.tokens, Token("L_PAR", "", self.line)) + table.insert(self.tokens, Token("(", "", self.line)) elseif char == ')' then - table.insert(self.tokens, Token("R_PAR", "", self.line)) + table.insert(self.tokens, Token(")", "", self.line)) elseif char == '{' then - table.insert(self.tokens, Token("L_CURLY", "", self.line)) + table.insert(self.tokens, Token("{", "", self.line)) elseif char == '}' then - table.insert(self.tokens, Token("R_CURLY", "", self.line)) + table.insert(self.tokens, Token("}", "", self.line)) elseif char == '\'' then - table.insert(self.tokens, Token("TICK", "", self.line)) + table.insert(self.tokens, Token("'", "", self.line)) elseif char == '`' then - table.insert(self.tokens, Token("BACKTICK", "", self.line)) + table.insert(self.tokens, Token("`", "", self.line)) elseif char == '^' then - table.insert(self.tokens, Token("CARROT", "", self.line)) + table.insert(self.tokens, Token("^", "", self.line)) elseif char == '@' then - table.insert(self.tokens, Token("AT", "", self.line)) + table.insert(self.tokens, Token("@", "", self.line)) elseif char == '"' then self:string() @@ -134,5 +186,12 @@ function Scanner.scanToken(self) end end +--[[ examples +print(Scanner.escape("\\\\\\\\")) + +print(Scanner.escape('\\"he"l\\nlo"')) +print(Scanner.unescape("\n")) +print(Scanner.unescape('"hello"')) +]] return Scanner diff --git a/impls/lua.2/step1_repl.lua b/impls/lua.2/step1_read_print.lua similarity index 55% rename from impls/lua.2/step1_repl.lua rename to impls/lua.2/step1_read_print.lua index 65ee36f854..d8cabd5f18 100644 --- a/impls/lua.2/step1_repl.lua +++ b/impls/lua.2/step1_read_print.lua @@ -1,22 +1,26 @@ -reader = require "reader" +Reader = require "reader" function READ(prompt) io.write(prompt) local v = io.read() - print(v) - return reader.read_str( v) + if v == nil then + io.write('\n') + return nil + end + return Reader.read_str( v) end function EVAL(a) return a end function PRINT(a) - reader.print_tokens(a) + print(Reader.stringfy_val(a, true)) end function main() + local line = '' while true do line = READ('user> ') - if not line then + if line == nil then break end PRINT(EVAL(line)) diff --git a/impls/lua.2/types.lua b/impls/lua.2/types.lua new file mode 100644 index 0000000000..81bb011f94 --- /dev/null +++ b/impls/lua.2/types.lua @@ -0,0 +1,81 @@ +local M = {} + +M.MalHashMap = {} +M.MalHashMap.__index = M.MalHashMap + +function M.MalHashMap.new(...) + arg = table.pack(...) + if #arg % 2 ~= 0 then + error("Hash map input must be even") + end + + local self = {} + setmetatable(self, M.MalHashMap) + for i= 1, #arg, 2 do + self[arg[i]] = arg[i+1] + end + + return self +end + +M.MalList = {} +M.MalList.__index = M.MalList + +function M.MalList.new(lst) + local self = lst and lst or {} + setmetatable(self, M.MalList) + return self +end + +M.MalVector = {} +M.MalVector.__index = M.MalVector +function M.MalVector.new(lst) + local self = lst and lst or {} + setmetatable(self, M.MalVector) + return self +end + +M.Sym = {} +M.Sym.__index = M.Sym +function M.Sym.new(val) + local self = {} + self.val = val + setmetatable(self, M.Sym) + return self +end +M.Err = {} +M.Err.__index = M.Err +function M.Err.new(val) + local self = {} + self.val = val + setmetatable(self, M.Err) + return self +end + + + + +M.MalNilType = {} +M.MalNilType.__index = M.MalNilType +function M.MalNilType.new() + local self = setmetatable({}, M.MalNilType) + return self +end + +function M.MalNilType:tostring() + return "Nil" +end + +M.Nil = M.MalNilType.new() + + +function M.isinstanceof(obj, super) + local mt = getmetatable(obj) + while true do + if mt == nil then return false end + if mt == super then return true end + mt = getmetatable(mt) + end +end + +return M From e3fc5be8b6cdb499f39b225271ce5358e6233c0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=BCseyin=20Er?= Date: Sat, 7 Jan 2023 17:26:10 +0300 Subject: [PATCH 04/25] step2 and step1 fixes --- impls/lua.2/reader.lua | 22 ++++--- impls/lua.2/scanner.lua | 10 +-- impls/lua.2/step1_read_print.lua | 34 ++++++++-- impls/lua.2/step2_eval.lua | 108 +++++++++++++++++++++++++++++++ impls/lua.2/types.lua | 14 +++- 5 files changed, 167 insertions(+), 21 deletions(-) create mode 100644 impls/lua.2/step2_eval.lua diff --git a/impls/lua.2/reader.lua b/impls/lua.2/reader.lua index ac6b76e3a7..ed3f124951 100644 --- a/impls/lua.2/reader.lua +++ b/impls/lua.2/reader.lua @@ -10,6 +10,8 @@ local HashMap = types.MalHashMap local Sym = types.Sym local is_instanceOf = types.isinstanceof local Err = types.Err +local throw = types.throw +local Function = types.MalFunction setmetatable(Reader, { __call = function (cls, ...) @@ -69,7 +71,7 @@ function Reader.read_form(self) local meta = self:read_form() return List.new({Sym.new('with-meta'), self:read_form(), meta}) elseif tok.typeof == ')' or tok.typeof == ']' or tok.typeof == '}' then - return Err.new("Syntax error") + throw("Syntax error unexpected '" .. tok.typeof .. "'") else return self:read_atom() end @@ -78,9 +80,6 @@ end function Reader.read_atom(self) local token = self:advance() if token.typeof == "STR" then - if token.val == nil then - return Err.new("unterminated string") - end return token.val elseif token.typeof == "SYM" then if token.val == "true" then @@ -96,15 +95,15 @@ function Reader.read_atom(self) end else - print(string.format("Error: is not atomic %s", token.typeof)) - return Err.new("internal error") + + throw(string.format("Error: is not atomic %s", token.typeof)) end end function Reader.read_seq(self, opening, closing, invalids) local tok = self:advance() -- consume opening if tok.typeof ~= opening then - error("Error: expected '" .. opening .. "' got '" .. tok.typeof .. "'.") + throw("Error: expected '" .. opening .. "' got '" .. tok.typeof .. "'.") end tok = self:peek() local res = {} @@ -117,7 +116,7 @@ function Reader.read_seq(self, opening, closing, invalids) for i= 1, #invalids do if tok.typeof == invalids[i] then - return Err.new("Error: invalid syntax") + throw("invalid syntax un expected '" .. tok.typeof .. "'") end end @@ -163,7 +162,10 @@ function Reader.stringfy_val(val, readably) elseif is_instanceOf(val, Sym) then return val.val elseif is_instanceOf(val, Err) then - return "Error:" .. Scanner.unescape(val.val) + return "Error: " .. Scanner.unescape(val.val) + elseif is_instanceOf(val, Function) then + res = "(fn* " -- .. Reader.stringfy_val(val.params) .. " " Reader.stringfy_val(val.ast) ..")" + elseif type(val) == "string" then if readably then res = Scanner.unescape(val) @@ -176,6 +178,8 @@ function Reader.stringfy_val(val, readably) res = "nil" elseif type(val) == "boolean" then res = tostring(val) + elseif type(val) == "function" then + res = "#" else error(string.format("Error: unknown type %s", val)) diff --git a/impls/lua.2/scanner.lua b/impls/lua.2/scanner.lua index 0627013de9..6528a28ff6 100644 --- a/impls/lua.2/scanner.lua +++ b/impls/lua.2/scanner.lua @@ -1,4 +1,6 @@ local Token = require('token') +types = require("types") +local throw = types.throw local Scanner = {} Scanner.__index = Scanner @@ -39,7 +41,7 @@ function Scanner.is_special(char) return char == '(' or char == ')' or char == '[' or char == ']' or char == '{' or char == '}' or char == '\'' or char == '`' or char == '"' or char == '@' or char == '~' or char == '^' or - char == '\0'or char == ' ' or char == '\t' or char == '\n' or char == ',' + char == '\0'or char == ' ' or char == '\t' or char == '\n' or char == ',' or char == ';' end function Scanner.escape(str) @@ -117,9 +119,7 @@ function Scanner.string(self) self:advance() end if self:isAtEnd() then - print(string.format("Error unbalanced string at line %d", self.line)) - table.insert(self.tokens, Token("STR", nil, self.line)) - return + throw(string.format("Error unbalanced string at line %d", self.line)) end self:advance() -- closing " @@ -182,7 +182,7 @@ function Scanner.scanToken(self) val = string.sub(self.source,self.start, self.index - 1) table.insert(self.tokens, Token("SYM", val, self.line)) else - print(string.format("Error: unknown char: %s at %d, %d", char , line , idx) ) + throw(string.format("Error: unknown char: %s at %d, %d", char , line , idx) ) end end diff --git a/impls/lua.2/step1_read_print.lua b/impls/lua.2/step1_read_print.lua index d8cabd5f18..f39916ca77 100644 --- a/impls/lua.2/step1_read_print.lua +++ b/impls/lua.2/step1_read_print.lua @@ -1,29 +1,53 @@ Reader = require "reader" -function READ(prompt) + +types = require "types" +--local throw = types.throw +local Err = types.Err +local is_instanceOf = types.isinstanceof + +function raw_read(prompt) io.write(prompt) local v = io.read() if v == nil then io.write('\n') - return nil end - return Reader.read_str( v) + return v +end + + +function READ(v) + return Reader.read_str(v) end + function EVAL(a) return a end + function PRINT(a) print(Reader.stringfy_val(a, true)) end +function rep(str) + return PRINT(EVAL(READ(str))) +end + + function main() local line = '' while true do - line = READ('user> ') + local line = raw_read('user> ') if line == nil then break end - PRINT(EVAL(line)) + status, err = pcall( function () print(rep(line)) end) + if not status then + if is_instanceOf(err, Err) then + err = Reader.stringfy_val(err) + end + print(err) + print(debug.traceback()) + end end end diff --git a/impls/lua.2/step2_eval.lua b/impls/lua.2/step2_eval.lua new file mode 100644 index 0000000000..418ad04f4d --- /dev/null +++ b/impls/lua.2/step2_eval.lua @@ -0,0 +1,108 @@ +Reader = require "reader" +types = require "types" +local Sym = types.Sym +local is_instanceOf = types.isinstanceof +local Err = types.Err +local List = types.MalList +local throw = types.throw +local HashMap = types.MalHashMap +local Vector = types.MalVector +function raw_read(prompt) + io.write(prompt) + local v = io.read() + if v == nil then + io.write('\n') + end + return v +end + +function READ(str) + return Reader.read_str(str) +end + +function EVAL(a, env) + if is_instanceOf(a, List) then + if #a == 0 then + return a + elseif #a == 3 then + new_list = eval_ast(a, env) + return new_list[1](new_list[2],new_list[3]) + else + throw("'" .. Reader.stringfy_val(a[1]) .. "' should be called with 2 elements") + end + else + return eval_ast(a, env) + end +end +function eval_ast(ast, env) + if is_instanceOf(ast, List) then + local l = List.new() + for i=1,#ast do + table.insert(l, EVAL(ast[i], env)) + end + return l + elseif is_instanceOf(ast, Vector) then + local v = Vector.new() + for i=1, #ast do + table.insert(v, EVAL(ast[i], env)) + end + return v + elseif is_instanceOf(ast, HashMap) then + local map = HashMap.new() + for i,v in pairs(ast) do + map[EVAL(i, env)] = EVAL(v, env) + end + return map + elseif is_instanceOf(ast, Sym) then + if env[ast.val] == nil then + types.throw(string.format("Value : '%s' does not exist", ast.val)) + else + return env[ast.val] + end + elseif type(ast) == "number" or type(ast) == "string" or type(ast) == "function" then + return ast + else + throw("dont know how to eval") + end +end + + +function PRINT(a) + print(Reader.stringfy_val(a, true)) +end + + +local repl_env = { +['+'] = function (a,b) return a+b end, +['-'] = function (a,b) return a-b end, +['*'] = function (a,b) return a*b end, +['/'] = function (a,b) return a/b end, +['a'] = types.MalList.new({1,2,3}), + +['n'] = 3, +} + +function rep(str) + return PRINT(EVAL(READ(str), repl_env)) +end + + +function main() + local line = '' + while true do + line = raw_read('user> ') + if line == nil then + break + end + status, err = pcall(function () print(rep(line)) end) + if not status then + if is_instanceOf(err, Err) then + err = Reader.stringfy_val(err) + end + print(err) + print(debug.traceback()) + end + end +end + +main() diff --git a/impls/lua.2/types.lua b/impls/lua.2/types.lua index 81bb011f94..675c50a85b 100644 --- a/impls/lua.2/types.lua +++ b/impls/lua.2/types.lua @@ -6,7 +6,7 @@ M.MalHashMap.__index = M.MalHashMap function M.MalHashMap.new(...) arg = table.pack(...) if #arg % 2 ~= 0 then - error("Hash map input must be even") + error("Hash map input must be even but got '" .. #arg .. "'") end local self = {} @@ -52,7 +52,9 @@ function M.Err.new(val) return self end - +function M.throw(val) + error(M.Err.new(val)) +end M.MalNilType = {} @@ -68,6 +70,14 @@ end M.Nil = M.MalNilType.new() +M.MalFunction = {} +M.MalFunction.__index = M.MalFunction +function M.MalFunction.new(fn, ast, env, params) + local self = {fn = fn, ast = ast, env = env, params = params} + return setmetatable(self, M.MalFunction) +end + + function M.isinstanceof(obj, super) local mt = getmetatable(obj) From 9b06e0c42b53f5ff176b7a7734270d8f2ca99e93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=BCseyin=20Er?= Date: Sat, 7 Jan 2023 20:29:10 +0300 Subject: [PATCH 05/25] adding support for ':key' --- impls/lua.2/reader.lua | 1 + impls/lua.2/scanner.lua | 7 ++++++- impls/lua.2/step2_eval.lua | 8 ++++++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/impls/lua.2/reader.lua b/impls/lua.2/reader.lua index ed3f124951..44f59aad48 100644 --- a/impls/lua.2/reader.lua +++ b/impls/lua.2/reader.lua @@ -197,6 +197,7 @@ function Reader.read_str(a) if not toks then return Err.new("No token") end + -- Reader.print_tokens(toks) local r = Reader(toks) return r:read_form() end diff --git a/impls/lua.2/scanner.lua b/impls/lua.2/scanner.lua index 6528a28ff6..851fc02c3d 100644 --- a/impls/lua.2/scanner.lua +++ b/impls/lua.2/scanner.lua @@ -174,7 +174,12 @@ function Scanner.scanToken(self) end val = string.sub(self.source, self.start + 1, self.current) table.insert(self.tokens, Token("CMT", val, self.line)) - + elseif char == ':' then + while not (self.is_special(self:peek())) and not(self:isAtEnd()) do + self:advance() + end + val = "\u{29E}" .. string.sub(self.source, self.start+1, self.index-1) + table.insert(self.tokens, Token("SYM", val, self.line)) elseif not self.is_special(char) then while not(self.is_special(self:peek())) do self:advance() diff --git a/impls/lua.2/step2_eval.lua b/impls/lua.2/step2_eval.lua index 418ad04f4d..91c751f09a 100644 --- a/impls/lua.2/step2_eval.lua +++ b/impls/lua.2/step2_eval.lua @@ -49,11 +49,15 @@ function eval_ast(ast, env) return v elseif is_instanceOf(ast, HashMap) then local map = HashMap.new() - for i,v in pairs(ast) do - map[EVAL(i, env)] = EVAL(v, env) + for k,v in pairs(ast) do + map[EVAL(k, env)] = EVAL(v, env) end return map elseif is_instanceOf(ast, Sym) then + if string.byte(ast.val, 1, 1) == 202 and + string.byte(ast.val, 2, 2) == 158 then -- this magic numbers come from \u{29E} + return Sym.new(":" .. string.sub(ast.val, 3, #ast.val)) + end if env[ast.val] == nil then types.throw(string.format("Value : '%s' does not exist", ast.val)) else From 7eca78be50589249bae8cbd08d5a8e1a96f9804a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=BCseyin=20Er?= Date: Sun, 8 Jan 2023 18:50:59 +0300 Subject: [PATCH 06/25] lua.2 : step3 all test passes --- impls/lua.2/env.lua | 47 ++++++++++++ impls/lua.2/reader.lua | 6 +- impls/lua.2/step3_env.lua | 151 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 202 insertions(+), 2 deletions(-) create mode 100644 impls/lua.2/env.lua create mode 100644 impls/lua.2/step3_env.lua diff --git a/impls/lua.2/env.lua b/impls/lua.2/env.lua new file mode 100644 index 0000000000..d15395568d --- /dev/null +++ b/impls/lua.2/env.lua @@ -0,0 +1,47 @@ +types = require "types" +Reader = require "reader" +throw = types.throw +Sym = types.Sym +is_instanceOf = types.isinstanceof + +local M = {} +M.Env = {} +M.Env.__index = M.Env + +function M.Env.new(outer) + local self = {} + self.outer = outer + setmetatable(self, M.Env) + return self +end + + +function M.Env:set(key, val) + assert(is_instanceOf(key, Sym), "key should be symbol") + self[Reader.stringfy_val(key)] = val + return self +end + +function M.Env:find(key) + assert(is_instanceOf(key, Sym), "key should be symbol") + if self[Reader.stringfy_val(key)] then + return self + end + if self.outer ~= nil then + return self.outer:find(key) + end + return nil + +end + +function M.Env:get(key) + assert(is_instanceOf(key, Sym), "key should be symbol") + local env = self:find(key) + if env then + return env[Reader.stringfy_val(key)] + end + throw(string.format("%s not found in any environments.", Reader.stringfy_val(key))) + +end + +return M.Env diff --git a/impls/lua.2/reader.lua b/impls/lua.2/reader.lua index 44f59aad48..0a77660465 100644 --- a/impls/lua.2/reader.lua +++ b/impls/lua.2/reader.lua @@ -86,13 +86,15 @@ function Reader.read_atom(self) return true elseif token.val == "false" then return false - elseif token.val == 'nil' then + elseif token.val == 'Nil' then return Nil elseif string.match(token.val, '^-?%d+%.?%d*$') then return tonumber(token.val) else return Sym.new(token.val) end + elseif token.typeof == "EOF" then + return Nil else @@ -175,7 +177,7 @@ function Reader.stringfy_val(val, readably) elseif type(val) == "number" then res = tostring(val) elseif val == Nil then - res = "nil" + res = "Nil" elseif type(val) == "boolean" then res = tostring(val) elseif type(val) == "function" then diff --git a/impls/lua.2/step3_env.lua b/impls/lua.2/step3_env.lua new file mode 100644 index 0000000000..41d9060ece --- /dev/null +++ b/impls/lua.2/step3_env.lua @@ -0,0 +1,151 @@ +Reader = require "reader" +types = require "types" +Env = require "env" +local Sym = types.Sym +local is_instanceOf = types.isinstanceof +local Err = types.Err +local List = types.MalList +local throw = types.throw +local HashMap = types.MalHashMap +local Vector = types.MalVector + +function raw_read(prompt) + io.write(prompt) + local v = io.read() + if v == nil then + io.write('\n') + end + return v +end + +function READ(str) + return Reader.read_str(str) +end + +function EVAL(a, env) + if is_instanceOf(a, List) then + if #a == 0 then + return a + end + local first_elem = a[1] + + if is_instanceOf(first_elem, Sym) and first_elem.val == "def!" then + if #a ~= 3 then + throw(string.format("def! expects 2 arguments got: %d", #a-1)) + end + if not(is_instanceOf(a[2], Sym)) then + throw("first argument to def! must be symbol") + end + local value = EVAL(a[3], env) + env:set(a[2], value) + return value + end + + if is_instanceOf(first_elem, Sym) and first_elem.val == "let*" then + if #a ~= 3 then + throw(string.format("let* expects 2 arguments got: %d", #a-1)) + end + + local let_env = Env.new(env) + if not(is_instanceOf(a[2], List) or is_instanceOf(a[2], Vector)) then + throw("Second arg to let* should be list or vector") + end + if #a[2] % 2 ~= 0 then + throw(string.format("Length ofSecond arg to let* should be even number got: %d", #a[2])) + end + + for i=1,#a[2],2 do + if not(is_instanceOf(a[2][i], Sym)) then + throw("Expected symbol in let*'s second argument") + end + local key = a[2][i] + local value = EVAL(a[2][i+1],let_env) + let_env:set(key,value) + end + return EVAL(a[3], let_env) + + end + + local new_list = eval_ast(a, env) + if type(new_list[1]) ~= "function" then + throw("First elem should be function or special form got :'" .. type(new_list[1]) .. "'.") + end + + if #a ~= 3 then + throw("currently all builtins expects 2 arguments") + end + return new_list[1](new_list[2],new_list[3]) --fixme: varargs? + + else + return eval_ast(a, env) + end +end +function eval_ast(ast, env) + if is_instanceOf(ast, List) then + local l = List.new() + for i=1,#ast do + table.insert(l, EVAL(ast[i], env)) + end + return l + elseif is_instanceOf(ast, Vector) then + local v = Vector.new() + for i=1, #ast do + table.insert(v, EVAL(ast[i], env)) + end + return v + elseif is_instanceOf(ast, HashMap) then + local map = HashMap.new() + for k,v in pairs(ast) do + map[EVAL(k, env)] = EVAL(v, env) + end + return map + elseif is_instanceOf(ast, Sym) then + if string.byte(ast.val, 1, 1) == 202 and + string.byte(ast.val, 2, 2) == 158 then -- this magic numbers come from \u{29E} + return Sym.new(":" .. string.sub(ast.val, 3, #ast.val)) + end + return env:get(ast) + + else + return ast + end +end + + +function PRINT(a) + print(Reader.stringfy_val(a, true)) +end + + +local repl_env = Env.new(nil) + +repl_env = repl_env:set(Sym.new('+'), function (a,b) return a+b end) +repl_env:set(Sym.new('-'), function (a,b) return a-b end) +repl_env:set(Sym.new('*'), function (a,b) return a*b end) +repl_env:set(Sym.new('/'), function (a,b) return a/b end) + + +function rep(str) + return PRINT(EVAL(READ(str), repl_env)) +end + + +function main() + local line = '' + while true do + line = raw_read('user> ') + if line == nil then + break + end + status, err = pcall(function () print(rep(line)) end) + if not status then + if is_instanceOf(err, Err) then + err = Reader.stringfy_val(err) + end + print(err) + print(debug.traceback()) + end + end +end + +main() From 04a7222c7e4aff24ccffe135c246e631fa21cad2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=BCseyin=20Er?= Date: Wed, 11 Jan 2023 22:40:14 +0300 Subject: [PATCH 07/25] if fn do --- impls/lua.2/env.lua | 55 +++++---- impls/lua.2/printer.lua | 76 +++++++++++++ impls/lua.2/reader.lua | 75 ++----------- impls/lua.2/scanner.lua | 38 +++---- impls/lua.2/step1_read_print.lua | 5 +- impls/lua.2/step2_eval.lua | 7 +- impls/lua.2/step3_env.lua | 11 +- impls/lua.2/step4_if_fn_do.lua | 185 +++++++++++++++++++++++++++++++ impls/lua.2/token.lua | 2 +- 9 files changed, 336 insertions(+), 118 deletions(-) create mode 100644 impls/lua.2/printer.lua create mode 100644 impls/lua.2/step4_if_fn_do.lua diff --git a/impls/lua.2/env.lua b/impls/lua.2/env.lua index d15395568d..f56e80a84a 100644 --- a/impls/lua.2/env.lua +++ b/impls/lua.2/env.lua @@ -1,47 +1,62 @@ -types = require "types" -Reader = require "reader" -throw = types.throw -Sym = types.Sym -is_instanceOf = types.isinstanceof +local types = require "types" +local Printer = require "printer" +local throw = types.throw +local Sym = types.Sym +local List = types.MalList +local is_instanceOf = types.isinstanceof -local M = {} -M.Env = {} -M.Env.__index = M.Env +local Env = {} +Env.__index = Env -function M.Env.new(outer) +function Env.new(outer) local self = {} self.outer = outer - setmetatable(self, M.Env) + setmetatable(self, Env) return self end -function M.Env:set(key, val) +function Env:bind(binds, exprs) + if not(is_instanceOf(binds, List)) then throw("binds should be list") end + if not(is_instanceOf(exprs, List)) then throw("exprs should be list") end + + if #binds ~= #exprs then + throw("number of bindings and expressions should be equal") + end + for i, b in ipairs(binds) do + if not(is_instanceOf(b, Sym)) then + throw("%d/ in the binds should be Symbol ",i, #binds) + end + print("binding " .. Printer.stringfy_val(b) .. " to " .. Printer.stringfy_val(exprs[i])) + self[Printer.stringfy_val(b)] = exprs[i] + end + +end + +function Env:set(key, val) assert(is_instanceOf(key, Sym), "key should be symbol") - self[Reader.stringfy_val(key)] = val + self[Printer.stringfy_val(key)] = val return self end -function M.Env:find(key) +function Env:find(key) assert(is_instanceOf(key, Sym), "key should be symbol") - if self[Reader.stringfy_val(key)] then + if self[Printer.stringfy_val(key)] then return self end if self.outer ~= nil then return self.outer:find(key) end return nil - end -function M.Env:get(key) +function Env:get(key) assert(is_instanceOf(key, Sym), "key should be symbol") local env = self:find(key) if env then - return env[Reader.stringfy_val(key)] + return env[Printer.stringfy_val(key)] end - throw(string.format("%s not found in any environments.", Reader.stringfy_val(key))) - + throw(string.format("%s not found in any environments.", Printer.stringfy_val(key))) end -return M.Env +return Env diff --git a/impls/lua.2/printer.lua b/impls/lua.2/printer.lua new file mode 100644 index 0000000000..475daf89f3 --- /dev/null +++ b/impls/lua.2/printer.lua @@ -0,0 +1,76 @@ +local Printer = {} + +local Scanner = require "scanner" +local types = require "types" + +local List = types.MalList +local Vector = types.MalVector +local Nil = types.Nil +local HashMap = types.MalHashMap +local Sym = types.Sym +local is_instanceOf = types.isinstanceof +local Err = types.Err +local Function = types.MalFunction + + + +function Printer.stringfy_val(val, readably) + local res = '' + if is_instanceOf(val, Vector) then + res = res .. '[' + for i=1, #val do + res = res .. Printer.stringfy_val(val[i],readably) + if i ~= #val then + res = res .. " " + end + end + res = res .. ']' + elseif is_instanceOf(val, List) then + res = res .. '(' + for i=1, #val do + res = res .. Printer.stringfy_val(val[i],readably) + if i ~= #val then + res = res .. " " + end + end + res = res .. ')' + elseif is_instanceOf(val, HashMap) then + res = res .. '{' + for i,v in pairs(val) do + res = res .. Printer.stringfy_val(i, readably) .. " " .. Printer.stringfy_val(v,readably) + res = res .. " " + end + if #res > 1 then + res = string.sub(res, 1, #res-1) -- trim last space + end + res = res .. '}' + + elseif is_instanceOf(val, Sym) then + return val.val + elseif is_instanceOf(val, Err) then + return "Error: " .. Scanner.unescape(val.val) + elseif is_instanceOf(val, Function) then + res = "(fn* " -- .. Printer.stringfy_val(val.params) .. " " Printer.stringfy_val(val.ast) ..")" + + elseif type(val) == "string" then + if readably then + res = Scanner.unescape(val) + else + res = Scanner.escape(val) + end + elseif type(val) == "number" then + res = tostring(val) + elseif val == Nil then + res = "Nil" + elseif type(val) == "boolean" then + res = tostring(val) + elseif type(val) == "function" then + res = "#" + else + error(string.format("Error: unknown type %s", val)) + end + return res +end + + +return Printer diff --git a/impls/lua.2/reader.lua b/impls/lua.2/reader.lua index 0a77660465..a10a543fa9 100644 --- a/impls/lua.2/reader.lua +++ b/impls/lua.2/reader.lua @@ -31,14 +31,14 @@ function Reader.peek(self) end function Reader.advance(self) - tok = self.tokens[self.index] + local tok = self.tokens[self.index] self.index = self.index + 1 return tok end -Scanner = require "scanner" +local Scanner = require "scanner" function Reader.read_form(self) local tok = self:peek() @@ -86,37 +86,36 @@ function Reader.read_atom(self) return true elseif token.val == "false" then return false - elseif token.val == 'Nil' then + elseif token.val == 'nil' then return Nil elseif string.match(token.val, '^-?%d+%.?%d*$') then return tonumber(token.val) else return Sym.new(token.val) end - elseif token.typeof == "EOF" then + elseif token.typeof == "EOF" then return Nil else - throw(string.format("Error: is not atomic %s", token.typeof)) end end function Reader.read_seq(self, opening, closing, invalids) local tok = self:advance() -- consume opening - if tok.typeof ~= opening then + if tok.typeof ~= opening then throw("Error: expected '" .. opening .. "' got '" .. tok.typeof .. "'.") end tok = self:peek() - local res = {} + local res = {} while tok.typeof ~= closing do - if tok.typeof == "EOF" then + if tok.typeof == "EOF" then print("Error: unexpected EOF before matching '" .. closing .. "'.") return {} end - for i= 1, #invalids do + for i= 1, #invalids do if tok.typeof == invalids[i] then throw("invalid syntax un expected '" .. tok.typeof .. "'") end @@ -130,64 +129,6 @@ function Reader.read_seq(self, opening, closing, invalids) end -function Reader.stringfy_val(val, readably) - local res = '' - if is_instanceOf(val, Vector) then - res = res .. '[' - for i=1, #val do - res = res .. Reader.stringfy_val(val[i],readably) - if i ~= #val then - res = res .. " " - end - end - res = res .. ']' - elseif is_instanceOf(val, List) then - res = res .. '(' - for i=1, #val do - res = res .. Reader.stringfy_val(val[i],readably) - if i ~= #val then - res = res .. " " - end - end - res = res .. ')' - elseif is_instanceOf(val, HashMap) then - res = res .. '{' - for i,v in pairs(val) do - res = res .. Reader.stringfy_val(i, readably) .. " " .. Reader.stringfy_val(v,readably) - res = res .. " " - end - if #res > 1 then - res = string.sub(res, 1, #res-1) -- trim last space - end - res = res .. '}' - - elseif is_instanceOf(val, Sym) then - return val.val - elseif is_instanceOf(val, Err) then - return "Error: " .. Scanner.unescape(val.val) - elseif is_instanceOf(val, Function) then - res = "(fn* " -- .. Reader.stringfy_val(val.params) .. " " Reader.stringfy_val(val.ast) ..")" - - elseif type(val) == "string" then - if readably then - res = Scanner.unescape(val) - else - res = Scanner.escape(val) - end - elseif type(val) == "number" then - res = tostring(val) - elseif val == Nil then - res = "Nil" - elseif type(val) == "boolean" then - res = tostring(val) - elseif type(val) == "function" then - res = "#" - else - error(string.format("Error: unknown type %s", val)) - - end - return res -end function Reader.print_tokens(tokens) for i,v in ipairs(tokens) do print(i,v:tostring()) end diff --git a/impls/lua.2/scanner.lua b/impls/lua.2/scanner.lua index 851fc02c3d..4356a5c2e5 100644 --- a/impls/lua.2/scanner.lua +++ b/impls/lua.2/scanner.lua @@ -1,5 +1,5 @@ local Token = require('token') -types = require("types") +local types = require("types") local throw = types.throw local Scanner = {} Scanner.__index = Scanner @@ -21,13 +21,13 @@ function Scanner.new(source) end function Scanner.advance(self) - ch = string.sub(self.source, self.index, self.index) + local ch = string.sub(self.source, self.index, self.index) self.index = self.index + 1 return ch end function Scanner.peek(self) - if self:isAtEnd() then + if self:isAtEnd() then return '\0' end return string.sub(self.source, self.index, self.index) @@ -50,7 +50,7 @@ function Scanner.escape(str) local nl = string.byte('n') local res = '' local idx = 1 - while idx <= #str do + while idx <= #str do if str:byte(idx) == target and str:byte(idx+1) == dq then res = res .. '"' idx = idx + 1 @@ -65,10 +65,8 @@ function Scanner.escape(str) end idx = idx + 1 - + end - for idx = 1, #str do - end return res end function Scanner.unescape(str) @@ -100,7 +98,7 @@ function Scanner.scanTokens(self) self:scanToken() end table.insert(self.tokens,Token("EOF", "", self.line)) - return self.tokens + return self.tokens end function Scanner.match(self, char) @@ -113,7 +111,7 @@ end function Scanner.string(self) while self:peek() ~= '"' and not(self:isAtEnd()) do - if self:peek() == '\\' then + if self:peek() == '\\' then self:advance() end self:advance() @@ -123,17 +121,17 @@ function Scanner.string(self) end self:advance() -- closing " - + -- trimmed opening and closing " - val = self.escape(string.sub(self.source, self.start+1, self.index-2)) - + local val = self.escape(string.sub(self.source, self.start+1, self.index-2)) + table.insert(self.tokens, Token("STR", val, self.line)) end function Scanner.scanToken(self) - char = self:advance() - + local char = self:advance() + -- print(string.format("b c:%s, i:%d", char, self.index)) if char == ' ' or char == ',' or char == '\n' or char =='\t' then if char == '\n' then @@ -142,7 +140,7 @@ function Scanner.scanToken(self) elseif char == '~' then if self:match('@') then table.insert(self.tokens, Token("~@", "", self.line)) - else + else table.insert(self.tokens, Token("~", "", self.line)) end elseif char == '[' then @@ -172,22 +170,22 @@ function Scanner.scanToken(self) while self:peek() ~= '\n' and not(self:isAtEnd()) do self:advance() end - val = string.sub(self.source, self.start + 1, self.current) + local val = string.sub(self.source, self.start + 1, self.current) table.insert(self.tokens, Token("CMT", val, self.line)) elseif char == ':' then while not (self.is_special(self:peek())) and not(self:isAtEnd()) do self:advance() end - val = "\u{29E}" .. string.sub(self.source, self.start+1, self.index-1) + local val = "\u{29E}" .. string.sub(self.source, self.start+1, self.index-1) table.insert(self.tokens, Token("SYM", val, self.line)) elseif not self.is_special(char) then while not(self.is_special(self:peek())) do self:advance() end - val = string.sub(self.source,self.start, self.index - 1) + local val = string.sub(self.source,self.start, self.index - 1) table.insert(self.tokens, Token("SYM", val, self.line)) - else - throw(string.format("Error: unknown char: %s at %d, %d", char , line , idx) ) + else + throw(string.format("Error: unknown char: %s at %d, %d", char , self.line , self.index) ) end end diff --git a/impls/lua.2/step1_read_print.lua b/impls/lua.2/step1_read_print.lua index f39916ca77..631d0f4090 100644 --- a/impls/lua.2/step1_read_print.lua +++ b/impls/lua.2/step1_read_print.lua @@ -1,4 +1,5 @@ Reader = require "reader" +Printer = require "printer" types = require "types" --local throw = types.throw @@ -24,7 +25,7 @@ function EVAL(a) end function PRINT(a) - print(Reader.stringfy_val(a, true)) + print(Printer.stringfy_val(a, true)) end function rep(str) @@ -43,7 +44,7 @@ function main() status, err = pcall( function () print(rep(line)) end) if not status then if is_instanceOf(err, Err) then - err = Reader.stringfy_val(err) + err = Printer.stringfy_val(err) end print(err) print(debug.traceback()) diff --git a/impls/lua.2/step2_eval.lua b/impls/lua.2/step2_eval.lua index 91c751f09a..cef8210b11 100644 --- a/impls/lua.2/step2_eval.lua +++ b/impls/lua.2/step2_eval.lua @@ -1,4 +1,5 @@ Reader = require "reader" +Printer = require "printer" types = require "types" local Sym = types.Sym local is_instanceOf = types.isinstanceof @@ -28,7 +29,7 @@ function EVAL(a, env) new_list = eval_ast(a, env) return new_list[1](new_list[2],new_list[3]) else - throw("'" .. Reader.stringfy_val(a[1]) .. "' should be called with 2 elements") + throw("'" .. Printer.stringfy_val(a[1]) .. "' should be called with 2 elements") end else return eval_ast(a, env) @@ -72,7 +73,7 @@ end function PRINT(a) - print(Reader.stringfy_val(a, true)) + print(Printer.stringfy_val(a, true)) end @@ -101,7 +102,7 @@ function main() status, err = pcall(function () print(rep(line)) end) if not status then if is_instanceOf(err, Err) then - err = Reader.stringfy_val(err) + err = Printer.stringfy_val(err) end print(err) print(debug.traceback()) diff --git a/impls/lua.2/step3_env.lua b/impls/lua.2/step3_env.lua index 41d9060ece..74e0c1a3c4 100644 --- a/impls/lua.2/step3_env.lua +++ b/impls/lua.2/step3_env.lua @@ -1,6 +1,7 @@ -Reader = require "reader" -types = require "types" -Env = require "env" +local Reader = require "reader" +local Printer = require "printer" +local types = require "types" +local Env = require "env" local Sym = types.Sym local is_instanceOf = types.isinstanceof local Err = types.Err @@ -113,7 +114,7 @@ end function PRINT(a) - print(Reader.stringfy_val(a, true)) + print(Printer.stringfy_val(a, true)) end @@ -140,7 +141,7 @@ function main() status, err = pcall(function () print(rep(line)) end) if not status then if is_instanceOf(err, Err) then - err = Reader.stringfy_val(err) + err = Printer.stringfy_val(err) end print(err) print(debug.traceback()) diff --git a/impls/lua.2/step4_if_fn_do.lua b/impls/lua.2/step4_if_fn_do.lua new file mode 100644 index 0000000000..43d3742904 --- /dev/null +++ b/impls/lua.2/step4_if_fn_do.lua @@ -0,0 +1,185 @@ +local Reader = require "reader" +local Printer = require "printer" +local types = require "types" +local Env = require "env" +local Sym = types.Sym +local is_instanceOf = types.isinstanceof +local Err = types.Err +local List = types.MalList +local throw = types.throw +local HashMap = types.MalHashMap +local Vector = types.MalVector +local Nil = types.Nil + +function raw_read(prompt) + io.write(prompt) + local v = io.read() + if v == nil then + io.write('\n') + end + return v +end + +function READ(str) + return Reader.read_str(str) +end + +function EVAL(a, env) + if is_instanceOf(a, List) then + if #a == 0 then + return a + end + local first_elem = a[1] + + if is_instanceOf(first_elem, Sym) and first_elem.val == "def!" then + if #a ~= 3 then + throw(string.format("def! expects 2 arguments got: %d", #a-1)) + end + if not(is_instanceOf(a[2], Sym)) then + throw("first argument to def! must be symbol") + end + local value = EVAL(a[3], env) + env:set(a[2], value) + return value + end + + if is_instanceOf(first_elem, Sym) and first_elem.val == "let*" then + if #a ~= 3 then + throw(string.format("let* expects 2 arguments got: %d", #a-1)) + end + + local let_env = Env.new(env) + if not(is_instanceOf(a[2], List) or is_instanceOf(a[2], Vector)) then + throw("Second arg to let* should be list or vector") + end + if #a[2] % 2 ~= 0 then + throw(string.format("Length ofSecond arg to let* should be even number got: %d", #a[2])) + end + + for i=1,#a[2],2 do + if not(is_instanceOf(a[2][i], Sym)) then + throw("Expected symbol in let*'s second argument") + end + local key = a[2][i] + local value = EVAL(a[2][i+1],let_env) + let_env:set(key,value) + end + return EVAL(a[3], let_env) + + end + + if is_instanceOf(first_elem, Sym) and first_elem.val == "do" then + local res + for i=2,#a do + res = EVAL(a[i], env) + end + return res + end + + if is_instanceOf(first_elem, Sym) and first_elem.val == "if" then + if not (#a == 3 or #a == 4) then + throw("if expected 2 or 3 arguments but got '" .. #a-1 .. "'.") + end + local cond = EVAL(a[2], env) + if cond ~= false and cond ~= Nil then + return EVAL(a[3], env) + else + if #a == 4 then return EVAL(a[4], env) end + return Nil + end + end + if is_instanceOf(first_elem, Sym) and first_elem.val == "fn*" then + if not (#a == 3 ) then throw("fn* expected 3 arguments but got '" .. #a-1 .. "'.") end + if false then throw("second parameter to fn* should have length 2 but got '" .. #a[2] .. "'.") end + return function (...) + local closed_over_env = Env.new(env) + local temp = List.new(table.pack(...)) + closed_over_env:bind(a[2], temp) + + return EVAL(a[3], closed_over_env) + end + end + + local new_list = eval_ast(a, env) + if type(new_list[1]) ~= "function" then + throw("First elem should be function or special form got :'" .. type(gonna_eval) .. "'.") + end + + if false then + throw("currently all builtins expects 2 arguments") + end + return new_list[1](table.unpack(new_list,2)) --fixme: varargs? + + else + return eval_ast(a, env) + end +end +function eval_ast(ast, env) + if is_instanceOf(ast, List) then + local l = List.new() + for i=1,#ast do + table.insert(l, EVAL(ast[i], env)) + end + return l + elseif is_instanceOf(ast, Vector) then + local v = Vector.new() + for i=1, #ast do + table.insert(v, EVAL(ast[i], env)) + end + return v + elseif is_instanceOf(ast, HashMap) then + local map = HashMap.new() + for k,v in pairs(ast) do + map[EVAL(k, env)] = EVAL(v, env) + end + return map + elseif is_instanceOf(ast, Sym) then + if string.byte(ast.val, 1, 1) == 202 and + string.byte(ast.val, 2, 2) == 158 then -- this magic numbers come from \u{29E} + return Sym.new(":" .. string.sub(ast.val, 3, #ast.val)) + end + return env:get(ast) + + else + return ast + end +end + + +function PRINT(a) + print(Printer.stringfy_val(a, true)) +end + + +local repl_env = Env.new(nil) + +repl_env = repl_env:set(Sym.new('+'), function (a,b) return a+b end) +repl_env:set(Sym.new('-'), function (a,b) return a-b end) +repl_env:set(Sym.new('*'), function (a,b) return a*b end) +repl_env:set(Sym.new('/'), function (a,b) return a/b end) + + +function rep(str) + return PRINT(EVAL(READ(str), repl_env)) +end + + +function main() + local line = '' + while true do + line = raw_read('user> ') + if line == nil then + break + end + status, err = pcall(function () print(rep(line)) end) + if not status then + if is_instanceOf(err, Err) then + err = Printer.stringfy_val(err) + end + print(err) + print(debug.traceback()) + end + end +end + +main() diff --git a/impls/lua.2/token.lua b/impls/lua.2/token.lua index 86f3da4627..7cf8a749b8 100644 --- a/impls/lua.2/token.lua +++ b/impls/lua.2/token.lua @@ -1,4 +1,4 @@ -Token = {} +local Token = {} Token.__index = Token setmetatable(Token, { From ee0987384f906259adc6fbf574e42e6d086deaca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=BCseyin=20Er?= Date: Sat, 14 Jan 2023 20:14:54 +0300 Subject: [PATCH 08/25] added some core functions --- impls/lua.2/core.lua | 98 ++++++++++++++++++++++++++++++++++ impls/lua.2/env.lua | 16 +++--- impls/lua.2/printer.lua | 2 +- impls/lua.2/step4_if_fn_do.lua | 12 +++-- impls/lua.2/types.lua | 30 +++++++++++ 5 files changed, 144 insertions(+), 14 deletions(-) create mode 100644 impls/lua.2/core.lua diff --git a/impls/lua.2/core.lua b/impls/lua.2/core.lua new file mode 100644 index 0000000000..1c73223b49 --- /dev/null +++ b/impls/lua.2/core.lua @@ -0,0 +1,98 @@ +local core = {} +local Printer = require "printer" +local types = require "types" +local List = types.MalList +local Sym = types.Sym +local Vector = types.MalVector +local is_instanceOf = types.isinstanceof +local is_sequence = types.is_sequence +local Nil = types.Nil +local throw = types.throw + +core[Sym.new('pr-str')] = function (...) + local res = "" + local args = table.pack(...) + for i,v in ipairs(args) do + res = res .. Printer.stringfy_val(v, true) .. " " + end + + return res:sub(1,#res-1) +end + +core[Sym.new('str')] = function (...) + local args = table.pack(...) + local res = "" + for i,v in ipairs(args) do + res = res .. Printer.stringfy_val(v, false) .. " " + end + return res:sub(1,#res-1) +end + + core[Sym.new('prn')] = function (...) + local res = "" + local args = table.pack(...) + for i,v in ipairs(args) do + res = res .. Printer.stringfy_val(v, true) .. " " + end + + print(res:sub(1,#res-1)) + return Nil +end + +core[Sym.new('println')] = function (...) + local args = table.pack(...) + local res = "" + for i,v in ipairs(args) do + res = res .. Printer.stringfy_val(v, false) .. " " + end + return res:sub(1,#res-1) +end + + + + +core[Sym.new('list')] = function (...) + local args = table.pack(...) + return List.new(args) +end + +core[Sym.new('list?')] = function (v) + if is_instanceOf(v, List) then + return true + else + return false + end +end + +core[Sym.new('empty?')] = function (v) + if is_sequence(v) then + return #v == 0 + end + throw("'empty? expects a parameter to be sequence'") +end + +core[Sym.new('count')] = function (v) + if v == Nil then + return 0 + end + if is_sequence(v) then + return #v + end + throw("'empty expects parameter to be sequence or nil'") +end + +core[Sym.new('<')] = function (a, b) return a < b end +core[Sym.new('>')] = function (a, b) return a > b end +core[Sym.new('<=')] = function (a, b) return a <= b end +core[Sym.new('>=')] = function (a, b) return a >= b end + +core[Sym.new('+')] = function (a, b) return a + b end +core[Sym.new('-')] = function (a, b) return a - b end +core[Sym.new('*')] = function (a, b) return a * b end +core[Sym.new('/')] = function (a, b) return a / b end + + +core[Sym.new('=')] = types.is_equal + + +return core diff --git a/impls/lua.2/env.lua b/impls/lua.2/env.lua index f56e80a84a..2e09787d3e 100644 --- a/impls/lua.2/env.lua +++ b/impls/lua.2/env.lua @@ -4,6 +4,7 @@ local throw = types.throw local Sym = types.Sym local List = types.MalList local is_instanceOf = types.isinstanceof +local is_sequence = types.is_sequence local Env = {} Env.__index = Env @@ -17,31 +18,30 @@ end function Env:bind(binds, exprs) - if not(is_instanceOf(binds, List)) then throw("binds should be list") end - if not(is_instanceOf(exprs, List)) then throw("exprs should be list") end + if not(is_sequence(binds)) then throw("binds should be sequence") end + if not(is_sequence(exprs)) then throw("exprs should be sequence") end if #binds ~= #exprs then throw("number of bindings and expressions should be equal") end for i, b in ipairs(binds) do if not(is_instanceOf(b, Sym)) then - throw("%d/ in the binds should be Symbol ",i, #binds) + throw(string.format("%d/%d in the binds should be Symbol ", i, #binds)) end - print("binding " .. Printer.stringfy_val(b) .. " to " .. Printer.stringfy_val(exprs[i])) - self[Printer.stringfy_val(b)] = exprs[i] + self[b.val] = exprs[i] end end function Env:set(key, val) assert(is_instanceOf(key, Sym), "key should be symbol") - self[Printer.stringfy_val(key)] = val + self[key.val] = val return self end function Env:find(key) assert(is_instanceOf(key, Sym), "key should be symbol") - if self[Printer.stringfy_val(key)] then + if self[key.val] then return self end if self.outer ~= nil then @@ -54,7 +54,7 @@ function Env:get(key) assert(is_instanceOf(key, Sym), "key should be symbol") local env = self:find(key) if env then - return env[Printer.stringfy_val(key)] + return env[key.val] end throw(string.format("%s not found in any environments.", Printer.stringfy_val(key))) end diff --git a/impls/lua.2/printer.lua b/impls/lua.2/printer.lua index 475daf89f3..3c776181cf 100644 --- a/impls/lua.2/printer.lua +++ b/impls/lua.2/printer.lua @@ -61,7 +61,7 @@ function Printer.stringfy_val(val, readably) elseif type(val) == "number" then res = tostring(val) elseif val == Nil then - res = "Nil" + res = "nil" elseif type(val) == "boolean" then res = tostring(val) elseif type(val) == "function" then diff --git a/impls/lua.2/step4_if_fn_do.lua b/impls/lua.2/step4_if_fn_do.lua index 43d3742904..37930344be 100644 --- a/impls/lua.2/step4_if_fn_do.lua +++ b/impls/lua.2/step4_if_fn_do.lua @@ -10,6 +10,7 @@ local throw = types.throw local HashMap = types.MalHashMap local Vector = types.MalVector local Nil = types.Nil +local core = require "core" function raw_read(prompt) io.write(prompt) @@ -153,10 +154,9 @@ end local repl_env = Env.new(nil) -repl_env = repl_env:set(Sym.new('+'), function (a,b) return a+b end) -repl_env:set(Sym.new('-'), function (a,b) return a-b end) -repl_env:set(Sym.new('*'), function (a,b) return a*b end) -repl_env:set(Sym.new('/'), function (a,b) return a/b end) +for k,v in pairs(core) do + repl_env:set(k,v) +end function rep(str) @@ -165,13 +165,15 @@ end function main() + rep("(def! not (fn* (a) (if a false true)))") + local line = '' while true do line = raw_read('user> ') if line == nil then break end - status, err = pcall(function () print(rep(line)) end) + local status, err = pcall(function () rep(line) end) if not status then if is_instanceOf(err, Err) then err = Printer.stringfy_val(err) diff --git a/impls/lua.2/types.lua b/impls/lua.2/types.lua index 675c50a85b..e4f5470869 100644 --- a/impls/lua.2/types.lua +++ b/impls/lua.2/types.lua @@ -77,6 +77,36 @@ function M.MalFunction.new(fn, ast, env, params) return setmetatable(self, M.MalFunction) end +function M.is_sequence(a) + return M.isinstanceof(a, M.MalList) or M.isinstanceof(a, M.MalVector) +end + + +function M.is_equal(a, b) + if M.isinstanceof(a, M.Sym) and M.isinstanceof(b, M.sym) then + return a.val == b.val + elseif M.is_sequence(a) and M.is_sequence(b) then + if #a ~= #b then return false end + for i,v in ipairs(a) do + if not M.is_equal(v, b[i]) then + return false + end + end + return true + elseif M.isinstanceof(a, M.HashMap) and M.isinstanceof(b, M.HashMap) then + for k,v in pairs(a) do + if not ( M.is_equal(a[k],b[k])) then + return false + end + end + return true + + else + return a == b + end + +end + function M.isinstanceof(obj, super) From d561877c879fe667d4f0c50bf36fb9422909af56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=BCseyin=20Er?= Date: Sun, 15 Jan 2023 15:48:19 +0300 Subject: [PATCH 09/25] step4 all test passes --- impls/lua.2/core.lua | 9 +++++---- impls/lua.2/env.lua | 25 +++++++++++++++++-------- impls/lua.2/step4_if_fn_do.lua | 5 +++-- impls/lua.2/types.lua | 2 +- 4 files changed, 26 insertions(+), 15 deletions(-) diff --git a/impls/lua.2/core.lua b/impls/lua.2/core.lua index 1c73223b49..aa4c942b2e 100644 --- a/impls/lua.2/core.lua +++ b/impls/lua.2/core.lua @@ -23,9 +23,9 @@ core[Sym.new('str')] = function (...) local args = table.pack(...) local res = "" for i,v in ipairs(args) do - res = res .. Printer.stringfy_val(v, false) .. " " + res = res .. Printer.stringfy_val(v, false) end - return res:sub(1,#res-1) + return res end core[Sym.new('prn')] = function (...) @@ -45,7 +45,8 @@ core[Sym.new('println')] = function (...) for i,v in ipairs(args) do res = res .. Printer.stringfy_val(v, false) .. " " end - return res:sub(1,#res-1) + print(res:sub(1,#res-1)) + return Nil end @@ -78,7 +79,7 @@ core[Sym.new('count')] = function (v) if is_sequence(v) then return #v end - throw("'empty expects parameter to be sequence or nil'") + throw("'count expects parameter to be sequence or nil'") end core[Sym.new('<')] = function (a, b) return a < b end diff --git a/impls/lua.2/env.lua b/impls/lua.2/env.lua index 2e09787d3e..9148670a83 100644 --- a/impls/lua.2/env.lua +++ b/impls/lua.2/env.lua @@ -10,38 +10,47 @@ local Env = {} Env.__index = Env function Env.new(outer) + local data = {} local self = {} self.outer = outer + self.data = data setmetatable(self, Env) return self end - function Env:bind(binds, exprs) if not(is_sequence(binds)) then throw("binds should be sequence") end if not(is_sequence(exprs)) then throw("exprs should be sequence") end - if #binds ~= #exprs then - throw("number of bindings and expressions should be equal") - end + + for i, b in ipairs(binds) do if not(is_instanceOf(b, Sym)) then throw(string.format("%d/%d in the binds should be Symbol ", i, #binds)) end - self[b.val] = exprs[i] + if b.val ~= '&' then + self.data[b.val] = exprs[i] + print(b.val .. ":" .. Printer.stringfy_val(exprs[i]) ) + else + if i == #binds or not(is_instanceOf(binds[i+1],Sym)) then + throw("Symbol '&' should be followed by an another symbol") + end + self.data[binds[i+1].val] = List.new(table.pack(table.unpack(exprs,i))) + break + end end end function Env:set(key, val) assert(is_instanceOf(key, Sym), "key should be symbol") - self[key.val] = val + self.data[key.val] = val return self end function Env:find(key) assert(is_instanceOf(key, Sym), "key should be symbol") - if self[key.val] then + if self.data[key.val] ~= nil then return self end if self.outer ~= nil then @@ -54,7 +63,7 @@ function Env:get(key) assert(is_instanceOf(key, Sym), "key should be symbol") local env = self:find(key) if env then - return env[key.val] + return env.data[key.val] end throw(string.format("%s not found in any environments.", Printer.stringfy_val(key))) end diff --git a/impls/lua.2/step4_if_fn_do.lua b/impls/lua.2/step4_if_fn_do.lua index 37930344be..581d372d1f 100644 --- a/impls/lua.2/step4_if_fn_do.lua +++ b/impls/lua.2/step4_if_fn_do.lua @@ -94,8 +94,9 @@ function EVAL(a, env) if false then throw("second parameter to fn* should have length 2 but got '" .. #a[2] .. "'.") end return function (...) local closed_over_env = Env.new(env) - local temp = List.new(table.pack(...)) - closed_over_env:bind(a[2], temp) + local exprs = List.new(table.pack(...)) + local binds = a[2] + closed_over_env:bind(binds, exprs) return EVAL(a[3], closed_over_env) end diff --git a/impls/lua.2/types.lua b/impls/lua.2/types.lua index e4f5470869..7f5664d0bf 100644 --- a/impls/lua.2/types.lua +++ b/impls/lua.2/types.lua @@ -83,7 +83,7 @@ end function M.is_equal(a, b) - if M.isinstanceof(a, M.Sym) and M.isinstanceof(b, M.sym) then + if M.isinstanceof(a, M.Sym) and M.isinstanceof(b, M.Sym) then return a.val == b.val elseif M.is_sequence(a) and M.is_sequence(b) then if #a ~= #b then return false end From 73ab9dfc76f209889ce909128b8a90a9635a1cd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=BCseyin=20Er?= Date: Sat, 28 Jan 2023 15:04:33 +0300 Subject: [PATCH 10/25] step5 all test passes except step1 keywords --- impls/lua.2/env.lua | 2 +- impls/lua.2/printer.lua | 2 +- impls/lua.2/step5_tco.lua | 198 ++++++++++++++++++++++++++++++++++++++ impls/lua.2/types.lua | 4 + 4 files changed, 204 insertions(+), 2 deletions(-) create mode 100644 impls/lua.2/step5_tco.lua diff --git a/impls/lua.2/env.lua b/impls/lua.2/env.lua index 9148670a83..414fa5ae81 100644 --- a/impls/lua.2/env.lua +++ b/impls/lua.2/env.lua @@ -30,7 +30,7 @@ function Env:bind(binds, exprs) end if b.val ~= '&' then self.data[b.val] = exprs[i] - print(b.val .. ":" .. Printer.stringfy_val(exprs[i]) ) + --print(b.val .. ":" .. Printer.stringfy_val(exprs[i]) ) else if i == #binds or not(is_instanceOf(binds[i+1],Sym)) then throw("Symbol '&' should be followed by an another symbol") diff --git a/impls/lua.2/printer.lua b/impls/lua.2/printer.lua index 3c776181cf..79186043a3 100644 --- a/impls/lua.2/printer.lua +++ b/impls/lua.2/printer.lua @@ -50,7 +50,7 @@ function Printer.stringfy_val(val, readably) elseif is_instanceOf(val, Err) then return "Error: " .. Scanner.unescape(val.val) elseif is_instanceOf(val, Function) then - res = "(fn* " -- .. Printer.stringfy_val(val.params) .. " " Printer.stringfy_val(val.ast) ..")" + res = "(fn* " .. Printer.stringfy_val(val.params) .. "-->" .. Printer.stringfy_val(val.ast) ..")" elseif type(val) == "string" then if readably then diff --git a/impls/lua.2/step5_tco.lua b/impls/lua.2/step5_tco.lua new file mode 100644 index 0000000000..243fdaf2a2 --- /dev/null +++ b/impls/lua.2/step5_tco.lua @@ -0,0 +1,198 @@ +local Reader = require "reader" +local Printer = require "printer" +local types = require "types" +local Env = require "env" +local Sym = types.Sym +local is_instanceOf = types.isinstanceof +local Err = types.Err +local List = types.MalList +local throw = types.throw +local HashMap = types.MalHashMap +local Vector = types.MalVector +local Nil = types.Nil +local core = require "core" + +function raw_read(prompt) + io.write(prompt) + local v = io.read() + if v == nil then + io.write('\n') + end + return v +end + +function READ(str) + return Reader.read_str(str) +end + +function EVAL(a, env) + while true do + if not(is_instanceOf(a, List)) then + return eval_ast(a, env) + end + + if #a == 0 then + return a + end + local first_elem = a[1] + local first_sym = is_instanceOf(first_elem, Sym) and first_elem.val or "" + + if first_sym == "def!" then + if #a ~= 3 then + throw(string.format("def! expects 2 arguments got: %d", #a-1)) + end + if not(is_instanceOf(a[2], Sym)) then + throw("first argument to def! must be symbol") + end + local value = EVAL(a[3], env) + env:set(a[2], value) + return value + + elseif first_sym == "let*" then + if #a ~= 3 then + throw(string.format("let* expects 2 arguments got: %d", #a-1)) + end + + local let_env = Env.new(env) + if not(is_instanceOf(a[2], List) or is_instanceOf(a[2], Vector)) then + throw("Second arg to let* should be list or vector") + end + if #a[2] % 2 ~= 0 then + throw(string.format("Length ofSecond arg to let* should be even number got: %d", #a[2])) + end + + for i=1,#a[2],2 do + if not(is_instanceOf(a[2][i], Sym)) then + throw("Expected symbol in let*'s second argument") + end + local key = a[2][i] + local value = EVAL(a[2][i+1],let_env) + let_env:set(key,value) + end + a = a[3] + env = let_env + + + elseif first_sym == "do" then + for i=2,#a-1 do + eval_ast(a[i], env) + end + a = a[#a] --tco + + + elseif first_sym == "if" then + if not (#a == 3 or #a == 4) then + throw("if expected 2 or 3 arguments but got '" .. #a-1 .. "'.") + end + local cond = EVAL(a[2], env) + if cond ~= false and cond ~= Nil then + a = a[3] + else + if #a == 4 then + a = a[4] + else + return Nil + end + end + elseif first_sym == "fn*" then + if not (#a == 3 ) then throw("fn* expected 3 arguments but got '" .. #a-1 .. "'.") end + if false then throw("second parameter to fn* should have length 2 but got '" .. #a[2] .. "'.") end + return types.MalFunction.new(function (...) + local closed_over_env = Env.new(env) + local exprs = List.new(table.pack(...)) + local binds = a[2] + closed_over_env:bind(binds, exprs) + + return EVAL(a[3], closed_over_env) + end, a[3], env, a[2]) + + else + + local args = eval_ast(a, env) + local f = table.remove(args,1) + if types.is_malfunc(f) then + a = f.ast + env = Env.new(f.env) + env:bind(f.params, args) + + else + if type(f) ~= "function" then + throw("First elem should be function or special form got :'" .. type(f) .. "'.") + end + + return f(table.unpack(args)) --fixme: varargs? + end + end + +end +end +function eval_ast(ast, env) + if is_instanceOf(ast, List) then + local l = List.new() + for i=1,#ast do + table.insert(l, EVAL(ast[i], env)) + end + return l + elseif is_instanceOf(ast, Vector) then + local v = Vector.new() + for i=1, #ast do + table.insert(v, EVAL(ast[i], env)) + end + return v + elseif is_instanceOf(ast, HashMap) then + local map = HashMap.new() + for k,v in pairs(ast) do + map[EVAL(k, env)] = EVAL(v, env) + end + return map + elseif is_instanceOf(ast, Sym) then + if string.byte(ast.val, 1, 1) == 202 and + string.byte(ast.val, 2, 2) == 158 then -- this magic numbers come from \u{29E} + return Sym.new(":" .. string.sub(ast.val, 3, #ast.val)) + end + return env:get(ast) + + else + return ast + end +end + + +function PRINT(a) + print(Printer.stringfy_val(a, true)) +end + + +local repl_env = Env.new(nil) + +for k,v in pairs(core) do + repl_env:set(k,v) +end + + +function rep(str) + return PRINT(EVAL(READ(str), repl_env)) +end + + +function main() + rep("(def! not (fn* (a) (if a false true)))") + + local line = '' + while true do + line = raw_read('user> ') + if line == nil then + break + end + local status, err = pcall(function () rep(line) end) + if not status then + if is_instanceOf(err, Err) then + err = Printer.stringfy_val(err) + end + print(err) + print(debug.traceback()) + end + end +end + +main() diff --git a/impls/lua.2/types.lua b/impls/lua.2/types.lua index 7f5664d0bf..3ff7dc361b 100644 --- a/impls/lua.2/types.lua +++ b/impls/lua.2/types.lua @@ -77,6 +77,10 @@ function M.MalFunction.new(fn, ast, env, params) return setmetatable(self, M.MalFunction) end +function M.is_malfunc(a) + return M.isinstanceof(a, M.MalFunction) +end + function M.is_sequence(a) return M.isinstanceof(a, M.MalList) or M.isinstanceof(a, M.MalVector) end From 51a12eb00283c7326b4a7aa1fc8dbb2f4115ef8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=BCseyin=20Er?= Date: Sat, 4 Feb 2023 21:42:07 +0300 Subject: [PATCH 11/25] step6 w/o *ARGV* --- impls/lua.2/core.lua | 33 ++++++ impls/lua.2/printer.lua | 3 + impls/lua.2/scanner.lua | 4 +- impls/lua.2/step6_file.lua | 209 +++++++++++++++++++++++++++++++++++++ impls/lua.2/types.lua | 14 ++- 5 files changed, 259 insertions(+), 4 deletions(-) create mode 100644 impls/lua.2/step6_file.lua diff --git a/impls/lua.2/core.lua b/impls/lua.2/core.lua index aa4c942b2e..76d79ea897 100644 --- a/impls/lua.2/core.lua +++ b/impls/lua.2/core.lua @@ -6,8 +6,11 @@ local Sym = types.Sym local Vector = types.MalVector local is_instanceOf = types.isinstanceof local is_sequence = types.is_sequence +local Function = types.MalFunction local Nil = types.Nil local throw = types.throw +local Reader = require "reader" +local Atom = types.Atom core[Sym.new('pr-str')] = function (...) local res = "" @@ -95,5 +98,35 @@ core[Sym.new('/')] = function (a, b) return a / b end core[Sym.new('=')] = types.is_equal +core[Sym.new('read-string')] = Reader.read_str + +core[Sym.new('slurp')] = function (filename) + local f = io.open(filename) + if f == nil then + throw(string.format("file '%s' cannot be opened", filename)) + end + local res = f:read('a') + f:close() + return res +end + +core[Sym.new('atom')] = function (v) return Atom.new(v) end +core[Sym.new('atom?')] = function (v) return types.is_atom(v) end +core[Sym.new('deref')] = function (v) return v.val end + +core[Sym.new('reset!')] = function (v, malval) + v.val = malval + return v.val end +core[Sym.new('swap!')] = function (v, f, ...) + if not(is_instanceOf(f, Function) or type(f) == "function") then + throw(string.format("second argument to swap! should be function")) + end + if is_instanceOf(f, Function) then + f = f.fn + end + + v.val = f(v.val, ...) + return v.val +end return core diff --git a/impls/lua.2/printer.lua b/impls/lua.2/printer.lua index 79186043a3..e51dbdd554 100644 --- a/impls/lua.2/printer.lua +++ b/impls/lua.2/printer.lua @@ -11,6 +11,7 @@ local Sym = types.Sym local is_instanceOf = types.isinstanceof local Err = types.Err local Function = types.MalFunction +local Atom = types.Atom @@ -51,6 +52,8 @@ function Printer.stringfy_val(val, readably) return "Error: " .. Scanner.unescape(val.val) elseif is_instanceOf(val, Function) then res = "(fn* " .. Printer.stringfy_val(val.params) .. "-->" .. Printer.stringfy_val(val.ast) ..")" + elseif is_instanceOf(val, Atom) then + res = "(atom " .. Printer.stringfy_val(val.val) .. ")" elseif type(val) == "string" then if readably then diff --git a/impls/lua.2/scanner.lua b/impls/lua.2/scanner.lua index 4356a5c2e5..9b06c2eb30 100644 --- a/impls/lua.2/scanner.lua +++ b/impls/lua.2/scanner.lua @@ -170,8 +170,8 @@ function Scanner.scanToken(self) while self:peek() ~= '\n' and not(self:isAtEnd()) do self:advance() end - local val = string.sub(self.source, self.start + 1, self.current) - table.insert(self.tokens, Token("CMT", val, self.line)) + --local val = string.sub(self.source, self.start + 1, self.current) + --table.insert(self.tokens, Token("CMT", val, self.line)) elseif char == ':' then while not (self.is_special(self:peek())) and not(self:isAtEnd()) do self:advance() diff --git a/impls/lua.2/step6_file.lua b/impls/lua.2/step6_file.lua new file mode 100644 index 0000000000..fe2a57d195 --- /dev/null +++ b/impls/lua.2/step6_file.lua @@ -0,0 +1,209 @@ +local Reader = require "reader" +local Printer = require "printer" +local types = require "types" +local Env = require "env" +local Sym = types.Sym +local is_instanceOf = types.isinstanceof +local Err = types.Err +local List = types.MalList +local throw = types.throw +local HashMap = types.MalHashMap +local Vector = types.MalVector +local Nil = types.Nil +local core = require "core" + +function raw_read(prompt) + io.write(prompt) + local v = io.read() + if v == nil then + io.write('\n') + end + return v +end + +function READ(str) + return Reader.read_str(str) +end + +function EVAL(a, env) + while true do + if not(is_instanceOf(a, List)) then + return eval_ast(a, env) + end + + if #a == 0 then + return a + end + local first_elem = a[1] + local first_sym = is_instanceOf(first_elem, Sym) and first_elem.val or "" + + if first_sym == "def!" then + if #a ~= 3 then + throw(string.format("def! expects 2 arguments got: %d", #a-1)) + end + if not(is_instanceOf(a[2], Sym)) then + throw("first argument to def! must be symbol") + end + local value = EVAL(a[3], env) + env:set(a[2], value) + return value + + elseif first_sym == "let*" then + if #a ~= 3 then + throw(string.format("let* expects 2 arguments got: %d", #a-1)) + end + + local let_env = Env.new(env) + if not(is_instanceOf(a[2], List) or is_instanceOf(a[2], Vector)) then + throw("Second arg to let* should be list or vector") + end + if #a[2] % 2 ~= 0 then + throw(string.format("Length ofSecond arg to let* should be even number got: %d", #a[2])) + end + + for i=1,#a[2],2 do + if not(is_instanceOf(a[2][i], Sym)) then + throw("Expected symbol in let*'s second argument") + end + local key = a[2][i] + local value = EVAL(a[2][i+1],let_env) + let_env:set(key,value) + end + a = a[3] + env = let_env + + + elseif first_sym == "do" then + for i=2,#a-1 do + EVAL(a[i], env) + end + a = a[#a] --tco + + + elseif first_sym == "if" then + if not (#a == 3 or #a == 4) then + throw("if expected 2 or 3 arguments but got '" .. #a-1 .. "'.") + end + local cond = EVAL(a[2], env) + if cond ~= false and cond ~= Nil then + a = a[3] + else + if #a == 4 then + a = a[4] + else + return Nil + end + end + elseif first_sym == "fn*" then + if (#a) ~= 3 then throw("fn* expected 2 arguments but got '" .. #a-1 .. "'.") end + if false then throw("second parameter to fn* should have length 2 but got '" .. #a[2] .. "'.") end + return types.MalFunction.new(function (...) + local closed_over_env = Env.new(env) + local exprs = List.new(table.pack(...)) + local binds = a[2] + closed_over_env:bind(binds, exprs) + + return EVAL(a[3], closed_over_env) + end, a[3], env, a[2]) + + else + + local args = eval_ast(a, env) + local f = table.remove(args,1) + if types.is_malfunc(f) then + a = f.ast + env = Env.new(f.env) + env:bind(f.params, args) + + else + if type(f) ~= "function" then + throw("First elem should be function or special form got :'" .. type(f) .. "'.") + end + + return f(table.unpack(args)) --fixme: varargs? + end + end + +end +end +function eval_ast(ast, env) + if is_instanceOf(ast, List) then + local l = List.new() + for i=1,#ast do + table.insert(l, EVAL(ast[i], env)) + end + return l + elseif is_instanceOf(ast, Vector) then + local v = Vector.new() + for i=1, #ast do + table.insert(v, EVAL(ast[i], env)) + end + return v + elseif is_instanceOf(ast, HashMap) then + local map = HashMap.new() + for k,v in pairs(ast) do + map[EVAL(k, env)] = EVAL(v, env) + end + return map + elseif is_instanceOf(ast, Sym) then + if string.byte(ast.val, 1, 1) == 202 and + string.byte(ast.val, 2, 2) == 158 then -- this magic numbers come from \u{29E} + return Sym.new(":" .. string.sub(ast.val, 3, #ast.val)) + end + return env:get(ast) + + else + return ast + end +end + + +function PRINT(a) + print(Printer.stringfy_val(a, true)) +end + + +local repl_env = Env.new(nil) + +for k,v in pairs(core) do + repl_env:set(k,v) +end + +repl_env:set(Sym.new('eval'), function (ast) + return EVAL(ast, repl_env) +end) + + +function rep(str) + return PRINT(EVAL(READ(str), repl_env)) +end + + +function main() + rep("(def! not (fn* (a) (if a false true)))") + rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \"(slurp f) \"\nnil)\")))))") + + if #arg > 0 then + --repl_env:set(Sym.new("*ARGV*"), List.new({1,2,3})) + rep("(load-file \"" .. arg[1] .. "\")") + os.exit(0) + end + + local line = '' + while true do + line = raw_read('user> ') + if line == nil then + break + end + local status, err = pcall(function () rep(line) end) + if not status then + if is_instanceOf(err, Err) then + err = Printer.stringfy_val(err) + end + print(err) + print(debug.traceback()) + end + end +end + +main() diff --git a/impls/lua.2/types.lua b/impls/lua.2/types.lua index 3ff7dc361b..34cd6bf765 100644 --- a/impls/lua.2/types.lua +++ b/impls/lua.2/types.lua @@ -85,6 +85,17 @@ function M.is_sequence(a) return M.isinstanceof(a, M.MalList) or M.isinstanceof(a, M.MalVector) end +M.Atom = {} +M.Atom.__index = M.Atom +function M.Atom.new(val) + local self = {val = val} + return setmetatable(self, M.Atom) +end + +function M.is_atom(v) + return M.isinstanceof(v, M.Atom) +end + function M.is_equal(a, b) if M.isinstanceof(a, M.Sym) and M.isinstanceof(b, M.Sym) then @@ -94,8 +105,7 @@ function M.is_equal(a, b) for i,v in ipairs(a) do if not M.is_equal(v, b[i]) then return false - end - end + end end return true elseif M.isinstanceof(a, M.HashMap) and M.isinstanceof(b, M.HashMap) then for k,v in pairs(a) do From 6b5b72e1fbe618f74faa4e6811fc40948048f292 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=BCseyin=20Er?= Date: Sun, 5 Feb 2023 10:28:16 +0300 Subject: [PATCH 12/25] step6 w/ *ARGV* --- impls/lua.2/step6_file.lua | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/impls/lua.2/step6_file.lua b/impls/lua.2/step6_file.lua index fe2a57d195..d32b7f852c 100644 --- a/impls/lua.2/step6_file.lua +++ b/impls/lua.2/step6_file.lua @@ -159,7 +159,7 @@ end function PRINT(a) - print(Printer.stringfy_val(a, true)) + return Printer.stringfy_val(a, true) end @@ -179,13 +179,14 @@ function rep(str) end + function main() rep("(def! not (fn* (a) (if a false true)))") rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \"(slurp f) \"\nnil)\")))))") - + repl_env:set(Sym.new("*ARGV*"), List.new(table.pack(table.unpack(arg,2)))) if #arg > 0 then - --repl_env:set(Sym.new("*ARGV*"), List.new({1,2,3})) - rep("(load-file \"" .. arg[1] .. "\")") + local file_to_run = table.remove(arg,1) + rep("(load-file \"" .. file_to_run .. "\")") os.exit(0) end @@ -195,7 +196,7 @@ function main() if line == nil then break end - local status, err = pcall(function () rep(line) end) + local status, err = pcall(function () print(rep(line)) end) if not status then if is_instanceOf(err, Err) then err = Printer.stringfy_val(err) From 9c2a1407b5c6d3553cb4837dc30c6426d53ed7b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=BCseyin=20Er?= Date: Fri, 17 Feb 2023 18:41:54 +0300 Subject: [PATCH 13/25] step7 wip --- impls/lua.2/core.lua | 41 +++++- impls/lua.2/step7_quote.lua | 254 ++++++++++++++++++++++++++++++++++++ 2 files changed, 292 insertions(+), 3 deletions(-) create mode 100644 impls/lua.2/step7_quote.lua diff --git a/impls/lua.2/core.lua b/impls/lua.2/core.lua index 76d79ea897..06752b1b27 100644 --- a/impls/lua.2/core.lua +++ b/impls/lua.2/core.lua @@ -55,9 +55,9 @@ end -core[Sym.new('list')] = function (...) - local args = table.pack(...) - return List.new(args) +core[Sym.new('list')] = function (...) + local args = table.pack(...) + return List.new(args) end core[Sym.new('list?')] = function (v) @@ -68,6 +68,12 @@ core[Sym.new('list?')] = function (v) end end +core[Sym.new('vec')] = function (...) + local args = table.pack(...) + return Vector.new(args) +end + + core[Sym.new('empty?')] = function (v) if is_sequence(v) then return #v == 0 @@ -129,4 +135,33 @@ core[Sym.new('swap!')] = function (v, f, ...) return v.val end +core[Sym.new('cons')] = function (first, second, ...) + if ... ~= nil then throw("cons expect expects 2 args got: " .. 2 + #table.pack(...)) end + if not(is_sequence(second, List)) then + throw("second argument to cons should be Sequence") + end + local res = List.new({first, table.unpack(second)}) + + return res + +end + +core[Sym.new('concat')] = function (...) + local args = table.pack(...) + local tmp = {} + for i, v in ipairs(args) do + if not(is_instanceOf(v, List)) then + throw("argument to concat should be List at index:" .. i) + end + for ii, vv in ipairs(v) do + table.insert(tmp, vv) + end + end + local res = List.new(tmp) + + return res + +end + + return core diff --git a/impls/lua.2/step7_quote.lua b/impls/lua.2/step7_quote.lua new file mode 100644 index 0000000000..c4bab1ca73 --- /dev/null +++ b/impls/lua.2/step7_quote.lua @@ -0,0 +1,254 @@ +local Reader = require "reader" +local Printer = require "printer" +local types = require "types" +local Env = require "env" +local Sym = types.Sym +local is_instanceOf = types.isinstanceof +local Err = types.Err +local List = types.MalList +local throw = types.throw +local HashMap = types.MalHashMap +local Vector = types.MalVector +local Nil = types.Nil +local core = require "core" + +function raw_read(prompt) + io.write(prompt) + local v = io.read() + if v == nil then + io.write('\n') + end + return v +end + +function READ(str) + return Reader.read_str(str) +end + +function starts_with(a, v) +return #a > 0 and is_instanceOf(a[1],Sym) and + a[1].val == v +end + +function quasiquote(a) + + if is_instanceOf(a,List) and starts_with(a, "unquote") then + if #a-1 ~= 1 then throw("unquote expected 1 argument bot got : " .. #a) end + print("unquote hit") + return a[2] + + elseif is_instanceOf(a, List) then + local res = List.new({}) + for i=#a,1,-1 do + local elt = a[i] + if is_instanceOf(elt, List) and starts_with(elt, "splice-unquote") then + + if #elt ~= 2 then throw("splice-unquote expected 1 argument bot got : " .. #elt) end + + res = List.new({Sym.new( "concat"), elt[2], res}) + else + res = List.new({Sym.new( "cons"), quasiquote(elt), res}) + end + end + return res + elseif is_instanceOf(a,Map) or is_instanceOf(a,Sym) then + return List.new({Sym.new('quote'), a}) + + else + return a + + end +end + +function EVAL(a, env) + while true do + if not(is_instanceOf(a, List)) then + return eval_ast(a, env) + end + + if #a == 0 then + return a + end + local first_elem = a[1] + local first_sym = is_instanceOf(first_elem, Sym) and first_elem.val or "" + + if first_sym == "def!" then + if #a ~= 3 then + throw(string.format("def! expects 2 arguments got: %d", #a-1)) + end + if not(is_instanceOf(a[2], Sym)) then + throw("first argument to def! must be symbol") + end + local value = EVAL(a[3], env) + env:set(a[2], value) + return value + + elseif first_sym == "let*" then + if #a ~= 3 then + throw(string.format("let* expects 2 arguments got: %d", #a-1)) + end + + local let_env = Env.new(env) + if not(is_instanceOf(a[2], List) or is_instanceOf(a[2], Vector)) then + throw("Second arg to let* should be list or vector") + end + if #a[2] % 2 ~= 0 then + throw(string.format("Length ofSecond arg to let* should be even number got: %d", #a[2])) + end + + for i=1,#a[2],2 do + if not(is_instanceOf(a[2][i], Sym)) then + throw("Expected symbol in let*'s second argument") + end + local key = a[2][i] + local value = EVAL(a[2][i+1],let_env) + let_env:set(key,value) + end + a = a[3] + env = let_env + + + elseif first_sym == "do" then + for i=2,#a-1 do + EVAL(a[i], env) + end + a = a[#a] --tco + + + elseif first_sym == "if" then + if not (#a == 3 or #a == 4) then + throw("if expected 2 or 3 arguments but got '" .. #a-1 .. "'.") + end + local cond = EVAL(a[2], env) + if cond ~= false and cond ~= Nil then + a = a[3] + else + if #a == 4 then + a = a[4] + else + return Nil + end + end + elseif first_sym == "fn*" then + if (#a) ~= 3 then throw("fn* expected 2 arguments but got '" .. #a-1 .. "'.") end + if false then throw("second parameter to fn* should have length 2 but got '" .. #a[2] .. "'.") end + return types.MalFunction.new(function (...) + local closed_over_env = Env.new(env) + local exprs = List.new(table.pack(...)) + local binds = a[2] + closed_over_env:bind(binds, exprs) + + return EVAL(a[3], closed_over_env) + end, a[3], env, a[2]) + + elseif first_sym == "quote" then + if #a-1 ~= 1 then throw("quote expects 1 argument got '" .. #a-1 .. "'.") end + return a[2] + elseif first_sym == "quasiquote" then + if #a-1 ~= 1 then throw("quote expects 1 argument got '" .. #a-1 .. "'.") end + a = quasiquote(a[2]) + else + + local args = eval_ast(a, env) + local f = table.remove(args,1) + if types.is_malfunc(f) then + a = f.ast + env = Env.new(f.env) + env:bind(f.params, args) + + else + if type(f) ~= "function" then + throw("First elem should be function or special form got :'" .. type(f) .. "'.") + end + + return f(table.unpack(args)) --fixme: varargs? + end + end + +end +end + + +-- TODO: learn why we return map +function eval_ast(ast, env) + if is_instanceOf(ast, List) then + local l = List.new() + for i=1,#ast do + table.insert(l, EVAL(ast[i], env)) + end + return l + elseif is_instanceOf(ast, Vector) then + local v = Vector.new() + for i=1, #ast do + table.insert(v, EVAL(ast[i], env)) + end + return v + elseif is_instanceOf(ast, HashMap) then + local map = HashMap.new() + for k,v in pairs(ast) do + map[EVAL(k, env)] = EVAL(v, env) + end + return map + elseif is_instanceOf(ast, Sym) then + if string.byte(ast.val, 1, 1) == 202 and + string.byte(ast.val, 2, 2) == 158 then -- this magic numbers come from \u{29E} + return Sym.new(":" .. string.sub(ast.val, 3, #ast.val)) + end + return env:get(ast) + + else + return ast + end +end + + +function PRINT(a) + return Printer.stringfy_val(a, true) +end + + +local repl_env = Env.new(nil) + +for k,v in pairs(core) do + repl_env:set(k,v) +end + +repl_env:set(Sym.new('eval'), function (ast) + return EVAL(ast, repl_env) +end) + + +function rep(str) + return PRINT(EVAL(READ(str), repl_env)) +end + + + +function main() + rep("(def! not (fn* (a) (if a false true)))") + rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \"(slurp f) \"\nnil)\")))))") + repl_env:set(Sym.new("*ARGV*"), List.new(table.pack(table.unpack(arg,2)))) + if #arg > 0 then + local file_to_run = table.remove(arg,1) + rep("(load-file \"" .. file_to_run .. "\")") + os.exit(0) + end + + local line = '' + while true do + line = raw_read('user> ') + if line == nil then + break + end + local status, err = pcall(function () print(rep(line)) end) + if not status then + if is_instanceOf(err, Err) then + err = Printer.stringfy_val(err) + end + print(err) + print(debug.traceback()) + end + end +end + +main() From d49c19a71dce25e0dbd787ac940de5f303ae1fe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=BCseyin=20Er?= Date: Sun, 19 Feb 2023 08:40:26 +0300 Subject: [PATCH 14/25] step7 w/o quasiquoteexpand --- impls/lua.2/core.lua | 51 ++++++++++++++++++++----------------- impls/lua.2/run | 2 +- impls/lua.2/step7_quote.lua | 47 ++++++++++++++++++++-------------- 3 files changed, 57 insertions(+), 43 deletions(-) diff --git a/impls/lua.2/core.lua b/impls/lua.2/core.lua index 06752b1b27..f9dd121dea 100644 --- a/impls/lua.2/core.lua +++ b/impls/lua.2/core.lua @@ -12,29 +12,29 @@ local throw = types.throw local Reader = require "reader" local Atom = types.Atom -core[Sym.new('pr-str')] = function (...) +core[Sym.new('pr-str')] = function (...) local res = "" local args = table.pack(...) - for i,v in ipairs(args) do + for i,v in ipairs(args) do res = res .. Printer.stringfy_val(v, true) .. " " end - return res:sub(1,#res-1) + return res:sub(1,#res-1) end core[Sym.new('str')] = function (...) local args = table.pack(...) local res = "" - for i,v in ipairs(args) do + for i,v in ipairs(args) do res = res .. Printer.stringfy_val(v, false) end return res end - core[Sym.new('prn')] = function (...) + core[Sym.new('prn')] = function (...) local res = "" local args = table.pack(...) - for i,v in ipairs(args) do + for i,v in ipairs(args) do res = res .. Printer.stringfy_val(v, true) .. " " end @@ -45,7 +45,7 @@ end core[Sym.new('println')] = function (...) local args = table.pack(...) local res = "" - for i,v in ipairs(args) do + for i,v in ipairs(args) do res = res .. Printer.stringfy_val(v, false) .. " " end print(res:sub(1,#res-1)) @@ -60,7 +60,7 @@ core[Sym.new('list')] = function (...) return List.new(args) end -core[Sym.new('list?')] = function (v) +core[Sym.new('list?')] = function (v) if is_instanceOf(v, List) then return true else @@ -68,13 +68,18 @@ core[Sym.new('list?')] = function (v) end end -core[Sym.new('vec')] = function (...) +core[Sym.new('vector')] = function (...) local args = table.pack(...) return Vector.new(args) end +core[Sym.new('vec')] = function (a) + return Vector.new(a) +end + + -core[Sym.new('empty?')] = function (v) +core[Sym.new('empty?')] = function (v) if is_sequence(v) then return #v == 0 end @@ -82,7 +87,7 @@ core[Sym.new('empty?')] = function (v) end core[Sym.new('count')] = function (v) - if v == Nil then + if v == Nil then return 0 end if is_sequence(v) then @@ -101,17 +106,17 @@ core[Sym.new('-')] = function (a, b) return a - b end core[Sym.new('*')] = function (a, b) return a * b end core[Sym.new('/')] = function (a, b) return a / b end - + core[Sym.new('=')] = types.is_equal core[Sym.new('read-string')] = Reader.read_str core[Sym.new('slurp')] = function (filename) local f = io.open(filename) - if f == nil then + if f == nil then throw(string.format("file '%s' cannot be opened", filename)) end - local res = f:read('a') + local res = f:read('a') f:close() return res end @@ -123,7 +128,7 @@ core[Sym.new('deref')] = function (v) return v.val end core[Sym.new('reset!')] = function (v, malval) v.val = malval return v.val end -core[Sym.new('swap!')] = function (v, f, ...) +core[Sym.new('swap!')] = function (v, f, ...) if not(is_instanceOf(f, Function) or type(f) == "function") then throw(string.format("second argument to swap! should be function")) end @@ -135,9 +140,9 @@ core[Sym.new('swap!')] = function (v, f, ...) return v.val end -core[Sym.new('cons')] = function (first, second, ...) +core[Sym.new('cons')] = function (first, second, ...) if ... ~= nil then throw("cons expect expects 2 args got: " .. 2 + #table.pack(...)) end - if not(is_sequence(second, List)) then + if not(is_sequence(second)) then throw("second argument to cons should be Sequence") end local res = List.new({first, table.unpack(second)}) @@ -146,14 +151,14 @@ core[Sym.new('cons')] = function (first, second, ...) end -core[Sym.new('concat')] = function (...) - local args = table.pack(...) +core[Sym.new('concat')] = function (...) + local args = table.pack(...) local tmp = {} - for i, v in ipairs(args) do - if not(is_instanceOf(v, List)) then - throw("argument to concat should be List at index:" .. i) + for i, v in ipairs(args) do + if not(is_sequence(v)) then + throw("argument to concat should be sequence at index:" .. i) end - for ii, vv in ipairs(v) do + for ii, vv in ipairs(v) do table.insert(tmp, vv) end end diff --git a/impls/lua.2/run b/impls/lua.2/run index f73e5b6f8f..a53fbc60ba 100755 --- a/impls/lua.2/run +++ b/impls/lua.2/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec lua $(dirname $0)/${STEP:-stepA_mal}.lua "${@}" diff --git a/impls/lua.2/step7_quote.lua b/impls/lua.2/step7_quote.lua index c4bab1ca73..e6f96db795 100644 --- a/impls/lua.2/step7_quote.lua +++ b/impls/lua.2/step7_quote.lua @@ -30,28 +30,39 @@ return #a > 0 and is_instanceOf(a[1],Sym) and a[1].val == v end -function quasiquote(a) - if is_instanceOf(a,List) and starts_with(a, "unquote") then - if #a-1 ~= 1 then throw("unquote expected 1 argument bot got : " .. #a) end - print("unquote hit") - return a[2] +function quasiloop(a) + local res = List.new({}) + for i=#a,1,-1 do + local elt = a[i] + if is_instanceOf(elt, List) and + starts_with(elt, "splice-unquote") then + + if #elt ~= 2 then throw("splice-unquote expected 1 argument bot got : " .. #elt) end - elseif is_instanceOf(a, List) then - local res = List.new({}) - for i=#a,1,-1 do - local elt = a[i] - if is_instanceOf(elt, List) and starts_with(elt, "splice-unquote") then + res = List.new({Sym.new( "concat"), elt[2], res}) + else + res = List.new({Sym.new( "cons"), quasiquote(elt), res}) + end + end + return res +end - if #elt ~= 2 then throw("splice-unquote expected 1 argument bot got : " .. #elt) end +function quasiquote(a) - res = List.new({Sym.new( "concat"), elt[2], res}) - else - res = List.new({Sym.new( "cons"), quasiquote(elt), res}) + if is_instanceOf(a,List) then + if starts_with(a, "unquote") then + if #a-1 ~= 1 then + throw("unquote expected 1 argument bot got : " .. #a) end + return a[2] + else + return quasiloop(a) end - return res - elseif is_instanceOf(a,Map) or is_instanceOf(a,Sym) then + elseif is_instanceOf(a, Vector) then + local tmp = quasiloop(a) + return List.new({Sym.new("vec"), tmp}) + elseif is_instanceOf(a,HashMap) or is_instanceOf(a,Sym) then return List.new({Sym.new('quote'), a}) else @@ -159,8 +170,7 @@ function EVAL(a, env) else if type(f) ~= "function" then throw("First elem should be function or special form got :'" .. type(f) .. "'.") - end - + end return f(table.unpack(args)) --fixme: varargs? end end @@ -169,7 +179,6 @@ end end --- TODO: learn why we return map function eval_ast(ast, env) if is_instanceOf(ast, List) then local l = List.new() From 08c07fa8c35d2df37b48e73428ebfd9a9da50f6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=BCseyin=20Er?= Date: Sun, 19 Feb 2023 16:00:26 +0300 Subject: [PATCH 15/25] step8 + quasiquotexpand --- impls/lua.2/core.lua | 36 +++- impls/lua.2/printer.lua | 3 +- impls/lua.2/step7_quote.lua | 6 +- impls/lua.2/step8_macros.lua | 310 +++++++++++++++++++++++++++++++++++ impls/lua.2/types.lua | 4 +- 5 files changed, 354 insertions(+), 5 deletions(-) create mode 100644 impls/lua.2/step8_macros.lua diff --git a/impls/lua.2/core.lua b/impls/lua.2/core.lua index f9dd121dea..9ce88f179d 100644 --- a/impls/lua.2/core.lua +++ b/impls/lua.2/core.lua @@ -74,7 +74,8 @@ core[Sym.new('vector')] = function (...) end core[Sym.new('vec')] = function (a) - return Vector.new(a) + local nt = table.pack(table.unpack(a)) -- this is done for copying + return Vector.new(nt) end @@ -168,5 +169,38 @@ core[Sym.new('concat')] = function (...) end +core[Sym.new('nth')] = function (v, idx, ...) + if ... ~= nil then throw("nth expect expects 2 args got: " .. 2 + #table.pack(...)) end + if not(is_sequence(v)) then + throw("first argument to nth should be Sequence") + end + if not(type(idx) == "number") then + throw("second argument to nth should be number") + end + if idx > #v - 1 or idx < 0 then + throw("invalid index") + end + return v[idx+1] or Nil +end + +core[Sym.new('first')] = function (v, ...) + if ... ~= nil then throw("first expect expects 1 args got: " .. 1 + #table.pack(...)) end + if not(is_sequence(v) or v == Nil) then + throw("first argument to first should be Sequence or nil") + end + return v[1] or Nil +end + +core[Sym.new('rest')] = function (v, ...) + if ... ~= nil then throw("rest expect expects 1 args got: " .. 1 + #table.pack(...)) end + if not(is_sequence(v) or v == Nil) then + throw("first argument to rest should be Sequence or nil") + end + if false and #v <= 1 then + return Nil + end + return List.new({table.unpack(v,2)}) +end + return core diff --git a/impls/lua.2/printer.lua b/impls/lua.2/printer.lua index e51dbdd554..5f713d05d8 100644 --- a/impls/lua.2/printer.lua +++ b/impls/lua.2/printer.lua @@ -51,7 +51,8 @@ function Printer.stringfy_val(val, readably) elseif is_instanceOf(val, Err) then return "Error: " .. Scanner.unescape(val.val) elseif is_instanceOf(val, Function) then - res = "(fn* " .. Printer.stringfy_val(val.params) .. "-->" .. Printer.stringfy_val(val.ast) ..")" + res = "(fn* " .. Printer.stringfy_val(val.params) .. + "-->" .. Printer.stringfy_val(val.ast) ..")" .. "ismacro: " .. tostring(val.is_macro) elseif is_instanceOf(val, Atom) then res = "(atom " .. Printer.stringfy_val(val.val) .. ")" diff --git a/impls/lua.2/step7_quote.lua b/impls/lua.2/step7_quote.lua index e6f96db795..46fe338ccf 100644 --- a/impls/lua.2/step7_quote.lua +++ b/impls/lua.2/step7_quote.lua @@ -156,8 +156,12 @@ function EVAL(a, env) if #a-1 ~= 1 then throw("quote expects 1 argument got '" .. #a-1 .. "'.") end return a[2] elseif first_sym == "quasiquote" then - if #a-1 ~= 1 then throw("quote expects 1 argument got '" .. #a-1 .. "'.") end + if #a-1 ~= 1 then throw("quasiquote expects 1 argument got '" .. #a-1 .. "'.") end a = quasiquote(a[2]) + elseif first_sym == "quasiquoteexpand" then + if #a-1 ~= 1 then throw("quasiquoteexpand expects 1 argument got '" .. #a-1 .. "'.") end + return quasiquote(a[2]) + else local args = eval_ast(a, env) diff --git a/impls/lua.2/step8_macros.lua b/impls/lua.2/step8_macros.lua new file mode 100644 index 0000000000..b23128679b --- /dev/null +++ b/impls/lua.2/step8_macros.lua @@ -0,0 +1,310 @@ +local Reader = require "reader" +local Printer = require "printer" +local types = require "types" +local Env = require "env" +local Sym = types.Sym +local is_instanceOf = types.isinstanceof +local Err = types.Err +local List = types.MalList +local throw = types.throw +local HashMap = types.MalHashMap +local Vector = types.MalVector +local Nil = types.Nil +local core = require "core" +local Function = types.MalFunction + +function raw_read(prompt) + io.write(prompt) + local v = io.read() + if v == nil then + io.write('\n') + end + return v +end + +function READ(str) + return Reader.read_str(str) +end + +function starts_with(a, v) +return #a > 0 and is_instanceOf(a[1],Sym) and + a[1].val == v +end + + +function quasiloop(a) + local res = List.new({}) + for i=#a,1,-1 do + local elt = a[i] + if is_instanceOf(elt, List) and + starts_with(elt, "splice-unquote") then + + if #elt ~= 2 then throw("splice-unquote expected 1 argument bot got : " .. #elt) end + + res = List.new({Sym.new( "concat"), elt[2], res}) + else + res = List.new({Sym.new( "cons"), quasiquote(elt), res}) + end + end + return res +end + +function quasiquote(a) + + if is_instanceOf(a,List) then + if starts_with(a, "unquote") then + if #a-1 ~= 1 then + throw("unquote expected 1 argument bot got : " .. #a) + end + return a[2] + else + return quasiloop(a) + end + elseif is_instanceOf(a, Vector) then + local tmp = quasiloop(a) + return List.new({Sym.new("vec"), tmp}) + elseif is_instanceOf(a,HashMap) or is_instanceOf(a,Sym) then + return List.new({Sym.new('quote'), a}) + + else + return a + + end +end + +function is_macro_call(ast, env) + if is_instanceOf(ast, List) and #ast >= 1 and is_instanceOf(ast[1], Sym) then + local status, first_env = pcall( function () return env:get(ast[1]) end) + if not status then return false end + if is_instanceOf(first_env, Function) and first_env.is_macro then + return true + else + return false + end + else + return false + end + +end + +function macro_expand(ast, env) + while is_macro_call(ast, env) do + local macro = env:get(ast[1]) + local f = macro.fn + ast = f(table.unpack(ast, 2)) + end + + return ast +end + +function EVAL(a, env) + while true do + a = macro_expand(a, env) + if not(is_instanceOf(a, List)) then + return eval_ast(a, env) + end + + if #a == 0 then + return a + end + local first_elem = a[1] + local first_sym = is_instanceOf(first_elem, Sym) and first_elem.val or "" + + if first_sym == "def!" then + if #a ~= 3 then + throw(string.format("def! expects 2 arguments got: %d", #a-1)) + end + if not(is_instanceOf(a[2], Sym)) then + throw("first argument to def! must be symbol") + end + local value = EVAL(a[3], env) + env:set(a[2], value) + return value + + elseif first_sym == "let*" then + if #a ~= 3 then + throw(string.format("let* expects 2 arguments got: %d", #a-1)) + end + + local let_env = Env.new(env) + if not(is_instanceOf(a[2], List) or is_instanceOf(a[2], Vector)) then + throw("Second arg to let* should be list or vector") + end + if #a[2] % 2 ~= 0 then + throw(string.format("Length ofSecond arg to let* should be even number got: %d", #a[2])) + end + + for i=1,#a[2],2 do + if not(is_instanceOf(a[2][i], Sym)) then + throw("Expected symbol in let*'s second argument") + end + local key = a[2][i] + local value = EVAL(a[2][i+1],let_env) + let_env:set(key,value) + end + a = a[3] + env = let_env + + + elseif first_sym == "do" then + for i=2,#a-1 do + EVAL(a[i], env) + end + a = a[#a] --tco + + + elseif first_sym == "if" then + if not (#a == 3 or #a == 4) then + throw("if expected 2 or 3 arguments but got '" .. #a-1 .. "'.") + end + local cond = EVAL(a[2], env) + if cond ~= false and cond ~= Nil then + a = a[3] + else + if #a == 4 then + a = a[4] + else + return Nil + end + end + elseif first_sym == "fn*" then + if (#a) ~= 3 then throw("fn* expected 2 arguments but got '" .. #a-1 .. "'.") end + if false then throw("second parameter to fn* should have length 2 but got '" .. #a[2] .. "'.") end + return Function.new(function (...) + local closed_over_env = Env.new(env) + local exprs = List.new(table.pack(...)) + local binds = a[2] + closed_over_env:bind(binds, exprs) + + return EVAL(a[3], closed_over_env) + end, a[3], env, a[2], false) + + elseif first_sym == "quote" then + if #a-1 ~= 1 then throw("quote expects 1 argument got '" .. #a-1 .. "'.") end + return a[2] + elseif first_sym == "quasiquote" then + if #a-1 ~= 1 then throw("quote expects 1 argument got '" .. #a-1 .. "'.") end + a = quasiquote(a[2]) + elseif first_sym == "defmacro!" then + if #a ~= 3 then + throw(string.format("defmacro! expects 2 arguments got: %d", #a-1)) + end + if not(is_instanceOf(a[2], Sym)) then + throw("first argument to defmacro! must be symbol") + end + local value = EVAL(a[3], env) + if not(is_instanceOf(value, Function)) then + throw("second argument to defmacro must be function") + end + value.is_macro = true + env:set(a[2], value) + return value + + + elseif first_sym == "macroexpand" then + if (#a) ~= 2 then throw("macroexpand expected 1 arguments but got '" .. #a-1 .. "'.") end + return macro_expand(a[2],env) + else + + local args = eval_ast(a, env) + local f = table.remove(args,1) + if types.is_malfunc(f) then + a = f.ast + env = Env.new(f.env) + env:bind(f.params, args) + + else + if type(f) ~= "function" then + throw("First elem should be function or special form got :'" .. type(f) .. "'.") + end + return f(table.unpack(args)) --fixme: varargs? + end + end + +end +end + + +function eval_ast(ast, env) + if is_instanceOf(ast, List) then + local l = List.new() + for i=1,#ast do + table.insert(l, EVAL(ast[i], env)) + end + return l + elseif is_instanceOf(ast, Vector) then + local v = Vector.new() + for i=1, #ast do + table.insert(v, EVAL(ast[i], env)) + end + return v + elseif is_instanceOf(ast, HashMap) then + local map = HashMap.new() + for k,v in pairs(ast) do + map[EVAL(k, env)] = EVAL(v, env) + end + return map + elseif is_instanceOf(ast, Sym) then + if string.byte(ast.val, 1, 1) == 202 and + string.byte(ast.val, 2, 2) == 158 then -- this magic numbers come from \u{29E} + return Sym.new(":" .. string.sub(ast.val, 3, #ast.val)) + end + return env:get(ast) + + else + return ast + end +end + + +function PRINT(a) + return Printer.stringfy_val(a, true) +end + + +local repl_env = Env.new(nil) + +for k,v in pairs(core) do + repl_env:set(k,v) +end + +repl_env:set(Sym.new('eval'), function (ast) + return EVAL(ast, repl_env) +end) + + +function rep(str) + return PRINT(EVAL(READ(str), repl_env)) +end + + + +function main() + rep("(def! not (fn* (a) (if a false true)))") + rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \"(slurp f) \"\nnil)\")))))") +rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))") + repl_env:set(Sym.new("*ARGV*"), List.new(table.pack(table.unpack(arg,2)))) + if #arg > 0 then + local file_to_run = table.remove(arg,1) + rep("(load-file \"" .. file_to_run .. "\")") + os.exit(0) + end + + local line = '' + while true do + line = raw_read('user> ') + if line == nil then + break + end + local status, err = pcall(function () print(rep(line)) end) + if not status then + if is_instanceOf(err, Err) then + err = Printer.stringfy_val(err) + end + print(err) + print(debug.traceback()) + end + end +end + +main() diff --git a/impls/lua.2/types.lua b/impls/lua.2/types.lua index 34cd6bf765..7c924a1ea0 100644 --- a/impls/lua.2/types.lua +++ b/impls/lua.2/types.lua @@ -72,8 +72,8 @@ M.Nil = M.MalNilType.new() M.MalFunction = {} M.MalFunction.__index = M.MalFunction -function M.MalFunction.new(fn, ast, env, params) - local self = {fn = fn, ast = ast, env = env, params = params} +function M.MalFunction.new(fn, ast, env, params, is_macro) + local self = {fn = fn, ast = ast, env = env, params = params, is_macro = is_macro} return setmetatable(self, M.MalFunction) end From 2be33cdd64dce114d360e64a6a0e46b465fa3222 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=BCseyin=20Er?= Date: Wed, 22 Feb 2023 21:31:21 +0300 Subject: [PATCH 16/25] step9 wip --- impls/lua.2/core.lua | 218 +++++++++++++++++++++++++ impls/lua.2/printer.lua | 2 +- impls/lua.2/step9_try.lua | 333 ++++++++++++++++++++++++++++++++++++++ impls/lua.2/types.lua | 8 + 4 files changed, 560 insertions(+), 1 deletion(-) create mode 100644 impls/lua.2/step9_try.lua diff --git a/impls/lua.2/core.lua b/impls/lua.2/core.lua index 9ce88f179d..1369c26abe 100644 --- a/impls/lua.2/core.lua +++ b/impls/lua.2/core.lua @@ -11,6 +11,8 @@ local Nil = types.Nil local throw = types.throw local Reader = require "reader" local Atom = types.Atom +local Err = types.Err +local Hashmap = types.MalHashMap core[Sym.new('pr-str')] = function (...) local res = "" @@ -202,5 +204,221 @@ core[Sym.new('rest')] = function (v, ...) return List.new({table.unpack(v,2)}) end +core[Sym.new('throw')] = function (v, ...) + if ... ~= nil then throw("throw expect expects 1 args got: " .. 1 + #table.pack(...)) end + if v == nil then + return Err.new("") + end + if false and not(type(v) == "string" or v == Nil) then + throw("first argument to throw should be string or nil") + end + + return Err.new(Printer.stringfy_val(v)) +end + + +core[Sym.new('map')] = function (f, seq, ...) + if ... ~= nil then + throw("map expect expects 1 args got: " .. 1 + #table.pack(...)) + end + + if not(is_instanceOf(f, Function) or type(f) == "function" ) then + throw("map expect first argument to be function") + end + if not(is_sequence(seq)) then + throw("map expect 2nd argument to be sequence") + end + local constructor = Err.new + if is_instanceOf(f, Function) then + f = f.fn + end + if is_instanceOf(seq, List) then + constructor = List.new + elseif is_instanceOf(seq, Vector) then + constructor = Vector.new + end + local acc = {} + for _,v in pairs(seq) do + table.insert(acc, f(v)) + end + return constructor(acc) + +end + +core[Sym.new('apply')] = function (...) + local args = table.pack(...) + if #args < 2 then + throw("apply expect at leasth 2 args got: " .. #args) + end + local f = args[1] + if not(is_instanceOf(f, Function) or type(f) == "function" ) then + throw("apply expect first argument to be function") + end + local last_arg = args[#args] + if not(is_instanceOf(last_arg, List)) then + throw("apply expect last argument to be List") + end + if is_instanceOf(f, Function) then + f = f.fn + end + + for i=#args-1,2,-1 do + table.insert(last_arg, 1, args[i]) + end + return f(table.unpack(last_arg)) +end + + + + + +core[Sym.new('nil?')] = function (v, ...) + if ... ~= nil then throw("nil? expect expects 1 args got: " .. 1 + #table.pack(...)) end + if v == nil then throw("nil? expect expects 1 args got: 0") end + return v == Nil +end + +core[Sym.new('true?')] = function (v, ...) + if ... ~= nil then throw("true? expect expects 1 args got: " .. 1 + #table.pack(...)) end + if v == nil then throw("true? expect expects 1 args got: 0") end + return v == true +end + +core[Sym.new('false?')] = function (v, ...) + if ... ~= nil then throw("false? expect expects 1 args got: " .. 1 + #table.pack(...)) end + if v == nil then throw("false? expect expects 1 args got: 0") end + return v == false +end + +core[Sym.new('symbol?')] = function (v, ...) + if ... ~= nil then throw("symbol? expect expects 1 args got: " .. 1 + #table.pack(...)) end + if v == nil then throw("symbol? expect expects 1 args got: 0") end + return is_instanceOf(v, Sym) +end + +core[Sym.new('sequential?')] = is_sequence + +core[Sym.new('vector?')] = function (v, ...) + if ... ~= nil then throw("vector? expect expects 1 args got: " .. 1 + #table.pack(...)) end + if v == nil then throw("vector? expect expects 1 args got: 0") end + return is_instanceOf(v, Vector) +end + +core[Sym.new('hash-map')] = Hashmap.new + +core[Sym.new('keys')] = function (v, ...) + if ... ~= nil then throw("keys expect expects 1 args got: " .. 1 + #table.pack(...)) end + if v == nil then throw("keys expect expects 1 args got: 0") end + if not(is_instanceOf(v, Hashmap)) then throw("keys expects its argument to be HashMap but got:" .. type(v) ) end + + local res = {} + for k,_ in pairs(v) do + table.insert(res, k) + end + return List.new(res) + +end + +core[Sym.new('vals')] = function (v, ...) + if ... ~= nil then throw("vals expect expects 1 args got: " .. 1 + #table.pack(...)) end + if v == nil then throw("vals expect expects 1 args got: 0") end + if not(is_instanceOf(v, Hashmap)) then throw("vals expects its argument to be HashMap but got:" .. type(v) ) end + + local res = {} + for _,v in pairs(v) do + table.insert(res, k) + end + return List.new(res) + +end + + +core[Sym.new('map?')] = function (v, ...) + if ... ~= nil then throw("map? expect expects 1 args got: " .. 1 + #table.pack(...)) end + if v == nil then throw("map? expect expects 1 args got: 0") end + return is_instanceOf(v, Hashmap) +end + +core[Sym.new('hash-map')] = Hashmap.new + +core[Sym.new('get')] = function (...) + local args = table.pack(...) + if #args ~= 2 then + throw("map? expect expects 2 args got: " .. #args) + end + local map = args[1] + local key = args[2] + if not(is_instanceOf(map, Hashmap)) then + throw("get expects first arg to be hashmap") + end + + return map[key] and map[key] or Nil +end + +core[Sym.new('contains?')] = function (...) + local args = table.pack(...) + if #args ~= 2 then + throw("contains? expect expects 1 args got: " .. #args) + end + local map = args[1] + local key = args[2] + if not(is_instanceOf(map, Hashmap)) then + throw("contains? expects first arg to be hashmap") + end + + return map[key] and true or false +end + +core[Sym.new('assoc')] = function (...) + local args = table.pack(...) + if #args % 2 ~= 1 then + throw("assoc expect expects odd number of args got: " .. #args) + end + local map = table.remove(args,1) + if not(is_instanceOf(map, Hashmap)) then + throw("assoc expects first arg to be hashmap") + end + local res = Hashmap.new() + for k,v in pairs(map) do + res[k] = v + end + for i=1,#args,2 do + print("associng") + res[args[i]] = args[i+1] + end + + return res +end + +core[Sym.new('dissoc')] = function (...) + local args = table.pack(...) + if #args ~= 2 then + throw("assoc expect expects 2 args got: " .. #args) + end + local map = args[1] + if not(is_instanceOf(map, Hashmap)) then + throw("assoc expects first arg to be HashMap") + end + local list = args[2] + if not(is_instanceOf(list, List)) then + throw("assoc expects second arg to be List") + end + + local res = Hashmap.new() + for k,v in pairs(map) do + for _, listval in pairs(list) do + if k == listval then + else + res[k] = v + end + end + end + + + return res +end + + + return core diff --git a/impls/lua.2/printer.lua b/impls/lua.2/printer.lua index 5f713d05d8..a344153388 100644 --- a/impls/lua.2/printer.lua +++ b/impls/lua.2/printer.lua @@ -49,7 +49,7 @@ function Printer.stringfy_val(val, readably) elseif is_instanceOf(val, Sym) then return val.val elseif is_instanceOf(val, Err) then - return "Error: " .. Scanner.unescape(val.val) + return "exc is: " .. Scanner.escape(val.val) elseif is_instanceOf(val, Function) then res = "(fn* " .. Printer.stringfy_val(val.params) .. "-->" .. Printer.stringfy_val(val.ast) ..")" .. "ismacro: " .. tostring(val.is_macro) diff --git a/impls/lua.2/step9_try.lua b/impls/lua.2/step9_try.lua new file mode 100644 index 0000000000..f013cde151 --- /dev/null +++ b/impls/lua.2/step9_try.lua @@ -0,0 +1,333 @@ +local Reader = require "reader" +local Printer = require "printer" +local types = require "types" +local Env = require "env" +local Sym = types.Sym +local is_instanceOf = types.isinstanceof +local Err = types.Err +local List = types.MalList +local throw = types.throw +local HashMap = types.MalHashMap +local Vector = types.MalVector +local Nil = types.Nil +local core = require "core" +local Function = types.MalFunction + +function raw_read(prompt) + io.write(prompt) + local v = io.read() + if v == nil then + io.write('\n') + end + return v +end + +function READ(str) + return Reader.read_str(str) +end + +function starts_with(a, v) +return #a > 0 and is_instanceOf(a[1],Sym) and + a[1].val == v +end + + +function quasiloop(a) + local res = List.new({}) + for i=#a,1,-1 do + local elt = a[i] + if is_instanceOf(elt, List) and + starts_with(elt, "splice-unquote") then + + if #elt ~= 2 then throw("splice-unquote expected 1 argument bot got : " .. #elt) end + + res = List.new({Sym.new( "concat"), elt[2], res}) + else + res = List.new({Sym.new( "cons"), quasiquote(elt), res}) + end + end + return res +end + +function quasiquote(a) + + if is_instanceOf(a,List) then + if starts_with(a, "unquote") then + if #a-1 ~= 1 then + throw("unquote expected 1 argument bot got : " .. #a) + end + return a[2] + else + return quasiloop(a) + end + elseif is_instanceOf(a, Vector) then + local tmp = quasiloop(a) + return List.new({Sym.new("vec"), tmp}) + elseif is_instanceOf(a,HashMap) or is_instanceOf(a,Sym) then + return List.new({Sym.new('quote'), a}) + + else + return a + + end +end + +function is_macro_call(ast, env) + if is_instanceOf(ast, List) and #ast >= 1 and is_instanceOf(ast[1], Sym) then + local status, first_env = pcall( function () return env:get(ast[1]) end) + if not status then return false end + if is_instanceOf(first_env, Function) and first_env.is_macro then + return true + else + return false + end + else + return false + end + +end + +function macro_expand(ast, env) + while is_macro_call(ast, env) do + local macro = env:get(ast[1]) + local f = macro.fn + ast = f(table.unpack(ast, 2)) + end + + return ast +end + +function try(a, c, env) + local status, val = pcall( function () + return EVAL(a, env) + end ) + if not status then + local env_with_ex = Env.new(env) + env_with_ex:set(c[2],val) + return EVAL(c[3], env_with_ex) + end + return val +end + + + +function EVAL(a, env) + while true do + a = macro_expand(a, env) + if not(is_instanceOf(a, List)) then + return eval_ast(a, env) + end + + if #a == 0 then + return a + end + local first_elem = a[1] + local first_sym = is_instanceOf(first_elem, Sym) and first_elem.val or "" + + if first_sym == "def!" then + if #a ~= 3 then + throw(string.format("def! expects 2 arguments got: %d", #a-1)) + end + if not(is_instanceOf(a[2], Sym)) then + throw("first argument to def! must be symbol") + end + local value = EVAL(a[3], env) + env:set(a[2], value) + return value + + elseif first_sym == "let*" then + if #a ~= 3 then + throw(string.format("let* expects 2 arguments got: %d", #a-1)) + end + + local let_env = Env.new(env) + if not(is_instanceOf(a[2], List) or is_instanceOf(a[2], Vector)) then + throw("Second arg to let* should be list or vector") + end + if #a[2] % 2 ~= 0 then + throw(string.format("Length ofSecond arg to let* should be even number got: %d", #a[2])) + end + + for i=1,#a[2],2 do + if not(is_instanceOf(a[2][i], Sym)) then + throw("Expected symbol in let*'s second argument") + end + local key = a[2][i] + local value = EVAL(a[2][i+1],let_env) + let_env:set(key,value) + end + a = a[3] + env = let_env + + + elseif first_sym == "do" then + for i=2,#a-1 do + EVAL(a[i], env) + end + a = a[#a] --tco + + + elseif first_sym == "if" then + if not (#a == 3 or #a == 4) then + throw("if expected 2 or 3 arguments but got '" .. #a-1 .. "'.") + end + local cond = EVAL(a[2], env) + if cond ~= false and cond ~= Nil then + a = a[3] + else + if #a == 4 then + a = a[4] + else + return Nil + end + end + elseif first_sym == "fn*" then + if (#a) ~= 3 then throw("fn* expected 2 arguments but got '" .. #a-1 .. "'.") end + if false then throw("second parameter to fn* should have length 2 but got '" .. #a[2] .. "'.") end + return Function.new(function (...) + local closed_over_env = Env.new(env) + local exprs = List.new(table.pack(...)) + local binds = a[2] + closed_over_env:bind(binds, exprs) + + return EVAL(a[3], closed_over_env) + end, a[3], env, a[2], false) + + elseif first_sym == "quote" then + if #a-1 ~= 1 then throw("quote expects 1 argument got '" .. #a-1 .. "'.") end + return a[2] + elseif first_sym == "quasiquote" then + if #a-1 ~= 1 then throw("quote expects 1 argument got '" .. #a-1 .. "'.") end + a = quasiquote(a[2]) + elseif first_sym == "defmacro!" then + if #a ~= 3 then + throw(string.format("defmacro! expects 2 arguments got: %d", #a-1)) + end + if not(is_instanceOf(a[2], Sym)) then + throw("first argument to defmacro! must be symbol") + end + local value = EVAL(a[3], env) + if not(is_instanceOf(value, Function)) then + throw("second argument to defmacro must be function") + end + value.is_macro = true + env:set(a[2], value) + return value + + + elseif first_sym == "macroexpand" then + if (#a) ~= 2 then throw("macroexpand expected 1 arguments but got '" .. #a-1 .. "'.") end + return macro_expand(a[2],env) + elseif first_sym == "try*" then + if (#a) ~= 3 then throw("try expected 2 arguments but got '" .. #a-1 .. "'.") end + if not( is_instanceOf(a[3], List) and + #a[3] == 3 and is_instanceOf(a[3][1],Sym) and a[3][1].val == "catch*") then + throw("try expected 2nd argument as list with 3 elems" .. + "first elem being symbol 'catch*'") + end + return try(a[2], a[3], env) + + else + + local args = eval_ast(a, env) + local f = table.remove(args,1) + if types.is_malfunc(f) then + a = f.ast + env = Env.new(f.env) + env:bind(f.params, args) + + else + if type(f) ~= "function" then + throw("First elem should be function or special form got :'" .. type(f) .. "'.") + end + return f(table.unpack(args)) --fixme: varargs? + end + end + +end +end + + +function eval_ast(ast, env) + if is_instanceOf(ast, List) then + local l = List.new() + for i=1,#ast do + table.insert(l, EVAL(ast[i], env)) + end + return l + elseif is_instanceOf(ast, Vector) then + local v = Vector.new() + for i=1, #ast do + table.insert(v, EVAL(ast[i], env)) + end + return v + elseif is_instanceOf(ast, HashMap) then + local map = HashMap.new() + for k,v in pairs(ast) do + map[EVAL(k, env)] = EVAL(v, env) + end + return map + elseif is_instanceOf(ast, Sym) then + if string.byte(ast.val, 1, 1) == 202 and + string.byte(ast.val, 2, 2) == 158 then -- this magic numbers come from \u{29E} + return Sym.new(":" .. string.sub(ast.val, 3, #ast.val)) + end + return env:get(ast) + + else + return ast + end +end + + +function PRINT(a) + return Printer.stringfy_val(a, true) +end + + +local repl_env = Env.new(nil) + +for k,v in pairs(core) do + repl_env:set(k,v) +end + +repl_env:set(Sym.new('eval'), function (ast) + return EVAL(ast, repl_env) +end) + + +function rep(str) + return PRINT(EVAL(READ(str), repl_env)) +end + + + +function main() + rep("(def! not (fn* (a) (if a false true)))") + rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \"(slurp f) \"\nnil)\")))))") +rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))") + repl_env:set(Sym.new("*ARGV*"), List.new(table.pack(table.unpack(arg,2)))) + if #arg > 0 then + local file_to_run = table.remove(arg,1) + rep("(load-file \"" .. file_to_run .. "\")") + os.exit(0) + end + + local line = '' + while true do + line = raw_read('user> ') + if line == nil then + break + end + local status, err = pcall(function () print(rep(line)) end) + if not status then + if is_instanceOf(err, Err) then + err = Printer.stringfy_val(err) + end + print(err) + print(debug.traceback()) + end + end +end + +main() diff --git a/impls/lua.2/types.lua b/impls/lua.2/types.lua index 7c924a1ea0..bc230c06fc 100644 --- a/impls/lua.2/types.lua +++ b/impls/lua.2/types.lua @@ -18,6 +18,14 @@ function M.MalHashMap.new(...) return self end +function M.MalHashMap:keys() + local res = {} + for k,_ in pairs(self) do + table.insert(k) + end + return M.MalList.new(res) +end + M.MalList = {} M.MalList.__index = M.MalList From 3a8b6912aa13372163cc4d53c48e7d1daec44d06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=BCseyin=20Er?= Date: Fri, 24 Feb 2023 21:27:01 +0300 Subject: [PATCH 17/25] step9 wip TODO: think about converting keywords to strings in core keys converted to strings instead of symbol. --- impls/lua.2/core.lua | 184 ++++++++++++++++++++++++-------------- impls/lua.2/env.lua | 2 +- impls/lua.2/printer.lua | 6 ++ impls/lua.2/step9_try.lua | 4 +- impls/lua.2/types.lua | 8 +- 5 files changed, 131 insertions(+), 73 deletions(-) diff --git a/impls/lua.2/core.lua b/impls/lua.2/core.lua index 1369c26abe..b1b1dd6b26 100644 --- a/impls/lua.2/core.lua +++ b/impls/lua.2/core.lua @@ -14,7 +14,7 @@ local Atom = types.Atom local Err = types.Err local Hashmap = types.MalHashMap -core[Sym.new('pr-str')] = function (...) +core['pr-str'] = function (...) local res = "" local args = table.pack(...) for i,v in ipairs(args) do @@ -24,7 +24,7 @@ core[Sym.new('pr-str')] = function (...) return res:sub(1,#res-1) end -core[Sym.new('str')] = function (...) +core['str'] = function (...) local args = table.pack(...) local res = "" for i,v in ipairs(args) do @@ -33,7 +33,7 @@ core[Sym.new('str')] = function (...) return res end - core[Sym.new('prn')] = function (...) + core['prn'] = function (...) local res = "" local args = table.pack(...) for i,v in ipairs(args) do @@ -44,7 +44,7 @@ end return Nil end -core[Sym.new('println')] = function (...) +core['println'] = function (...) local args = table.pack(...) local res = "" for i,v in ipairs(args) do @@ -57,12 +57,12 @@ end -core[Sym.new('list')] = function (...) +core['list'] = function (...) local args = table.pack(...) return List.new(args) end -core[Sym.new('list?')] = function (v) +core['list?'] = function (v) if is_instanceOf(v, List) then return true else @@ -70,26 +70,26 @@ core[Sym.new('list?')] = function (v) end end -core[Sym.new('vector')] = function (...) +core['vector'] = function (...) local args = table.pack(...) return Vector.new(args) end -core[Sym.new('vec')] = function (a) +core['vec'] = function (a) local nt = table.pack(table.unpack(a)) -- this is done for copying return Vector.new(nt) end -core[Sym.new('empty?')] = function (v) +core['empty?'] = function (v) if is_sequence(v) then return #v == 0 end throw("'empty? expects a parameter to be sequence'") end -core[Sym.new('count')] = function (v) +core['count'] = function (v) if v == Nil then return 0 end @@ -99,22 +99,22 @@ core[Sym.new('count')] = function (v) throw("'count expects parameter to be sequence or nil'") end -core[Sym.new('<')] = function (a, b) return a < b end -core[Sym.new('>')] = function (a, b) return a > b end -core[Sym.new('<=')] = function (a, b) return a <= b end -core[Sym.new('>=')] = function (a, b) return a >= b end +core['<'] = function (a, b) return a < b end +core['>'] = function (a, b) return a > b end +core['<='] = function (a, b) return a <= b end +core['>='] = function (a, b) return a >= b end -core[Sym.new('+')] = function (a, b) return a + b end -core[Sym.new('-')] = function (a, b) return a - b end -core[Sym.new('*')] = function (a, b) return a * b end -core[Sym.new('/')] = function (a, b) return a / b end +core['+'] = function (a, b) return a + b end +core['-'] = function (a, b) return a - b end +core['*'] = function (a, b) return a * b end +core['/'] = function (a, b) return a / b end -core[Sym.new('=')] = types.is_equal +core['='] = types.is_equal -core[Sym.new('read-string')] = Reader.read_str +core['read-string'] = Reader.read_str -core[Sym.new('slurp')] = function (filename) +core['slurp'] = function (filename) local f = io.open(filename) if f == nil then throw(string.format("file '%s' cannot be opened", filename)) @@ -124,14 +124,14 @@ core[Sym.new('slurp')] = function (filename) return res end -core[Sym.new('atom')] = function (v) return Atom.new(v) end -core[Sym.new('atom?')] = function (v) return types.is_atom(v) end -core[Sym.new('deref')] = function (v) return v.val end +core['atom'] = function (v) return Atom.new(v) end +core['atom?'] = function (v) return types.is_atom(v) end +core['deref'] = function (v) return v.val end -core[Sym.new('reset!')] = function (v, malval) +core['reset!'] = function (v, malval) v.val = malval return v.val end -core[Sym.new('swap!')] = function (v, f, ...) +core['swap!'] = function (v, f, ...) if not(is_instanceOf(f, Function) or type(f) == "function") then throw(string.format("second argument to swap! should be function")) end @@ -143,7 +143,7 @@ core[Sym.new('swap!')] = function (v, f, ...) return v.val end -core[Sym.new('cons')] = function (first, second, ...) +core['cons'] = function (first, second, ...) if ... ~= nil then throw("cons expect expects 2 args got: " .. 2 + #table.pack(...)) end if not(is_sequence(second)) then throw("second argument to cons should be Sequence") @@ -154,7 +154,7 @@ core[Sym.new('cons')] = function (first, second, ...) end -core[Sym.new('concat')] = function (...) +core['concat'] = function (...) local args = table.pack(...) local tmp = {} for i, v in ipairs(args) do @@ -171,7 +171,7 @@ core[Sym.new('concat')] = function (...) end -core[Sym.new('nth')] = function (v, idx, ...) +core['nth'] = function (v, idx, ...) if ... ~= nil then throw("nth expect expects 2 args got: " .. 2 + #table.pack(...)) end if not(is_sequence(v)) then throw("first argument to nth should be Sequence") @@ -185,7 +185,7 @@ core[Sym.new('nth')] = function (v, idx, ...) return v[idx+1] or Nil end -core[Sym.new('first')] = function (v, ...) +core['first'] = function (v, ...) if ... ~= nil then throw("first expect expects 1 args got: " .. 1 + #table.pack(...)) end if not(is_sequence(v) or v == Nil) then throw("first argument to first should be Sequence or nil") @@ -193,7 +193,7 @@ core[Sym.new('first')] = function (v, ...) return v[1] or Nil end -core[Sym.new('rest')] = function (v, ...) +core['rest'] = function (v, ...) if ... ~= nil then throw("rest expect expects 1 args got: " .. 1 + #table.pack(...)) end if not(is_sequence(v) or v == Nil) then throw("first argument to rest should be Sequence or nil") @@ -204,7 +204,7 @@ core[Sym.new('rest')] = function (v, ...) return List.new({table.unpack(v,2)}) end -core[Sym.new('throw')] = function (v, ...) +core['throw'] = function (v, ...) if ... ~= nil then throw("throw expect expects 1 args got: " .. 1 + #table.pack(...)) end if v == nil then return Err.new("") @@ -216,8 +216,8 @@ core[Sym.new('throw')] = function (v, ...) return Err.new(Printer.stringfy_val(v)) end - -core[Sym.new('map')] = function (f, seq, ...) +--fixme +core['map'] = function (f, seq, ...) if ... ~= nil then throw("map expect expects 1 args got: " .. 1 + #table.pack(...)) end @@ -232,20 +232,16 @@ core[Sym.new('map')] = function (f, seq, ...) if is_instanceOf(f, Function) then f = f.fn end - if is_instanceOf(seq, List) then - constructor = List.new - elseif is_instanceOf(seq, Vector) then - constructor = Vector.new - end local acc = {} - for _,v in pairs(seq) do + for k,v in ipairs(seq) do + --print("iterating " .. Printer.stringfy_val(k) .."--".. Printer.stringfy_val(v)) table.insert(acc, f(v)) end - return constructor(acc) + return List.new(acc) end -core[Sym.new('apply')] = function (...) +core['apply'] = function (...) local args = table.pack(...) if #args < 2 then throw("apply expect at leasth 2 args got: " .. #args) @@ -255,13 +251,12 @@ core[Sym.new('apply')] = function (...) throw("apply expect first argument to be function") end local last_arg = args[#args] - if not(is_instanceOf(last_arg, List)) then - throw("apply expect last argument to be List") + if not(is_sequence(last_arg)) then + throw("apply expect last argument to be sequence") end if is_instanceOf(f, Function) then f = f.fn end - for i=#args-1,2,-1 do table.insert(last_arg, 1, args[i]) end @@ -270,78 +265,121 @@ end - - -core[Sym.new('nil?')] = function (v, ...) +core['nil?'] = function (v, ...) if ... ~= nil then throw("nil? expect expects 1 args got: " .. 1 + #table.pack(...)) end if v == nil then throw("nil? expect expects 1 args got: 0") end return v == Nil end -core[Sym.new('true?')] = function (v, ...) +core['true?'] = function (v, ...) if ... ~= nil then throw("true? expect expects 1 args got: " .. 1 + #table.pack(...)) end if v == nil then throw("true? expect expects 1 args got: 0") end return v == true end -core[Sym.new('false?')] = function (v, ...) +core['false?'] = function (v, ...) if ... ~= nil then throw("false? expect expects 1 args got: " .. 1 + #table.pack(...)) end if v == nil then throw("false? expect expects 1 args got: 0") end return v == false end -core[Sym.new('symbol?')] = function (v, ...) +core['symbol?'] = function (v, ...) if ... ~= nil then throw("symbol? expect expects 1 args got: " .. 1 + #table.pack(...)) end if v == nil then throw("symbol? expect expects 1 args got: 0") end - return is_instanceOf(v, Sym) + return is_instanceOf(v, Sym) and not( "\u{29E}" == string.sub(v.val,1,2)) +end + +core['keyword?'] = function (v, ...) + if ... ~= nil then throw("keyword? expect expects 1 args got: " .. 1 + #table.pack(...)) end + if v == nil then throw("keyword? expect expects 1 args got: 0") end + return is_instanceOf(v, Sym) and "\u{029e}" == string.sub(v.val,1,2) + +end + +core['keyword'] = function (...) + local args = table.pack(...) + if #args ~= 1 then + throw("keyword expect expects 1 args got: " .. #args) + end + local val = args[1] + if core['keyword?'](val) then + return val + end + if not( type(val) == "string") then + throw("keyword expects string or keyword type") + end + return Sym.new("\u{029e}" .. val) + +end + +core['symbol'] = function (...) + local args = table.pack(...) + if #args ~= 1 then + throw("symbol expect expects 1 args got: " .. #args) + end + local val = args[1] + if core['symbol?'](val) then + return val + end + if not( type(val) == "string") then + throw("symbol expects string or symbol type") + end + return Sym.new(val) + end -core[Sym.new('sequential?')] = is_sequence -core[Sym.new('vector?')] = function (v, ...) + +core['sequential?'] = is_sequence + +core['vector?'] = function (v, ...) if ... ~= nil then throw("vector? expect expects 1 args got: " .. 1 + #table.pack(...)) end if v == nil then throw("vector? expect expects 1 args got: 0") end return is_instanceOf(v, Vector) end -core[Sym.new('hash-map')] = Hashmap.new +core['hash-map'] = Hashmap.new -core[Sym.new('keys')] = function (v, ...) +core['keys'] = function (v, ...) if ... ~= nil then throw("keys expect expects 1 args got: " .. 1 + #table.pack(...)) end if v == nil then throw("keys expect expects 1 args got: 0") end if not(is_instanceOf(v, Hashmap)) then throw("keys expects its argument to be HashMap but got:" .. type(v) ) end local res = {} for k,_ in pairs(v) do - table.insert(res, k) + if type(k) == "string" and "\u{29E}" == string.sub(k, 1, 2) then + table.insert(res, Sym.new(k)) + else + table.insert(res, k) + end end return List.new(res) end -core[Sym.new('vals')] = function (v, ...) +core['vals'] = function (v, ...) if ... ~= nil then throw("vals expect expects 1 args got: " .. 1 + #table.pack(...)) end if v == nil then throw("vals expect expects 1 args got: 0") end if not(is_instanceOf(v, Hashmap)) then throw("vals expects its argument to be HashMap but got:" .. type(v) ) end local res = {} - for _,v in pairs(v) do - table.insert(res, k) + for _,val in pairs(v) do + table.insert(res, val) end return List.new(res) end -core[Sym.new('map?')] = function (v, ...) +core['map?'] = function (v, ...) if ... ~= nil then throw("map? expect expects 1 args got: " .. 1 + #table.pack(...)) end if v == nil then throw("map? expect expects 1 args got: 0") end return is_instanceOf(v, Hashmap) end -core[Sym.new('hash-map')] = Hashmap.new +core['hash-map'] = Hashmap.new -core[Sym.new('get')] = function (...) +core['get'] = function (...) local args = table.pack(...) if #args ~= 2 then throw("map? expect expects 2 args got: " .. #args) @@ -351,11 +389,14 @@ core[Sym.new('get')] = function (...) if not(is_instanceOf(map, Hashmap)) then throw("get expects first arg to be hashmap") end + if core['keyword?'](key) then + key = key.val + end - return map[key] and map[key] or Nil + return map[key] and map[key] or Nil end -core[Sym.new('contains?')] = function (...) +core['contains?'] = function (...) local args = table.pack(...) if #args ~= 2 then throw("contains? expect expects 1 args got: " .. #args) @@ -365,11 +406,16 @@ core[Sym.new('contains?')] = function (...) if not(is_instanceOf(map, Hashmap)) then throw("contains? expects first arg to be hashmap") end + if core['keyword?'](key) then + key = key.val + end - return map[key] and true or false + return map[key] and true or false end -core[Sym.new('assoc')] = function (...) + +--fixme every mapping place should check for keywords +core['assoc'] = function (...) local args = table.pack(...) if #args % 2 ~= 1 then throw("assoc expect expects odd number of args got: " .. #args) @@ -383,14 +429,14 @@ core[Sym.new('assoc')] = function (...) res[k] = v end for i=1,#args,2 do - print("associng") res[args[i]] = args[i+1] end return res end -core[Sym.new('dissoc')] = function (...) +--fixme every mapping place should check for keywords +core['dissoc'] = function (...) local args = table.pack(...) if #args ~= 2 then throw("assoc expect expects 2 args got: " .. #args) diff --git a/impls/lua.2/env.lua b/impls/lua.2/env.lua index 414fa5ae81..2efacb39ed 100644 --- a/impls/lua.2/env.lua +++ b/impls/lua.2/env.lua @@ -65,7 +65,7 @@ function Env:get(key) if env then return env.data[key.val] end - throw(string.format("%s not found in any environments.", Printer.stringfy_val(key))) + throw(string.format("'%s' not found.", Printer.stringfy_val(key))) end return Env diff --git a/impls/lua.2/printer.lua b/impls/lua.2/printer.lua index a344153388..6065975c88 100644 --- a/impls/lua.2/printer.lua +++ b/impls/lua.2/printer.lua @@ -38,6 +38,11 @@ function Printer.stringfy_val(val, readably) elseif is_instanceOf(val, HashMap) then res = res .. '{' for i,v in pairs(val) do + if type(i) == "string" and + "\u{29E}" == string.sub(i,1,2) then + i = Sym.new(i) + end + res = res .. Printer.stringfy_val(i, readably) .. " " .. Printer.stringfy_val(v,readably) res = res .. " " end @@ -47,6 +52,7 @@ function Printer.stringfy_val(val, readably) res = res .. '}' elseif is_instanceOf(val, Sym) then + if "\u{29E}" == string.sub(val.val, 1, 2) then return ":" .. string.sub(val.val, 3) end return val.val elseif is_instanceOf(val, Err) then return "exc is: " .. Scanner.escape(val.val) diff --git a/impls/lua.2/step9_try.lua b/impls/lua.2/step9_try.lua index f013cde151..90122defc4 100644 --- a/impls/lua.2/step9_try.lua +++ b/impls/lua.2/step9_try.lua @@ -270,7 +270,7 @@ function eval_ast(ast, env) elseif is_instanceOf(ast, Sym) then if string.byte(ast.val, 1, 1) == 202 and string.byte(ast.val, 2, 2) == 158 then -- this magic numbers come from \u{29E} - return Sym.new(":" .. string.sub(ast.val, 3, #ast.val)) + return ast end return env:get(ast) @@ -288,7 +288,7 @@ end local repl_env = Env.new(nil) for k,v in pairs(core) do - repl_env:set(k,v) + repl_env:set(Sym.new(k),v) end repl_env:set(Sym.new('eval'), function (ast) diff --git a/impls/lua.2/types.lua b/impls/lua.2/types.lua index bc230c06fc..f510fb07ca 100644 --- a/impls/lua.2/types.lua +++ b/impls/lua.2/types.lua @@ -12,7 +12,13 @@ function M.MalHashMap.new(...) local self = {} setmetatable(self, M.MalHashMap) for i= 1, #arg, 2 do - self[arg[i]] = arg[i+1] + if M.isinstanceof(arg[i], M.Sym) and "\u{29E}" == string.sub(arg[i].val,1,2) then + self[arg[i].val] = arg[i+1] + else + + self[arg[i]] = arg[i+1] + end + end return self From 7074e32705dc334f99c14d68c6bc17d23e5c3441 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=BCseyin=20Er?= Date: Sat, 25 Feb 2023 12:48:36 +0300 Subject: [PATCH 18/25] step9 wip keywords converted to string w/ prefixes --- impls/lua.2/core.lua | 37 ++++++++++--------------------------- impls/lua.2/printer.lua | 8 ++------ impls/lua.2/scanner.lua | 2 +- impls/lua.2/types.lua | 2 +- 4 files changed, 14 insertions(+), 35 deletions(-) diff --git a/impls/lua.2/core.lua b/impls/lua.2/core.lua index b1b1dd6b26..d835d32364 100644 --- a/impls/lua.2/core.lua +++ b/impls/lua.2/core.lua @@ -286,13 +286,13 @@ end core['symbol?'] = function (v, ...) if ... ~= nil then throw("symbol? expect expects 1 args got: " .. 1 + #table.pack(...)) end if v == nil then throw("symbol? expect expects 1 args got: 0") end - return is_instanceOf(v, Sym) and not( "\u{29E}" == string.sub(v.val,1,2)) + return is_instanceOf(v, Sym) end core['keyword?'] = function (v, ...) if ... ~= nil then throw("keyword? expect expects 1 args got: " .. 1 + #table.pack(...)) end if v == nil then throw("keyword? expect expects 1 args got: 0") end - return is_instanceOf(v, Sym) and "\u{029e}" == string.sub(v.val,1,2) + return type(v) == "string" and "\u{029e}" == string.sub(v,1,2) end @@ -308,7 +308,7 @@ core['keyword'] = function (...) if not( type(val) == "string") then throw("keyword expects string or keyword type") end - return Sym.new("\u{029e}" .. val) + return "\u{029e}" .. val end @@ -347,11 +347,7 @@ core['keys'] = function (v, ...) local res = {} for k,_ in pairs(v) do - if type(k) == "string" and "\u{29E}" == string.sub(k, 1, 2) then - table.insert(res, Sym.new(k)) - else - table.insert(res, k) - end + table.insert(res, k) end return List.new(res) @@ -382,16 +378,13 @@ core['hash-map'] = Hashmap.new core['get'] = function (...) local args = table.pack(...) if #args ~= 2 then - throw("map? expect expects 2 args got: " .. #args) + throw("get expect expects 2 args got: " .. #args) end local map = args[1] local key = args[2] if not(is_instanceOf(map, Hashmap)) then throw("get expects first arg to be hashmap") end - if core['keyword?'](key) then - key = key.val - end return map[key] and map[key] or Nil end @@ -406,15 +399,11 @@ core['contains?'] = function (...) if not(is_instanceOf(map, Hashmap)) then throw("contains? expects first arg to be hashmap") end - if core['keyword?'](key) then - key = key.val - end - + return map[key] and true or false end ---fixme every mapping place should check for keywords core['assoc'] = function (...) local args = table.pack(...) if #args % 2 ~= 1 then @@ -435,24 +424,18 @@ core['assoc'] = function (...) return res end ---fixme every mapping place should check for keywords core['dissoc'] = function (...) local args = table.pack(...) - if #args ~= 2 then - throw("assoc expect expects 2 args got: " .. #args) + if #args < 2 then + throw("assoc expect expects at least 2 args got: " .. #args) end - local map = args[1] + local map = table.remove(args,1) if not(is_instanceOf(map, Hashmap)) then throw("assoc expects first arg to be HashMap") end - local list = args[2] - if not(is_instanceOf(list, List)) then - throw("assoc expects second arg to be List") - end - local res = Hashmap.new() for k,v in pairs(map) do - for _, listval in pairs(list) do + for _, listval in ipairs(args) do if k == listval then else res[k] = v diff --git a/impls/lua.2/printer.lua b/impls/lua.2/printer.lua index 6065975c88..fabefb9ba7 100644 --- a/impls/lua.2/printer.lua +++ b/impls/lua.2/printer.lua @@ -38,11 +38,6 @@ function Printer.stringfy_val(val, readably) elseif is_instanceOf(val, HashMap) then res = res .. '{' for i,v in pairs(val) do - if type(i) == "string" and - "\u{29E}" == string.sub(i,1,2) then - i = Sym.new(i) - end - res = res .. Printer.stringfy_val(i, readably) .. " " .. Printer.stringfy_val(v,readably) res = res .. " " end @@ -52,7 +47,6 @@ function Printer.stringfy_val(val, readably) res = res .. '}' elseif is_instanceOf(val, Sym) then - if "\u{29E}" == string.sub(val.val, 1, 2) then return ":" .. string.sub(val.val, 3) end return val.val elseif is_instanceOf(val, Err) then return "exc is: " .. Scanner.escape(val.val) @@ -63,6 +57,8 @@ function Printer.stringfy_val(val, readably) res = "(atom " .. Printer.stringfy_val(val.val) .. ")" elseif type(val) == "string" then + if "\u{29E}" == string.sub(val, 1, 2) then + return ":" .. string.sub(val, 3) end if readably then res = Scanner.unescape(val) else diff --git a/impls/lua.2/scanner.lua b/impls/lua.2/scanner.lua index 9b06c2eb30..cd8d90c451 100644 --- a/impls/lua.2/scanner.lua +++ b/impls/lua.2/scanner.lua @@ -177,7 +177,7 @@ function Scanner.scanToken(self) self:advance() end local val = "\u{29E}" .. string.sub(self.source, self.start+1, self.index-1) - table.insert(self.tokens, Token("SYM", val, self.line)) + table.insert(self.tokens, Token("STR", val, self.line)) elseif not self.is_special(char) then while not(self.is_special(self:peek())) do self:advance() diff --git a/impls/lua.2/types.lua b/impls/lua.2/types.lua index f510fb07ca..946c88fd4b 100644 --- a/impls/lua.2/types.lua +++ b/impls/lua.2/types.lua @@ -121,7 +121,7 @@ function M.is_equal(a, b) return false end end return true - elseif M.isinstanceof(a, M.HashMap) and M.isinstanceof(b, M.HashMap) then + elseif M.isinstanceof(a, M.MalHashMap) and M.isinstanceof(b, M.MalHashMap) then for k,v in pairs(a) do if not ( M.is_equal(a[k],b[k])) then return false From 1632e1f75d22b27188c7da2793fc0599a0bf3d29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=BCseyin=20Er?= Date: Sun, 26 Feb 2023 08:50:25 +0300 Subject: [PATCH 19/25] step9 all test passes --- impls/lua.2/core.lua | 26 ++++++++++++------------ impls/lua.2/env.lua | 2 +- impls/lua.2/printer.lua | 2 +- impls/lua.2/step4_if_fn_do.lua | 2 +- impls/lua.2/step5_tco.lua | 2 +- impls/lua.2/step6_file.lua | 2 +- impls/lua.2/step7_quote.lua | 2 +- impls/lua.2/step8_macros.lua | 2 +- impls/lua.2/step9_try.lua | 36 ++++++++++++++++++++++------------ 9 files changed, 42 insertions(+), 34 deletions(-) diff --git a/impls/lua.2/core.lua b/impls/lua.2/core.lua index d835d32364..0119f90425 100644 --- a/impls/lua.2/core.lua +++ b/impls/lua.2/core.lua @@ -33,7 +33,7 @@ core['str'] = function (...) return res end - core['prn'] = function (...) +core['prn'] = function (...) local res = "" local args = table.pack(...) for i,v in ipairs(args) do @@ -180,7 +180,7 @@ core['nth'] = function (v, idx, ...) throw("second argument to nth should be number") end if idx > #v - 1 or idx < 0 then - throw("invalid index") + throw("index out of range") end return v[idx+1] or Nil end @@ -207,13 +207,10 @@ end core['throw'] = function (v, ...) if ... ~= nil then throw("throw expect expects 1 args got: " .. 1 + #table.pack(...)) end if v == nil then - return Err.new("") + throw("") end - if false and not(type(v) == "string" or v == Nil) then - throw("first argument to throw should be string or nil") - end - - return Err.new(Printer.stringfy_val(v)) + + throw(v) end --fixme @@ -382,8 +379,8 @@ core['get'] = function (...) end local map = args[1] local key = args[2] - if not(is_instanceOf(map, Hashmap)) then - throw("get expects first arg to be hashmap") + if not(is_instanceOf(map, Hashmap) or map == Nil) then + throw("get expects first arg to be hashmap or nil") end return map[key] and map[key] or Nil @@ -435,15 +432,16 @@ core['dissoc'] = function (...) end local res = Hashmap.new() for k,v in pairs(map) do + local keep = true for _, listval in ipairs(args) do if k == listval then - else - res[k] = v + keep = false end end + if keep then + res[k] = v + end end - - return res end diff --git a/impls/lua.2/env.lua b/impls/lua.2/env.lua index 2efacb39ed..e1cbb80aac 100644 --- a/impls/lua.2/env.lua +++ b/impls/lua.2/env.lua @@ -65,7 +65,7 @@ function Env:get(key) if env then return env.data[key.val] end - throw(string.format("'%s' not found.", Printer.stringfy_val(key))) + throw(string.format("'%s' not found", Printer.stringfy_val(key))) end return Env diff --git a/impls/lua.2/printer.lua b/impls/lua.2/printer.lua index fabefb9ba7..edc353117b 100644 --- a/impls/lua.2/printer.lua +++ b/impls/lua.2/printer.lua @@ -49,7 +49,7 @@ function Printer.stringfy_val(val, readably) elseif is_instanceOf(val, Sym) then return val.val elseif is_instanceOf(val, Err) then - return "exc is: " .. Scanner.escape(val.val) + return Printer.stringfy_val(val.val, true) elseif is_instanceOf(val, Function) then res = "(fn* " .. Printer.stringfy_val(val.params) .. "-->" .. Printer.stringfy_val(val.ast) ..")" .. "ismacro: " .. tostring(val.is_macro) diff --git a/impls/lua.2/step4_if_fn_do.lua b/impls/lua.2/step4_if_fn_do.lua index 581d372d1f..547937f279 100644 --- a/impls/lua.2/step4_if_fn_do.lua +++ b/impls/lua.2/step4_if_fn_do.lua @@ -156,7 +156,7 @@ end local repl_env = Env.new(nil) for k,v in pairs(core) do - repl_env:set(k,v) + repl_env:set(Sym.new(k),v) end diff --git a/impls/lua.2/step5_tco.lua b/impls/lua.2/step5_tco.lua index 243fdaf2a2..c82b536cdb 100644 --- a/impls/lua.2/step5_tco.lua +++ b/impls/lua.2/step5_tco.lua @@ -166,7 +166,7 @@ end local repl_env = Env.new(nil) for k,v in pairs(core) do - repl_env:set(k,v) + repl_env:set(Sym.new(k),v) end diff --git a/impls/lua.2/step6_file.lua b/impls/lua.2/step6_file.lua index d32b7f852c..03117f3560 100644 --- a/impls/lua.2/step6_file.lua +++ b/impls/lua.2/step6_file.lua @@ -166,7 +166,7 @@ end local repl_env = Env.new(nil) for k,v in pairs(core) do - repl_env:set(k,v) + repl_env:set(Sym.new(k),v) end repl_env:set(Sym.new('eval'), function (ast) diff --git a/impls/lua.2/step7_quote.lua b/impls/lua.2/step7_quote.lua index 46fe338ccf..387b872065 100644 --- a/impls/lua.2/step7_quote.lua +++ b/impls/lua.2/step7_quote.lua @@ -223,7 +223,7 @@ end local repl_env = Env.new(nil) for k,v in pairs(core) do - repl_env:set(k,v) + repl_env:set(Sym.new(k),v) end repl_env:set(Sym.new('eval'), function (ast) diff --git a/impls/lua.2/step8_macros.lua b/impls/lua.2/step8_macros.lua index b23128679b..35780bbef3 100644 --- a/impls/lua.2/step8_macros.lua +++ b/impls/lua.2/step8_macros.lua @@ -265,7 +265,7 @@ end local repl_env = Env.new(nil) for k,v in pairs(core) do - repl_env:set(k,v) + repl_env:set(Sym.new(k),v) end repl_env:set(Sym.new('eval'), function (ast) diff --git a/impls/lua.2/step9_try.lua b/impls/lua.2/step9_try.lua index 90122defc4..af78af7e76 100644 --- a/impls/lua.2/step9_try.lua +++ b/impls/lua.2/step9_try.lua @@ -97,14 +97,28 @@ function macro_expand(ast, env) return ast end -function try(a, c, env) +function try(a, env) + --assert(nil, "try is not refactored yet") + if #a > 2 and #a < 1 then + throw("try expected at 1 or 2 arguments but got '" .. #a .. "'.") + end + if #a == 2 then + if not(is_instanceOf(a[2], List) and #a[2] == 3 and is_instanceOf(a[2][1],Sym) and + a[2][1].val == "catch*" and is_instanceOf(a[2][2],Sym) ) then + throw("try expected 2nd argument as list with 3 elems " .. + "first elem being symbol 'catch*' " .. "second being symbol") + end + end + + local tb = a[1] + local cb = a[2] or List.new({ nil , Sym.new('_'), Sym.new('_')}) local status, val = pcall( function () - return EVAL(a, env) + return EVAL(tb, env) end ) if not status then local env_with_ex = Env.new(env) - env_with_ex:set(c[2],val) - return EVAL(c[3], env_with_ex) + env_with_ex:set(cb[2],val) + return EVAL(cb[3], env_with_ex) end return val end @@ -123,6 +137,7 @@ function EVAL(a, env) end local first_elem = a[1] local first_sym = is_instanceOf(first_elem, Sym) and first_elem.val or "" + --print("First symbol= '" .. first_sym .. "'") if first_sym == "def!" then if #a ~= 3 then @@ -219,13 +234,8 @@ function EVAL(a, env) if (#a) ~= 2 then throw("macroexpand expected 1 arguments but got '" .. #a-1 .. "'.") end return macro_expand(a[2],env) elseif first_sym == "try*" then - if (#a) ~= 3 then throw("try expected 2 arguments but got '" .. #a-1 .. "'.") end - if not( is_instanceOf(a[3], List) and - #a[3] == 3 and is_instanceOf(a[3][1],Sym) and a[3][1].val == "catch*") then - throw("try expected 2nd argument as list with 3 elems" .. - "first elem being symbol 'catch*'") - end - return try(a[2], a[3], env) + table.remove(a,1) + return try(a, env) else @@ -322,9 +332,9 @@ rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if ( local status, err = pcall(function () print(rep(line)) end) if not status then if is_instanceOf(err, Err) then - err = Printer.stringfy_val(err) + err = Printer.stringfy_val(err, true) end - print(err) + print("Error: " .. err) print(debug.traceback()) end end From 6b9775c75ad857bc59a49db0ae75135aa5b33f04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=BCseyin=20Er?= Date: Thu, 2 Mar 2023 20:24:40 +0300 Subject: [PATCH 20/25] stepA WIP --- impls/lua.2/core.lua | 83 +++++++++- impls/lua.2/printer.lua | 2 +- impls/lua.2/stepA_mal.lua | 335 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 418 insertions(+), 2 deletions(-) create mode 100755 impls/lua.2/stepA_mal.lua diff --git a/impls/lua.2/core.lua b/impls/lua.2/core.lua index 0119f90425..4d9237c804 100644 --- a/impls/lua.2/core.lua +++ b/impls/lua.2/core.lua @@ -210,6 +210,7 @@ core['throw'] = function (v, ...) throw("") end + throw(v) end @@ -389,7 +390,7 @@ end core['contains?'] = function (...) local args = table.pack(...) if #args ~= 2 then - throw("contains? expect expects 1 args got: " .. #args) + throw("contains? expect expects 2 args got: " .. #args) end local map = args[1] local key = args[2] @@ -445,6 +446,86 @@ core['dissoc'] = function (...) return res end +core['readline'] = function (...) + local args = table.pack(...) + if #args ~= 1 then + throw("readline expect expects 1 args got: " .. #args) + end + local prompt = args[1] + if type(prompt) ~= "string" then + throw("readline expects first arg to be string") + end + io.write(prompt) + local v = io.read() + if v == nil then + io.write('\n') + end + return v or Nil +end + +core['time-ms'] = function (...) + assert(nil, 'time-ms not implemented yet') +end +core['meta'] = function (...) + local args = table.pack(...) + if #args ~= 1 then + throw("fn? expect expects 1 args got: " .. #args) + end + + local m = getmetatable(obj) + if m == nil or m.meta == nil then return Nil end + return m.meta +end + +core['with-meta'] = function (...) + local new_obj = types.copy(obj) + getmetatable(new_obj).meta = meta + return new_obj +end + +core['fn?'] = function (...) + local args = table.pack(...) + if #args ~= 1 then + throw("fn? expect expects 1 args got: " .. #args) + end + return is_instanceOf(args[1],Function) or type(args[1]) == "function" + +end + +core['number?'] = function (...) + local args = table.pack(...) + if #args ~= 1 then + throw("number? expect expects 1 args got: " .. #args) + end + return type(args[1]) == "number" +end + +core['string?'] = function (...) + local args = table.pack(...) + if #args ~= 1 then + throw("string? expect expects 1 args got: " .. #args) + end + return type(args[1]) == "string" +end + +core['macro?'] = function (...) + local args = table.pack(...) + if #args ~= 1 then + throw("macro? expect expects 1 args got: " .. #args) + end + return is_instanceOf(args[1], Function) and args[1].is_macro +end + + +core['seq'] = function (...) + assert(nil, 'seq not implemented yet') +end + + +core['conj'] = function (...) + assert(nil, 'conj not implemented yet') +end + diff --git a/impls/lua.2/printer.lua b/impls/lua.2/printer.lua index edc353117b..1bc1a7a1fe 100644 --- a/impls/lua.2/printer.lua +++ b/impls/lua.2/printer.lua @@ -49,7 +49,7 @@ function Printer.stringfy_val(val, readably) elseif is_instanceOf(val, Sym) then return val.val elseif is_instanceOf(val, Err) then - return Printer.stringfy_val(val.val, true) + return Printer.stringfy_val(val.val,readably) elseif is_instanceOf(val, Function) then res = "(fn* " .. Printer.stringfy_val(val.params) .. "-->" .. Printer.stringfy_val(val.ast) ..")" .. "ismacro: " .. tostring(val.is_macro) diff --git a/impls/lua.2/stepA_mal.lua b/impls/lua.2/stepA_mal.lua new file mode 100755 index 0000000000..09db063900 --- /dev/null +++ b/impls/lua.2/stepA_mal.lua @@ -0,0 +1,335 @@ +#!/usr/bin/env lua +local Reader = require "reader" +local Printer = require "printer" +local types = require "types" +local Env = require "env" +local Sym = types.Sym +local is_instanceOf = types.isinstanceof +local Err = types.Err +local List = types.MalList +local throw = types.throw +local HashMap = types.MalHashMap +local Vector = types.MalVector +local Nil = types.Nil +local core = require "core" +local Function = types.MalFunction +local readline = core.readline + +function READ(str) + return Reader.read_str(str) +end + +function starts_with(a, v) +return #a > 0 and is_instanceOf(a[1],Sym) and + a[1].val == v +end + + +function quasiloop(a) + local res = List.new({}) + for i=#a,1,-1 do + local elt = a[i] + if is_instanceOf(elt, List) and + starts_with(elt, "splice-unquote") then + + if #elt ~= 2 then throw("splice-unquote expected 1 argument bot got : " .. #elt) end + + res = List.new({Sym.new( "concat"), elt[2], res}) + else + res = List.new({Sym.new( "cons"), quasiquote(elt), res}) + end + end + return res +end + +function quasiquote(a) + + if is_instanceOf(a,List) then + if starts_with(a, "unquote") then + if #a-1 ~= 1 then + throw("unquote expected 1 argument bot got : " .. #a) + end + return a[2] + else + return quasiloop(a) + end + elseif is_instanceOf(a, Vector) then + local tmp = quasiloop(a) + return List.new({Sym.new("vec"), tmp}) + elseif is_instanceOf(a,HashMap) or is_instanceOf(a,Sym) then + return List.new({Sym.new('quote'), a}) + + else + return a + + end +end + +function is_macro_call(ast, env) + if is_instanceOf(ast, List) and #ast >= 1 and is_instanceOf(ast[1], Sym) then + local status, first_env = pcall( function () return env:get(ast[1]) end) + if not status then return false end + if is_instanceOf(first_env, Function) and first_env.is_macro then + return true + else + return false + end + else + return false + end + +end + +function macro_expand(ast, env) + while is_macro_call(ast, env) do + local macro = env:get(ast[1]) + local f = macro.fn + ast = f(table.unpack(ast, 2)) + end + + return ast +end + +function try(a, env) + --assert(nil, "try is not refactored yet") + if #a > 2 and #a < 1 then + throw("try expected at 1 or 2 arguments but got '" .. #a .. "'.") + end + if #a == 2 then + if not(is_instanceOf(a[2], List) and #a[2] == 3 and is_instanceOf(a[2][1],Sym) and + a[2][1].val == "catch*" and is_instanceOf(a[2][2],Sym) ) then + throw("try expected 2nd argument as list with 3 elems " .. + "first elem being symbol 'catch*' " .. "second being symbol") + end + end + + local tb = a[1] + local cb = a[2] or List.new({ nil , Sym.new('_'), Sym.new('_')}) + local status, val = pcall( function () + return EVAL(tb, env) + end ) + if not status then + local env_with_ex = Env.new(env) + env_with_ex:set(cb[2],val) + return EVAL(cb[3], env_with_ex) + end + return val +end + + + +function EVAL(a, env) + while true do + a = macro_expand(a, env) + if not(is_instanceOf(a, List)) then + return eval_ast(a, env) + end + + if #a == 0 then + return a + end + local first_elem = a[1] + local first_sym = is_instanceOf(first_elem, Sym) and first_elem.val or "" + --print("First symbol= '" .. first_sym .. "'") + + if first_sym == "def!" then + if #a ~= 3 then + throw(string.format("def! expects 2 arguments got: %d", #a-1)) + end + if not(is_instanceOf(a[2], Sym)) then + throw("first argument to def! must be symbol") + end + local value = EVAL(a[3], env) + env:set(a[2], value) + return value + + elseif first_sym == "let*" then + if #a ~= 3 then + throw(string.format("let* expects 2 arguments got: %d", #a-1)) + end + + local let_env = Env.new(env) + if not(is_instanceOf(a[2], List) or is_instanceOf(a[2], Vector)) then + throw("Second arg to let* should be list or vector") + end + if #a[2] % 2 ~= 0 then + throw(string.format("Length ofSecond arg to let* should be even number got: %d", #a[2])) + end + + for i=1,#a[2],2 do + if not(is_instanceOf(a[2][i], Sym)) then + throw("Expected symbol in let*'s second argument") + end + local key = a[2][i] + local value = EVAL(a[2][i+1],let_env) + let_env:set(key,value) + end + a = a[3] + env = let_env + + + elseif first_sym == "do" then + for i=2,#a-1 do EVAL(a[i],env) end + a = a[#a] --tco + + + elseif first_sym == "if" then + if not (#a == 3 or #a == 4) then + throw("if expected 2 or 3 arguments but got '" .. #a-1 .. "'.") + end + local cond = EVAL(a[2], env) + if cond ~= false and cond ~= Nil then + a = a[3] + else + if #a == 4 then + a = a[4] + else + return Nil + end + end + elseif first_sym == "fn*" then + if (#a) ~= 3 then throw("fn* expected 2 arguments but got '" .. #a-1 .. "'.") end + if false then throw("second parameter to fn* should have length 2 but got '" .. #a[2] .. "'.") end + return Function.new(function (...) + local closed_over_env = Env.new(env) + local exprs = List.new(table.pack(...)) + local binds = a[2] + closed_over_env:bind(binds, exprs) + + return EVAL(a[3], closed_over_env) + end, a[3], env, a[2], false) + + elseif first_sym == "quote" then + if #a-1 ~= 1 then throw("quote expects 1 argument got '" .. #a-1 .. "'.") end + return a[2] + elseif first_sym == "quasiquote" then + if #a-1 ~= 1 then throw("quote expects 1 argument got '" .. #a-1 .. "'.") end + a = quasiquote(a[2]) + elseif first_sym == "defmacro!" then + if #a ~= 3 then + throw(string.format("defmacro! expects 2 arguments got: %d", #a-1)) + end + if not(is_instanceOf(a[2], Sym)) then + throw("first argument to defmacro! must be symbol") + end + local value = EVAL(a[3], env) + if not(is_instanceOf(value, Function)) then + throw("second argument to defmacro must be function") + end + value.is_macro = true + env:set(a[2], value) + return value + + + elseif first_sym == "macroexpand" then + if (#a) ~= 2 then throw("macroexpand expected 1 arguments but got '" .. #a-1 .. "'.") end + return macro_expand(a[2],env) + elseif first_sym == "try*" then + table.remove(a,1) + return try(a, env) + + else + + local args = eval_ast(a, env) + local f = table.remove(args,1) + if types.is_malfunc(f) then + a = f.ast + env = Env.new(f.env) + env:bind(f.params, args) + + else + if type(f) ~= "function" then + throw("First elem should be function or special form got :'" .. type(f) .. "'.") + end + return f(table.unpack(args)) --fixme: varargs? + end + end + +end +end + + +function eval_ast(ast, env) + if is_instanceOf(ast, List) then + local l = List.new() + for i=1,#ast do + table.insert(l, EVAL(ast[i], env)) + end + return l + elseif is_instanceOf(ast, Vector) then + local v = Vector.new() + for i=1, #ast do + table.insert(v, EVAL(ast[i], env)) + end + return v + elseif is_instanceOf(ast, HashMap) then + local map = HashMap.new() + for k,v in pairs(ast) do + map[EVAL(k, env)] = EVAL(v, env) + end + return map + elseif is_instanceOf(ast, Sym) then + if string.byte(ast.val, 1, 1) == 202 and + string.byte(ast.val, 2, 2) == 158 then -- this magic numbers come from \u{29E} + return ast + end + return env:get(ast) + + else + return ast + end +end + + +function PRINT(a) + return Printer.stringfy_val(a, true) +end + + +local repl_env = Env.new(nil) + +for k,v in pairs(core) do + repl_env:set(Sym.new(k),v) +end + +repl_env:set(Sym.new('eval'), function (ast) + return EVAL(ast, repl_env) +end) + + +function rep(str) + return PRINT(EVAL(READ(str), repl_env)) +end + + + +function main() + rep("(def! not (fn* (a) (if a false true)))") + rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \"(slurp f) \"\nnil)\")))))") +rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))") + repl_env:set(Sym.new("*ARGV*"), List.new(table.pack(table.unpack(arg,2)))) + repl_env:set(Sym.new("*host-language*"), "lua") + if #arg > 0 then + local file_to_run = table.remove(arg,1) + rep("(load-file \"" .. file_to_run .. "\")") + os.exit(0) + end + rep("(println (str \"Mal [\" *host-language* \"]\"))") + local line = '' + while true do + line = readline('user> ') + if line == Nil then + break + end + local status, err = pcall(function () print(rep(line)) end) + if not status then + if is_instanceOf(err, Err) then + err = Printer.stringfy_val(err, true) + end + print("Error: " .. err) + print(debug.traceback()) + end + end +end + +main() From 8cf61e5c2d6ae984746b401c41fd52e55551f20b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=BCseyin=20Er?= Date: Sat, 18 Mar 2023 21:27:49 +0300 Subject: [PATCH 21/25] fixed a bug in try which modifies ast --- impls/lua.2/env.lua | 4 +--- impls/lua.2/stepA_mal.lua | 19 ++++++++++++++----- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/impls/lua.2/env.lua b/impls/lua.2/env.lua index e1cbb80aac..fecf64dc15 100644 --- a/impls/lua.2/env.lua +++ b/impls/lua.2/env.lua @@ -1,5 +1,4 @@ local types = require "types" -local Printer = require "printer" local throw = types.throw local Sym = types.Sym local List = types.MalList @@ -30,7 +29,6 @@ function Env:bind(binds, exprs) end if b.val ~= '&' then self.data[b.val] = exprs[i] - --print(b.val .. ":" .. Printer.stringfy_val(exprs[i]) ) else if i == #binds or not(is_instanceOf(binds[i+1],Sym)) then throw("Symbol '&' should be followed by an another symbol") @@ -65,7 +63,7 @@ function Env:get(key) if env then return env.data[key.val] end - throw(string.format("'%s' not found", Printer.stringfy_val(key))) + throw(string.format("'%s' not found", key.val)) end return Env diff --git a/impls/lua.2/stepA_mal.lua b/impls/lua.2/stepA_mal.lua index 09db063900..ef9dfa4a2c 100755 --- a/impls/lua.2/stepA_mal.lua +++ b/impls/lua.2/stepA_mal.lua @@ -66,7 +66,7 @@ function quasiquote(a) end function is_macro_call(ast, env) - if is_instanceOf(ast, List) and #ast >= 1 and is_instanceOf(ast[1], Sym) then + if is_instanceOf(ast, List) and #ast >= 1 and is_instanceOf(ast[1], Sym) then local status, first_env = pcall( function () return env:get(ast[1]) end) if not status then return false end if is_instanceOf(first_env, Function) and first_env.is_macro then @@ -92,6 +92,7 @@ end function try(a, env) --assert(nil, "try is not refactored yet") + a = table.pack(table.unpack(a,2)) -- removing try* symbol if #a > 2 and #a < 1 then throw("try expected at 1 or 2 arguments but got '" .. #a .. "'.") end @@ -131,7 +132,6 @@ function EVAL(a, env) local first_elem = a[1] local first_sym = is_instanceOf(first_elem, Sym) and first_elem.val or "" --print("First symbol= '" .. first_sym .. "'") - if first_sym == "def!" then if #a ~= 3 then throw(string.format("def! expects 2 arguments got: %d", #a-1)) @@ -225,13 +225,12 @@ function EVAL(a, env) if (#a) ~= 2 then throw("macroexpand expected 1 arguments but got '" .. #a-1 .. "'.") end return macro_expand(a[2],env) elseif first_sym == "try*" then - table.remove(a,1) return try(a, env) else local args = eval_ast(a, env) - local f = table.remove(args,1) + local f = table.remove(args,1) if types.is_malfunc(f) then a = f.ast env = Env.new(f.env) @@ -311,7 +310,17 @@ rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if ( repl_env:set(Sym.new("*host-language*"), "lua") if #arg > 0 then local file_to_run = table.remove(arg,1) - rep("(load-file \"" .. file_to_run .. "\")") + local status, err = pcall(function () + rep("(load-file \"" .. file_to_run .. "\")") + end) + if not status then + if is_instanceOf(err, Err) then + err = Printer.stringfy_val(err, true) + end + print("Error: " .. err) + print(debug.traceback()) + end + os.exit(0) end rep("(println (str \"Mal [\" *host-language* \"]\"))") From 38db8ccdadf3b46ffebee66c3402eee6c507ce91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=BCseyin=20Er?= Date: Thu, 30 Mar 2023 13:34:42 +0300 Subject: [PATCH 22/25] Bug fix in scanner, increased builtins, dockerfile Internal representation of strings obtained via not escaping during scanning because escaping mutates the string which causes second read of string gives different result. `time`, `meta`, `with-meta` builtins are added. Dockerfile and READMEs updated/added. --- README.md | 17 ++++++++ docs/graph/base_data.yaml | 1 + impls/lua.2/Dockerfile | 33 +++++++++++++++ impls/lua.2/README.md | 15 +++++++ impls/lua.2/core.lua | 29 ++++++++++--- impls/lua.2/printer.lua | 2 +- impls/lua.2/reader.lua | 6 +-- impls/lua.2/scanner.lua | 5 +-- impls/lua.2/stepA_mal.lua | 85 +++++++++++++++++++++------------------ impls/lua.2/types.lua | 45 +++++++++++++++++++++ 10 files changed, 186 insertions(+), 52 deletions(-) create mode 100644 impls/lua.2/Dockerfile create mode 100644 impls/lua.2/README.md diff --git a/README.md b/README.md index 3fa1c13df2..cee3bd3a7a 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,7 @@ FAQ](docs/FAQ.md) where I attempt to answer some common questions. | [LiveScript](#livescript) | [Jos van Bakel](https://github.com/c0deaddict) | | [Logo](#logo) | [Dov Murik](https://github.com/dubek) | | [Lua](#lua) | [Joel Martin](https://github.com/kanaka) | +| [Lua #2](#lua-2) | [Hüseyin Er](https://github.com/chemindefer) | | [GNU Make](#gnu-make-381) | [Joel Martin](https://github.com/kanaka) | | [mal itself](#mal) | [Joel Martin](https://github.com/kanaka) | | [MATLAB](#matlab-gnu-octave-and-matlab) (GNU Octave & MATLAB) | [Joel Martin](https://github.com/kanaka) | @@ -715,6 +716,22 @@ make # to build and link linenoise.so and rex_pcre.so ./stepX_YYY.lua ``` +### Lua.2 + +The second Lua implementation of mal has been tested with Lua 5.3 and +5.4 this implementation does not include readline functionality. In +order to have it one can use `rlwrap`. This implementation uses manual +tokenizer instead of using regular expressions. + +``` +cd impls/lua.2 +./stepX_YYY.lua +# with readline functionality +rlwrap ./stepX_YYY.lua +# with readline functionality +rlwrap ./stepX_YYY.lua +``` + ### Mal Running the mal implementation of mal involves running stepA of one of diff --git a/docs/graph/base_data.yaml b/docs/graph/base_data.yaml index 3ff703d35a..4648a3f47c 100644 --- a/docs/graph/base_data.yaml +++ b/docs/graph/base_data.yaml @@ -43,6 +43,7 @@ languages: - [livescript , LiveScript , ML , Dynamic , []] - [logo , Logo , OTHER , Dynamic , []] - [lua , Lua , Algol , Dynamic , []] + - [lua.2 , Lua , Algol , Dynamic , []] - [make , GNU Make , OTHER , OTHER , []] - [mal , mal itself , Lisp , Dynamic , []] - [matlab , MATLAB , Algol , Dynamic , []] diff --git a/impls/lua.2/Dockerfile b/impls/lua.2/Dockerfile new file mode 100644 index 0000000000..a94c444088 --- /dev/null +++ b/impls/lua.2/Dockerfile @@ -0,0 +1,33 @@ +FROM ubuntu:18.04 +MAINTAINER Hüseyin Er + +########################################################## +# General requirements for testing or common across many +# implementations +########################################################## + +RUN apt-get -y update + +# Required for running tests +RUN apt-get -y install make python + +# Some typical implementation and test requirements +RUN apt-get -y install curl +RUN mkdir -p /mal +WORKDIR /mal + +########################################################## +# Specific implementation requirements +########################################################## +# rlwrap for readline program +RUN apt-get -y install rlwrap +# Lua +RUN apt-get -y install gcc libreadline-dev unzip + +RUN \ +curl -R -O http://www.lua.org/ftp/lua-5.3.5.tar.gz && \ +tar -zxf lua-5.3.5.tar.gz && \ +cd lua-5.3.5 && \ +make linux test && \ +make install + diff --git a/impls/lua.2/README.md b/impls/lua.2/README.md new file mode 100644 index 0000000000..b9e92b011b --- /dev/null +++ b/impls/lua.2/README.md @@ -0,0 +1,15 @@ +# Second lua implementation of [mal](https://github.com/kanaka/mal) +Major difference from first lua implementation is tokenization is done manually instead of using regular expressions. + +# requirements +* lua 5.3 or greater +* rlwrap (optional) for readline editing [rlwrap](https: + //github.com/hanslub42/rlwrap) +```console +# for repl with a readline +rlwrap ./stepA_mal.lua +# for repl +./stepA_mal.lua +# for evaluating script file +./stepA_mal.lua +``` diff --git a/impls/lua.2/core.lua b/impls/lua.2/core.lua index 4d9237c804..def406e341 100644 --- a/impls/lua.2/core.lua +++ b/impls/lua.2/core.lua @@ -6,6 +6,7 @@ local Sym = types.Sym local Vector = types.MalVector local is_instanceOf = types.isinstanceof local is_sequence = types.is_sequence +local is_func = types.is_func local Function = types.MalFunction local Nil = types.Nil local throw = types.throw @@ -232,7 +233,6 @@ core['map'] = function (f, seq, ...) end local acc = {} for k,v in ipairs(seq) do - --print("iterating " .. Printer.stringfy_val(k) .."--".. Printer.stringfy_val(v)) table.insert(acc, f(v)) end return List.new(acc) @@ -464,21 +464,38 @@ core['readline'] = function (...) end core['time-ms'] = function (...) - assert(nil, 'time-ms not implemented yet') + return math.floor(os.clock()*1000000) end +-- TODO: handle built-in functions meta and with-meta core['meta'] = function (...) local args = table.pack(...) if #args ~= 1 then - throw("fn? expect expects 1 args got: " .. #args) + throw("meta expect expects 1 args got: " .. #args) + end + local first = args[1] + if not(is_instanceOf(first, Hashmap) or is_sequence(first) or is_func(first)) then + throw("argument to meta should be one of {hashmap, list, vector, function}." .. type(first)) end - local m = getmetatable(obj) - if m == nil or m.meta == nil then return Nil end + local m = getmetatable(first) + if m == nil or m.meta == nil then + return Nil + end return m.meta end core['with-meta'] = function (...) - local new_obj = types.copy(obj) + local args = table.pack(...) + if #args ~= 2 then + throw("with-meta expect expects 2 args got: " .. #args) + end + local first = args[1] + local meta = args[2] + if not(is_instanceOf(first, Hashmap) or is_sequence(first) or is_func(first)) then + throw("argument to with-meta should be one of {hashmap, list, vector, function}. got: " .. type(first)) + end + + local new_obj = types.copy(first) getmetatable(new_obj).meta = meta return new_obj end diff --git a/impls/lua.2/printer.lua b/impls/lua.2/printer.lua index 1bc1a7a1fe..6e6a9b1e5e 100644 --- a/impls/lua.2/printer.lua +++ b/impls/lua.2/printer.lua @@ -62,7 +62,7 @@ function Printer.stringfy_val(val, readably) if readably then res = Scanner.unescape(val) else - res = Scanner.escape(val) + res = val end elseif type(val) == "number" then res = tostring(val) diff --git a/impls/lua.2/reader.lua b/impls/lua.2/reader.lua index a10a543fa9..7b8c01c28a 100644 --- a/impls/lua.2/reader.lua +++ b/impls/lua.2/reader.lua @@ -1,6 +1,7 @@ local Reader = {} Reader.__index = Reader +local Scanner = require "scanner" local types = require "types" local List = types.MalList @@ -38,7 +39,6 @@ end -local Scanner = require "scanner" function Reader.read_form(self) local tok = self:peek() @@ -110,8 +110,8 @@ function Reader.read_seq(self, opening, closing, invalids) local res = {} while tok.typeof ~= closing do if tok.typeof == "EOF" then - print("Error: unexpected EOF before matching '" .. closing .. "'.") - return {} + throw("Error: unexpected EOF before matching '" .. closing .. "'at line:" .. tok.line) + end diff --git a/impls/lua.2/scanner.lua b/impls/lua.2/scanner.lua index cd8d90c451..75e9ebd797 100644 --- a/impls/lua.2/scanner.lua +++ b/impls/lua.2/scanner.lua @@ -120,10 +120,9 @@ function Scanner.string(self) throw(string.format("Error unbalanced string at line %d", self.line)) end - self:advance() -- closing " - -- trimmed opening and closing " - local val = self.escape(string.sub(self.source, self.start+1, self.index-2)) + local val = Scanner.escape(string.sub(self.source, self.start+1, self.index-1)) + self:advance() -- closing " table.insert(self.tokens, Token("STR", val, self.line)) diff --git a/impls/lua.2/stepA_mal.lua b/impls/lua.2/stepA_mal.lua index ef9dfa4a2c..0062d8c318 100755 --- a/impls/lua.2/stepA_mal.lua +++ b/impls/lua.2/stepA_mal.lua @@ -13,60 +13,64 @@ local Vector = types.MalVector local Nil = types.Nil local core = require "core" local Function = types.MalFunction +local FunctionRef = types.FunctionRef local readline = core.readline function READ(str) - return Reader.read_str(str) + return Reader.read_str(str) end function starts_with(a, v) -return #a > 0 and is_instanceOf(a[1],Sym) and - a[1].val == v + return #a > 0 and is_instanceOf(a[1],Sym) and + a[1].val == v end function quasiloop(a) local res = List.new({}) - for i=#a,1,-1 do - local elt = a[i] - if is_instanceOf(elt, List) and - starts_with(elt, "splice-unquote") then + for i=#a,1,-1 do + local elt = a[i] + if is_instanceOf(elt, List) and + starts_with(elt, "splice-unquote") then - if #elt ~= 2 then throw("splice-unquote expected 1 argument bot got : " .. #elt) end - - res = List.new({Sym.new( "concat"), elt[2], res}) - else - res = List.new({Sym.new( "cons"), quasiquote(elt), res}) + if #elt ~= 2 then + throw("splice-unquote expected 1 argument bot got : " .. #elt) end + + res = List.new({Sym.new( "concat"), elt[2], res}) + else + res = List.new({Sym.new( "cons"), quasiquote(elt), res}) end - return res + end + return res end function quasiquote(a) - if is_instanceOf(a,List) then - if starts_with(a, "unquote") then - if #a-1 ~= 1 then - throw("unquote expected 1 argument bot got : " .. #a) - end - return a[2] - else - return quasiloop(a) - end - elseif is_instanceOf(a, Vector) then - local tmp = quasiloop(a) - return List.new({Sym.new("vec"), tmp}) - elseif is_instanceOf(a,HashMap) or is_instanceOf(a,Sym) then - return List.new({Sym.new('quote'), a}) + if is_instanceOf(a,List) then + if starts_with(a, "unquote") then + if #a-1 ~= 1 then + throw("unquote expected 1 argument bot got : " .. #a) + end + return a[2] + else + return quasiloop(a) + end + elseif is_instanceOf(a, Vector) then + local tmp = quasiloop(a) + return List.new({Sym.new("vec"), tmp}) + elseif is_instanceOf(a,HashMap) or is_instanceOf(a,Sym) then + return List.new({Sym.new('quote'), a}) - else - return a + else + return a - end + end end function is_macro_call(ast, env) - if is_instanceOf(ast, List) and #ast >= 1 and is_instanceOf(ast[1], Sym) then + if is_instanceOf(ast, List) and #ast >= 1 and + is_instanceOf(ast[1], Sym) then local status, first_env = pcall( function () return env:get(ast[1]) end) if not status then return false end if is_instanceOf(first_env, Function) and first_env.is_macro then @@ -93,6 +97,7 @@ end function try(a, env) --assert(nil, "try is not refactored yet") a = table.pack(table.unpack(a,2)) -- removing try* symbol + if #a > 2 and #a < 1 then throw("try expected at 1 or 2 arguments but got '" .. #a .. "'.") end @@ -153,7 +158,7 @@ function EVAL(a, env) throw("Second arg to let* should be list or vector") end if #a[2] % 2 ~= 0 then - throw(string.format("Length ofSecond arg to let* should be even number got: %d", #a[2])) + throw(string.format("Length of second arg to let* should be even number got: %d", #a[2])) end for i=1,#a[2],2 do @@ -188,7 +193,9 @@ function EVAL(a, env) end end elseif first_sym == "fn*" then - if (#a) ~= 3 then throw("fn* expected 2 arguments but got '" .. #a-1 .. "'.") end + if #a ~= 3 then + throw("fn* expected 2 arguments but got '" .. #a-1 .. "'.") + end if false then throw("second parameter to fn* should have length 2 but got '" .. #a[2] .. "'.") end return Function.new(function (...) local closed_over_env = Env.new(env) @@ -230,21 +237,21 @@ function EVAL(a, env) else local args = eval_ast(a, env) - local f = table.remove(args,1) + local f = table.remove(args,1) if types.is_malfunc(f) then a = f.ast env = Env.new(f.env) env:bind(f.params, args) else - if type(f) ~= "function" then + if not(type(f) == "function" or is_instanceOf(f, FunctionRef)) then throw("First elem should be function or special form got :'" .. type(f) .. "'.") end - return f(table.unpack(args)) --fixme: varargs? + return f(table.unpack(args)) end end -end + end end @@ -305,7 +312,7 @@ end function main() rep("(def! not (fn* (a) (if a false true)))") rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \"(slurp f) \"\nnil)\")))))") -rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))") + rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))") repl_env:set(Sym.new("*ARGV*"), List.new(table.pack(table.unpack(arg,2)))) repl_env:set(Sym.new("*host-language*"), "lua") if #arg > 0 then @@ -317,7 +324,7 @@ rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if ( if is_instanceOf(err, Err) then err = Printer.stringfy_val(err, true) end - print("Error: " .. err) + print("lua mal Error: " .. err) print(debug.traceback()) end diff --git a/impls/lua.2/types.lua b/impls/lua.2/types.lua index 946c88fd4b..51d385169f 100644 --- a/impls/lua.2/types.lua +++ b/impls/lua.2/types.lua @@ -95,6 +95,11 @@ function M.is_malfunc(a) return M.isinstanceof(a, M.MalFunction) end +function M.is_func(a) + return type(a) == "function" or (M.isinstanceof(a, M.MalFunction) and not a.is_macro) +end + + function M.is_sequence(a) return M.isinstanceof(a, M.MalList) or M.isinstanceof(a, M.MalVector) end @@ -146,4 +151,44 @@ function M.isinstanceof(obj, super) end end + +function M.copy(obj) + if type(obj) == "function" then + return M.FunctionRef.new(obj) + end + if type(obj) ~= "table" then return obj end + + -- copy object data + local new_obj = {} + for k,v in pairs(obj) do + new_obj[k] = v + end + + -- copy metatable and link to original + local old_mt = getmetatable(obj) + if old_mt ~= nil then + local new_mt = {} + for k,v in pairs(old_mt) do + new_mt[k] = v + end + setmetatable(new_mt, old_mt) + setmetatable(new_obj, new_mt) + end + + return new_obj +end + +M.FunctionRef = {} +M.FunctionRef.__index = M.FunctionRef + +function M.FunctionRef.new(fn) + local self = {fn = fn} + return setmetatable(self, M.FunctionRef) +end + +function M.FunctionRef.__call(self, ...) + return self.fn(...) +end + + return M From 3a4ba74c6f45da5ba0856753ecfdd558a1e35860 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=BCseyin=20Er?= Date: Fri, 31 Mar 2023 12:03:55 +0300 Subject: [PATCH 23/25] stepA all tests pass builtin conj, seq added bug fixes in with-meta, defmacro! string --- impls/lua.2/core.lua | 59 +++++++++++++++++++++++++++++++-------- impls/lua.2/printer.lua | 3 +- impls/lua.2/reader.lua | 2 +- impls/lua.2/stepA_mal.lua | 7 +++-- impls/lua.2/types.lua | 3 +- 5 files changed, 57 insertions(+), 17 deletions(-) diff --git a/impls/lua.2/core.lua b/impls/lua.2/core.lua index def406e341..51877f0974 100644 --- a/impls/lua.2/core.lua +++ b/impls/lua.2/core.lua @@ -466,7 +466,7 @@ end core['time-ms'] = function (...) return math.floor(os.clock()*1000000) end --- TODO: handle built-in functions meta and with-meta + core['meta'] = function (...) local args = table.pack(...) if #args ~= 1 then @@ -492,7 +492,7 @@ core['with-meta'] = function (...) local first = args[1] local meta = args[2] if not(is_instanceOf(first, Hashmap) or is_sequence(first) or is_func(first)) then - throw("argument to with-meta should be one of {hashmap, list, vector, function}. got: " .. type(first)) + throw("argument to with-meta should be one of {hashmap, list, vector, function}. got: " .. type(first)) end local new_obj = types.copy(first) @@ -505,7 +505,8 @@ core['fn?'] = function (...) if #args ~= 1 then throw("fn? expect expects 1 args got: " .. #args) end - return is_instanceOf(args[1],Function) or type(args[1]) == "function" + local first = args[1] + return (is_instanceOf(first,Function) and not(first.is_macro) ) or type(first) == "function" end @@ -522,7 +523,7 @@ core['string?'] = function (...) if #args ~= 1 then throw("string? expect expects 1 args got: " .. #args) end - return type(args[1]) == "string" + return type(args[1]) == "string" and "\u{029e}" ~= string.sub(args[1], 1, 2) end core['macro?'] = function (...) @@ -533,17 +534,53 @@ core['macro?'] = function (...) return is_instanceOf(args[1], Function) and args[1].is_macro end - core['seq'] = function (...) - assert(nil, 'seq not implemented yet') + local args = table.pack(...) + if #args ~= 1 then + throw("seq expects 1 args got: " .. #args) + end + local first = args[1] + if not(is_sequence(first) or type(first) == "string" or first == Nil) then + throw("seq expects its arguments to be type of {list, vector, string or nil}.") + end + if first == Nil or first == "" or (is_sequence(first) and #first == 0 ) then + return Nil + elseif (is_sequence(first)) then + return List.new({table.unpack(first)}) + elseif type(first) == "string" then + local res = {} + for i= 1,#first do + table.insert(res,string.sub(first, i, i)) + end + return List.new(res) + else + assert(nil, "unreachable in built-in seq") + end + + end - core['conj'] = function (...) - assert(nil, 'conj not implemented yet') -end - - + local args = table.pack(...) + if #args < 1 then + throw("conj expects at least 1 args got: " .. #args) + end + local first = args[1] + if not(is_sequence(first)) then + throw("conj expects its first argument to be type of {list, vector}.") + end + local cls = is_instanceOf(first, List) and List or Vector + local res = types.copy(first) + + for i = 2,#args do + if is_instanceOf(first, List) then + table.insert(res, 1, args[i]) + else + table.insert(res, args[i]) + end + end + return cls.new(res) +end return core diff --git a/impls/lua.2/printer.lua b/impls/lua.2/printer.lua index 6e6a9b1e5e..d2f987cbab 100644 --- a/impls/lua.2/printer.lua +++ b/impls/lua.2/printer.lua @@ -11,6 +11,7 @@ local Sym = types.Sym local is_instanceOf = types.isinstanceof local Err = types.Err local Function = types.MalFunction +local FunctionRef = types.FunctionRef local Atom = types.Atom @@ -70,7 +71,7 @@ function Printer.stringfy_val(val, readably) res = "nil" elseif type(val) == "boolean" then res = tostring(val) - elseif type(val) == "function" then + elseif type(val) == "function" or is_instanceOf(val, FunctionRef) then res = "#" else error(string.format("Error: unknown type %s", val)) diff --git a/impls/lua.2/reader.lua b/impls/lua.2/reader.lua index 7b8c01c28a..c176ca9ae4 100644 --- a/impls/lua.2/reader.lua +++ b/impls/lua.2/reader.lua @@ -76,7 +76,7 @@ function Reader.read_form(self) return self:read_atom() end end --- fix read atom with types module + function Reader.read_atom(self) local token = self:advance() if token.typeof == "STR" then diff --git a/impls/lua.2/stepA_mal.lua b/impls/lua.2/stepA_mal.lua index 0062d8c318..87876a570c 100755 --- a/impls/lua.2/stepA_mal.lua +++ b/impls/lua.2/stepA_mal.lua @@ -223,9 +223,10 @@ function EVAL(a, env) if not(is_instanceOf(value, Function)) then throw("second argument to defmacro must be function") end - value.is_macro = true - env:set(a[2], value) - return value + local c_val = types.copy(value) + c_val.is_macro = true + env:set(a[2], c_val) + return c_val elseif first_sym == "macroexpand" then diff --git a/impls/lua.2/types.lua b/impls/lua.2/types.lua index 51d385169f..2eafdca673 100644 --- a/impls/lua.2/types.lua +++ b/impls/lua.2/types.lua @@ -96,7 +96,8 @@ function M.is_malfunc(a) end function M.is_func(a) - return type(a) == "function" or (M.isinstanceof(a, M.MalFunction) and not a.is_macro) + return type(a) == "function" or (M.isinstanceof(a, M.MalFunction) and not a.is_macro) + or M.isinstanceof(a, M.FunctionRef) end From c412d61965c8825bd9d7e618a2ae9f4bde8ad21c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=BCseyin=20Er?= Date: Fri, 31 Mar 2023 12:30:56 +0300 Subject: [PATCH 24/25] shebangs and +x modes added for step0..9 --- impls/lua.2/step0_repl.lua | 1 + impls/lua.2/step1_read_print.lua | 1 + impls/lua.2/step2_eval.lua | 1 + impls/lua.2/step3_env.lua | 1 + impls/lua.2/step4_if_fn_do.lua | 1 + impls/lua.2/step5_tco.lua | 1 + impls/lua.2/step6_file.lua | 1 + impls/lua.2/step7_quote.lua | 1 + impls/lua.2/step8_macros.lua | 1 + impls/lua.2/step9_try.lua | 1 + 10 files changed, 10 insertions(+) mode change 100644 => 100755 impls/lua.2/step0_repl.lua mode change 100644 => 100755 impls/lua.2/step1_read_print.lua mode change 100644 => 100755 impls/lua.2/step2_eval.lua mode change 100644 => 100755 impls/lua.2/step3_env.lua mode change 100644 => 100755 impls/lua.2/step4_if_fn_do.lua mode change 100644 => 100755 impls/lua.2/step5_tco.lua mode change 100644 => 100755 impls/lua.2/step6_file.lua mode change 100644 => 100755 impls/lua.2/step7_quote.lua mode change 100644 => 100755 impls/lua.2/step8_macros.lua mode change 100644 => 100755 impls/lua.2/step9_try.lua diff --git a/impls/lua.2/step0_repl.lua b/impls/lua.2/step0_repl.lua old mode 100644 new mode 100755 index fa257fbd95..de2b8615a2 --- a/impls/lua.2/step0_repl.lua +++ b/impls/lua.2/step0_repl.lua @@ -1,3 +1,4 @@ +#!/usr/bin/env lua function READ(prompt) io.write(prompt) return io.read() diff --git a/impls/lua.2/step1_read_print.lua b/impls/lua.2/step1_read_print.lua old mode 100644 new mode 100755 index 631d0f4090..23b09e8ffa --- a/impls/lua.2/step1_read_print.lua +++ b/impls/lua.2/step1_read_print.lua @@ -1,3 +1,4 @@ +#!/usr/bin/env lua Reader = require "reader" Printer = require "printer" diff --git a/impls/lua.2/step2_eval.lua b/impls/lua.2/step2_eval.lua old mode 100644 new mode 100755 index cef8210b11..135456560e --- a/impls/lua.2/step2_eval.lua +++ b/impls/lua.2/step2_eval.lua @@ -1,3 +1,4 @@ +#!/usr/bin/env lua Reader = require "reader" Printer = require "printer" types = require "types" diff --git a/impls/lua.2/step3_env.lua b/impls/lua.2/step3_env.lua old mode 100644 new mode 100755 index 74e0c1a3c4..5dbdb77f51 --- a/impls/lua.2/step3_env.lua +++ b/impls/lua.2/step3_env.lua @@ -1,3 +1,4 @@ +#!/usr/bin/env lua local Reader = require "reader" local Printer = require "printer" local types = require "types" diff --git a/impls/lua.2/step4_if_fn_do.lua b/impls/lua.2/step4_if_fn_do.lua old mode 100644 new mode 100755 index 547937f279..36692b66ac --- a/impls/lua.2/step4_if_fn_do.lua +++ b/impls/lua.2/step4_if_fn_do.lua @@ -1,3 +1,4 @@ +#!/usr/bin/env lua local Reader = require "reader" local Printer = require "printer" local types = require "types" diff --git a/impls/lua.2/step5_tco.lua b/impls/lua.2/step5_tco.lua old mode 100644 new mode 100755 index c82b536cdb..e05dd3cbdc --- a/impls/lua.2/step5_tco.lua +++ b/impls/lua.2/step5_tco.lua @@ -1,3 +1,4 @@ +#!/usr/bin/env lua local Reader = require "reader" local Printer = require "printer" local types = require "types" diff --git a/impls/lua.2/step6_file.lua b/impls/lua.2/step6_file.lua old mode 100644 new mode 100755 index 03117f3560..2ff6d89cff --- a/impls/lua.2/step6_file.lua +++ b/impls/lua.2/step6_file.lua @@ -1,3 +1,4 @@ +#!/usr/bin/env lua local Reader = require "reader" local Printer = require "printer" local types = require "types" diff --git a/impls/lua.2/step7_quote.lua b/impls/lua.2/step7_quote.lua old mode 100644 new mode 100755 index 387b872065..3ad0d2d2e4 --- a/impls/lua.2/step7_quote.lua +++ b/impls/lua.2/step7_quote.lua @@ -1,3 +1,4 @@ +#!/usr/bin/env lua local Reader = require "reader" local Printer = require "printer" local types = require "types" diff --git a/impls/lua.2/step8_macros.lua b/impls/lua.2/step8_macros.lua old mode 100644 new mode 100755 index 35780bbef3..e16f5b6873 --- a/impls/lua.2/step8_macros.lua +++ b/impls/lua.2/step8_macros.lua @@ -1,3 +1,4 @@ +#!/usr/bin/env lua local Reader = require "reader" local Printer = require "printer" local types = require "types" diff --git a/impls/lua.2/step9_try.lua b/impls/lua.2/step9_try.lua old mode 100644 new mode 100755 index af78af7e76..070ce3c6a9 --- a/impls/lua.2/step9_try.lua +++ b/impls/lua.2/step9_try.lua @@ -1,3 +1,4 @@ +#!/usr/bin/env lua local Reader = require "reader" local Printer = require "printer" local types = require "types" From 300043022eb43fb39f873dd5d2fa7c155e9da1c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=BCseyin=20Er?= Date: Fri, 31 Mar 2023 12:58:28 +0300 Subject: [PATCH 25/25] Typos fixed at impls/lua.2/README.md --- impls/lua.2/README.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/impls/lua.2/README.md b/impls/lua.2/README.md index b9e92b011b..6368008198 100644 --- a/impls/lua.2/README.md +++ b/impls/lua.2/README.md @@ -1,11 +1,10 @@ -# Second lua implementation of [mal](https://github.com/kanaka/mal) -Major difference from first lua implementation is tokenization is done manually instead of using regular expressions. +# Second Lua implementation of [mal](https://github.com/kanaka/mal) +Major difference from first Lua implementation is tokenisation is done manually instead of using regular expressions. # requirements -* lua 5.3 or greater -* rlwrap (optional) for readline editing [rlwrap](https: - //github.com/hanslub42/rlwrap) -```console +* Lua 5.3 or greater +* rlwrap (optional) for readline editing [rlwrap](https://github.com/hanslub42/rlwrap) +```sh # for repl with a readline rlwrap ./stepA_mal.lua # for repl