forked from osmarks/potatOS
577 lines
15 KiB
Lua
577 lines
15 KiB
Lua
|
function deepclone(t)
|
||
|
local res={}
|
||
|
for i,v in ipairs(t) do
|
||
|
if type(v)=="table" then
|
||
|
res[i]=deepclone(v)
|
||
|
else
|
||
|
res[i]=v
|
||
|
end
|
||
|
end
|
||
|
return res
|
||
|
end
|
||
|
function tokenize(str)
|
||
|
local ptr=1
|
||
|
local tokens={}
|
||
|
local line=1
|
||
|
local function isIn(e,s)
|
||
|
for i=1,#s do
|
||
|
if s:sub(i,i)==e then return true end
|
||
|
end
|
||
|
return false
|
||
|
end
|
||
|
local function isInT(e,s)
|
||
|
for i=1,#s do
|
||
|
if s[i]==e then return true end
|
||
|
end
|
||
|
return false
|
||
|
end
|
||
|
local function isNotIdent(c)
|
||
|
return isIn(c,"()[] \n\t\v\r\f\";'`")
|
||
|
end
|
||
|
local function isDigit(c)
|
||
|
return isIn(c,"0123456789")
|
||
|
end
|
||
|
while true do
|
||
|
local c=str:sub(ptr,ptr)
|
||
|
if c=="(" then table.insert(tokens,{"(",line=line}) ptr=ptr+1
|
||
|
elseif c==")" then table.insert(tokens,{")",line=line}) ptr=ptr+1
|
||
|
elseif c=="[" then table.insert(tokens,{"[",line=line}) ptr=ptr+1
|
||
|
elseif c=="]" then table.insert(tokens,{"]",line=line}) ptr=ptr+1
|
||
|
elseif c=="\n" then line=line+1 ptr=ptr+1
|
||
|
elseif c==" " or c=="\t" or c=="\v" or c=="\f" or c=="\r" or c==";" then ptr=ptr+1 --ignore whitespace. semicolon is treated as whitespace, somewhat.
|
||
|
elseif c=="/" and str:sub(ptr+1,ptr+1)=="/" then
|
||
|
ptr=ptr+1
|
||
|
local nc=str:sub(ptr,ptr)
|
||
|
while nc~="\n" and ptr<=#str do
|
||
|
ptr=ptr+1
|
||
|
nc=str:sub(ptr,ptr)
|
||
|
end
|
||
|
line=line+1
|
||
|
ptr=ptr+1
|
||
|
elseif c=="," then table.insert(tokens,{",",line=line}) ptr=ptr+1
|
||
|
elseif c==":" then table.insert(tokens,{"string",str:sub(ptr+1,ptr+1),line=line}) if str:sub(ptr+1,ptr+1)=="\n" then line=line+1 end ptr=ptr+2
|
||
|
elseif c=="'" then
|
||
|
local res=""
|
||
|
ptr=ptr+1
|
||
|
local nc=str:sub(ptr,ptr)
|
||
|
while not isNotIdent(nc) and ptr<=#str do
|
||
|
ptr=ptr+1
|
||
|
res=res..nc
|
||
|
nc=str:sub(ptr,ptr)
|
||
|
end
|
||
|
table.insert(tokens,{"string",res,line=line})
|
||
|
elseif c=="\"" then
|
||
|
local res=""
|
||
|
ptr=ptr+1
|
||
|
local nc=str:sub(ptr,ptr)
|
||
|
while nc~="\"" and ptr<=#str do
|
||
|
if nc=="\\" then ptr=ptr+1 nc=str:sub(ptr,ptr) end
|
||
|
if nc=="\n" then line=line+1 end
|
||
|
res=res..nc
|
||
|
ptr=ptr+1
|
||
|
nc=str:sub(ptr,ptr)
|
||
|
end
|
||
|
ptr=ptr+1
|
||
|
table.insert(tokens,{"string",res,line=line})
|
||
|
elseif c=="`" and str:sub(ptr+1,ptr+1)=="`" then
|
||
|
local res=""
|
||
|
ptr=ptr+2
|
||
|
local nc=str:sub(ptr,ptr+1)
|
||
|
while nc~="``" and ptr<=#str do
|
||
|
if nc:sub(1,1)=="\\" then ptr=ptr+1 nc=str:sub(ptr,ptr) end
|
||
|
if nc:sub(1,1)=="\n" then line=line+1 end
|
||
|
res=res..nc:sub(1,1)
|
||
|
ptr=ptr+1
|
||
|
nc=str:sub(ptr,ptr+1)
|
||
|
end
|
||
|
ptr=ptr+2
|
||
|
table.insert(tokens,{"string",res,line=line})
|
||
|
elseif isDigit(c) then
|
||
|
local res=c
|
||
|
ptr=ptr+1
|
||
|
local nc=str:sub(ptr,ptr)
|
||
|
while isDigit(nc) and ptr<=#str do
|
||
|
ptr=ptr+1
|
||
|
res=res..nc
|
||
|
nc=str:sub(ptr,ptr)
|
||
|
end
|
||
|
table.insert(tokens,{"number",tonumber(res,10),line=line})
|
||
|
elseif ptr>#str then break
|
||
|
elseif not isNotIdent(c) then
|
||
|
local res=c
|
||
|
ptr=ptr+1
|
||
|
local nc=str:sub(ptr,ptr)
|
||
|
while not isNotIdent(nc) and ptr<=#str do
|
||
|
ptr=ptr+1
|
||
|
res=res..nc
|
||
|
nc=str:sub(ptr,ptr)
|
||
|
end
|
||
|
table.insert(tokens,{"identifier",res,line=line})
|
||
|
else print("no idea what this is: "..c) ptr=ptr+1 end
|
||
|
end
|
||
|
table.insert(tokens,{"EOF"})
|
||
|
return tokens
|
||
|
end
|
||
|
|
||
|
function into_ast(tokens)
|
||
|
local ptr=1
|
||
|
local function expect(token)
|
||
|
if tokens[ptr][1]~=token then
|
||
|
error("expected "..token..", got "..tokens[ptr][1])
|
||
|
end
|
||
|
return tokens[ptr]
|
||
|
end
|
||
|
local function expect2(token)
|
||
|
if tokens[ptr][2]~=token then
|
||
|
error("expected "..token..", got "..tokens[ptr][2] and tokens[ptr][2] or tokens[ptr][1])
|
||
|
end
|
||
|
return tokens[ptr]
|
||
|
end
|
||
|
local function combinator_and(p,p2)
|
||
|
return function()
|
||
|
local bk=ptr
|
||
|
local r1,r2=p(),p2()
|
||
|
if not (r1 and r2) then
|
||
|
ptr=bk
|
||
|
return false
|
||
|
end
|
||
|
return {r1,r2}
|
||
|
end
|
||
|
end
|
||
|
local function combinator_or(p,p2)
|
||
|
return function()
|
||
|
local bk=ptr
|
||
|
local r1=p()
|
||
|
if not r1 then
|
||
|
local r2=p2()
|
||
|
if not r2 then
|
||
|
ptr=bk
|
||
|
return false
|
||
|
end
|
||
|
return r2
|
||
|
end
|
||
|
return r1
|
||
|
end
|
||
|
end
|
||
|
local function any_amount(p)
|
||
|
return function()
|
||
|
local res={}
|
||
|
while true do
|
||
|
local bk=ptr
|
||
|
local tmp=p()
|
||
|
if (tmp==nil) or tmp==false or tokens[bk][1]=="EOF" then ptr=bk break end
|
||
|
table.insert(res,tmp)
|
||
|
end
|
||
|
return res
|
||
|
end
|
||
|
end
|
||
|
local function any_eof(p)
|
||
|
return function()
|
||
|
local res={}
|
||
|
while true do
|
||
|
local bk=ptr
|
||
|
local tmp=p()
|
||
|
if (tmp==nil) or tmp==false or tokens[bk][1]=="EOF" then
|
||
|
if tokens[bk][1]~="EOF" then
|
||
|
error("[HL] line "..tokens[bk].line..": some syntax error occured.")
|
||
|
end
|
||
|
ptr=bk
|
||
|
break
|
||
|
end
|
||
|
table.insert(res,tmp)
|
||
|
end
|
||
|
return res
|
||
|
end
|
||
|
end
|
||
|
local function more_than_one(p)
|
||
|
return function()
|
||
|
local bk=ptr
|
||
|
local r1=p()
|
||
|
if not r1 or tokens[bk][1]=="EOF" then ptr=bk return false end
|
||
|
local res={r1}
|
||
|
while true do
|
||
|
local bk=ptr
|
||
|
local tmp=p()
|
||
|
if (tmp==nil) or tmp==false or tokens[bk][1]=="EOF" then ptr=bk break end
|
||
|
table.insert(res,tmp)
|
||
|
end
|
||
|
return res
|
||
|
end
|
||
|
end
|
||
|
local function number()
|
||
|
return function()
|
||
|
if tokens[ptr][1]=="number" then ptr=ptr+1 return {"number",tokens[ptr-1][2],line=tokens[ptr-1].line} end
|
||
|
return false
|
||
|
end
|
||
|
end
|
||
|
local function string()
|
||
|
return function()
|
||
|
if tokens[ptr][1]=="string" then ptr=ptr+1 return {"string",tokens[ptr-1][2],line=tokens[ptr-1].line} end
|
||
|
return false
|
||
|
end
|
||
|
end
|
||
|
local function symbol(x)
|
||
|
return function()
|
||
|
if tokens[ptr][1]==x then ptr=ptr+1 return tokens[ptr-1] end
|
||
|
return false
|
||
|
end
|
||
|
end
|
||
|
local function literal()
|
||
|
return combinator_or(number(),string())
|
||
|
end
|
||
|
local expression,lambda
|
||
|
function statement()
|
||
|
return combinator_or(combinator_or(expression(),lambda()),symbol("identifier"))
|
||
|
end
|
||
|
expression=function()
|
||
|
return combinator_or(literal(),function()
|
||
|
local res={"expression"}
|
||
|
local tmp=symbol("(")()
|
||
|
if not tmp then return false end
|
||
|
res.line=tmp.line
|
||
|
res[2]=more_than_one(statement())()
|
||
|
if not symbol(")")() then return false end
|
||
|
return res
|
||
|
end)
|
||
|
end
|
||
|
lambda=function()
|
||
|
return combinator_or(literal(),function()
|
||
|
local res={"lambda"}
|
||
|
local tmp=symbol("[")()
|
||
|
if not tmp then return false end
|
||
|
res.line=tmp.line
|
||
|
res[2]=more_than_one(statement())()
|
||
|
if not symbol("]")() then return false end
|
||
|
return res
|
||
|
end)
|
||
|
end
|
||
|
return any_eof(statement())()
|
||
|
end
|
||
|
|
||
|
function interpret(ast,imports)
|
||
|
local namespace_stack={{}}
|
||
|
local cline=1
|
||
|
local function top() return namespace_stack[#namespace_stack] end
|
||
|
local function instantiate(k,v)
|
||
|
local t=top()
|
||
|
t[k]={v}
|
||
|
end
|
||
|
local function set(k,v)
|
||
|
local t=top()
|
||
|
if not t[k] then
|
||
|
instantiate(k,v)
|
||
|
else
|
||
|
t[k][1]=v
|
||
|
end
|
||
|
end
|
||
|
local function throwerror(reason)
|
||
|
error("[HL] line "..cline..": "..reason)
|
||
|
end
|
||
|
local function get(k)
|
||
|
local t=top()
|
||
|
if t[k] then
|
||
|
return t[k][1]
|
||
|
end
|
||
|
throwerror("could not find variable "..k)
|
||
|
end
|
||
|
|
||
|
local function clone()
|
||
|
namespace_stack[#namespace_stack+1]=deepclone(top())
|
||
|
end
|
||
|
local function discard()
|
||
|
namespace_stack[#namespace_stack]=nil
|
||
|
end
|
||
|
local function to(...)
|
||
|
local res={}
|
||
|
local a={...}
|
||
|
for i,v in ipairs(a) do
|
||
|
if type(v)=="table" then
|
||
|
local res2={}
|
||
|
for i2,v2 in ipairs(v) do
|
||
|
res2[i2]=to(v2)
|
||
|
end
|
||
|
table.insert(res,{type="list",value=res2})
|
||
|
else
|
||
|
table.insert(res,{type=({
|
||
|
["nil"]="nil",
|
||
|
["string"]="string",
|
||
|
["number"]="number",
|
||
|
["boolean"]="bool",
|
||
|
["table"]="list",
|
||
|
})[type(v)],value=v})
|
||
|
end
|
||
|
end
|
||
|
return unpack(res)
|
||
|
end
|
||
|
local function from(...)
|
||
|
local res={}
|
||
|
local a={...}
|
||
|
for i,v in ipairs(a) do
|
||
|
if v.type=="list" then
|
||
|
local res2={}
|
||
|
for i2,v2 in ipairs(v.value) do
|
||
|
res2[i2]=from(v2)
|
||
|
end
|
||
|
table.insert(res,res2)
|
||
|
else
|
||
|
table.insert(res,v.value)
|
||
|
end
|
||
|
end
|
||
|
return unpack(res)
|
||
|
end
|
||
|
local raw
|
||
|
raw={
|
||
|
["+"]=function(x,y)
|
||
|
if x and y then
|
||
|
if x.type=="number" and y.type=="number" then
|
||
|
return {type="number",value=x.value+y.value}
|
||
|
end
|
||
|
if (x.type=="string" and (y.type=="string" or y.type=="number" or y.type=="bool")) or (y.type=="string" and (x.type=="string" or x.type=="number" or x.type=="bool")) then
|
||
|
return {type="string",value=x.value..y.value}
|
||
|
end
|
||
|
end
|
||
|
return throwerror("types incompatible: "..(x and x.type or "nil")..", "..(y and y.type or "nil"))
|
||
|
end,
|
||
|
["*"]=function(x,y)
|
||
|
if x and y and x.type=="number" and y.type=="number" then
|
||
|
return {type="number",value=x.value*y.value}
|
||
|
end
|
||
|
throwerror("types incompatible: "..(x and x.type or "nil")..", "..(y and y.type or "nil"))
|
||
|
end,
|
||
|
["negate"]=function(x)
|
||
|
if x and x.type=="number" then
|
||
|
return {type="number",value=-(x.value)}
|
||
|
end
|
||
|
throwerror("types incompatible: "..(args[1] and args[1].type or "nil"))
|
||
|
end,
|
||
|
["at"]=function(x,y)
|
||
|
if x and x.type=="list" and y and (y.type=="number" or y.type=="string") then
|
||
|
if x.value[y.value] then
|
||
|
return x.value[y.value]
|
||
|
end
|
||
|
throwerror("attempt to get out of bounds index "..y.value)
|
||
|
end
|
||
|
if x and x.type=="string" and y and y.type=="number" then
|
||
|
if #x.value>=(y.value+1) and (y.value+1)>=1 then
|
||
|
return {type="string",value=x.value:sub(y.value+1,y.value+1)}
|
||
|
end
|
||
|
throwerror("attempt to get out of bounds index "..y.value)
|
||
|
end
|
||
|
throwerror("types incompatible: "..(x and x.type or "nil")..", "..(y and y.type or "nil"))
|
||
|
end,
|
||
|
["keys"]=function(x)
|
||
|
if x and x.type=="list" then
|
||
|
local res={type="list",value={}}
|
||
|
for i,v in pairs(x.value) do
|
||
|
table.insert(res.value,i)
|
||
|
end
|
||
|
return res
|
||
|
end
|
||
|
throwerror("types incompatible: "..(x and x.type or "nil"))
|
||
|
end,
|
||
|
["/"]=function(x,y)
|
||
|
if x and y and x.type=="number" and y.type=="number" then
|
||
|
return {type="number",value=x.value/y.value}
|
||
|
end
|
||
|
throwerror("types incompatible: "..(x and x.type or nil)..", "..y and y.type or nil)
|
||
|
end,
|
||
|
["=="]=function(x,y)
|
||
|
if x.type~=y.type then return {type="bool",value=false} end
|
||
|
return {type="bool",value=x.value==y.value}
|
||
|
end,
|
||
|
["len"]=function(x)
|
||
|
if x.type=="list" or x.type=="string" then
|
||
|
return {type="number",value=#x.value}
|
||
|
end
|
||
|
end,
|
||
|
["seti"]=function(x,y,z)
|
||
|
if x and x.type=="list" and y and (y.type=="number" or y.type=="string") and z then
|
||
|
x.value[y.value]=z
|
||
|
end
|
||
|
throwerror("types incompatible: "..(x and x.type or "nil")..", "..(y and y.type or "nil")..", "..(z and z.type or "nil"))
|
||
|
end,
|
||
|
["if"]=function(x,y,z)
|
||
|
if x and x.value~=0 and x.value~=false then
|
||
|
if y and y.type=="function" then
|
||
|
return y.value()
|
||
|
end
|
||
|
else
|
||
|
if x and z and z.type=="function" then
|
||
|
return z.value()
|
||
|
end
|
||
|
end
|
||
|
return throwerror("types incompatible: "..(x and x.type or "nil")..", "..(y and y.type or "nil")..", "..(z and z.type or "nil"))
|
||
|
end,
|
||
|
["print"]=function(...)
|
||
|
local tmp={}
|
||
|
local args={...}
|
||
|
for i=1,#args do
|
||
|
if args[i].type=="string" or args[i].type=="number" or args[i].type=="bool" then
|
||
|
tmp[#tmp+1]=args[i].value
|
||
|
end
|
||
|
if args[i].type=="error" then
|
||
|
tmp[#tmp+1]="error: "..args[i].value
|
||
|
end
|
||
|
if args[i].type=="list" then
|
||
|
tmp[#tmp+1]="(list, length: "..#args[i].value..")"
|
||
|
end
|
||
|
if args[i].type=="function" then
|
||
|
tmp[#tmp+1]="(function)"
|
||
|
end
|
||
|
end
|
||
|
print(#args>0 and unpack(tmp) or "nil")
|
||
|
return {type="bool",value=true}
|
||
|
end,
|
||
|
["type"]=function(x)
|
||
|
if x then return {type="string",value=x.type} end
|
||
|
throwerror("types incompatible: nil")
|
||
|
end,
|
||
|
["<"]=function(x,y)
|
||
|
if x and y and x.type=="number" and y.type=="number" then
|
||
|
return {type="bool",value=(x.value<y.value)}
|
||
|
end
|
||
|
throwerror("types incompatible: "..(x and x.type or nil)..", "..y and y.type or nil)
|
||
|
end,
|
||
|
["newvar"]=function(...)
|
||
|
local args={...}
|
||
|
if args[1] and args[1].type=="string" then
|
||
|
if args[3] and args[3].type=="bool" and args[3].value==true then
|
||
|
local tmp=top()
|
||
|
discard()
|
||
|
instantiate(args[1].value,args[2])
|
||
|
namespace_stack[#namespace_stack+1]=tmp
|
||
|
instantiate(args[1].value,args[2])
|
||
|
else
|
||
|
instantiate(args[1].value,args[2])
|
||
|
end
|
||
|
return {type="bool",value=true}
|
||
|
end
|
||
|
return throwerror("types incompatible: "..(args[1] and args[1].type or "nil"))
|
||
|
end,
|
||
|
["var"]=function(...)
|
||
|
local args={...}
|
||
|
if args[1] and args[1].type=="string" then set(args[1].value,args[2]) return {type="bool",value=true} end
|
||
|
return throwerror("types incompatible: "..(args[1] and args[1].type or "nil"))
|
||
|
end,
|
||
|
["extern"]=function(x,y)
|
||
|
if x and x.type=="string" then
|
||
|
local namespace=""
|
||
|
if y and y.type=="string" then
|
||
|
namespace=y.value.."_"
|
||
|
end
|
||
|
local f=io.open(x.value,"r")
|
||
|
if not f then
|
||
|
return {type="bool",value=false}
|
||
|
end
|
||
|
local i=f:read("*a")
|
||
|
f:close()
|
||
|
local xp=interpret(into_ast(tokenize(i)),imports).exports
|
||
|
if xp then
|
||
|
for k,v in pairs(xp) do
|
||
|
instantiate(namespace..k,{type="function",value=v})
|
||
|
end
|
||
|
return {type="bool",value=true}
|
||
|
end
|
||
|
end
|
||
|
return {type="bool",value=false}
|
||
|
end,
|
||
|
}
|
||
|
for i,v in pairs(raw) do
|
||
|
set(i,{type="function",value=v})
|
||
|
end
|
||
|
for i,v in pairs(imports or {}) do
|
||
|
set("lua_"..i,{type="function",value=function(...)
|
||
|
return to(v(from(...)))
|
||
|
end})
|
||
|
end
|
||
|
instantiate("true",{type="bool",value=true})
|
||
|
instantiate("false",{type="bool",value=false})
|
||
|
local function exec(node)
|
||
|
local nodet=node[1]
|
||
|
cline=node.line
|
||
|
if nodet=="string" then return {type="string",value=node[2]} end
|
||
|
if nodet=="number" then return {type="number",value=node[2]} end
|
||
|
if nodet=="identifier" then
|
||
|
local r=get(node[2])
|
||
|
if not r then
|
||
|
return {type="error",value="not found"}
|
||
|
else
|
||
|
return r
|
||
|
end
|
||
|
end
|
||
|
if nodet=="expression" then
|
||
|
local vl=node[2]
|
||
|
local to_call=nil
|
||
|
local list_res={}
|
||
|
for i,v in ipairs(vl) do
|
||
|
local evr=exec(v)
|
||
|
if i==1 and evr.type=="function" then
|
||
|
to_call=evr
|
||
|
else
|
||
|
table.insert(list_res,evr)
|
||
|
end
|
||
|
end
|
||
|
if to_call then
|
||
|
local m=to_call.value(unpack(list_res))
|
||
|
--print(m)
|
||
|
return m
|
||
|
else
|
||
|
return {type="list",value=list_res}
|
||
|
end
|
||
|
end
|
||
|
if nodet=="lambda" then
|
||
|
local vl=node[2]
|
||
|
local to_call=nil
|
||
|
local list_res={}
|
||
|
for i,v in ipairs(vl) do
|
||
|
table.insert(list_res,v)
|
||
|
end
|
||
|
local cscope=top()
|
||
|
return {type="function",value=function(...)
|
||
|
local a={...}
|
||
|
clone()
|
||
|
for i,v in pairs(cscope) do
|
||
|
if not namespace_stack[#namespace_stack][i] then namespace_stack[#namespace_stack][i]=v end
|
||
|
end
|
||
|
for i=1,#a do
|
||
|
instantiate("arg"..i,a[i])
|
||
|
end
|
||
|
instantiate("argc",#a)
|
||
|
local retval=nil
|
||
|
for i,v in ipairs(list_res) do
|
||
|
retval=exec(v)
|
||
|
end
|
||
|
if not retval then
|
||
|
return throwerror("functions cannot have no return value")
|
||
|
end
|
||
|
discard()
|
||
|
return retval
|
||
|
end}
|
||
|
end
|
||
|
print("oh apio oh "..nodet)
|
||
|
end
|
||
|
local rv=nil
|
||
|
for i=1,#ast do
|
||
|
rv=exec(ast[i])
|
||
|
end
|
||
|
local exports={}
|
||
|
local lua_exports={throwerror=throwerror}
|
||
|
if rv and rv.type=="list" then
|
||
|
if rv.value[1] and rv.value[1].value=="exports" then
|
||
|
for i=2,#rv.value,2 do
|
||
|
if rv.value[i] and rv.value[i].type=="string" then
|
||
|
if rv.value[i+1] and rv.value[i+1].type=="function" then
|
||
|
exports[rv.value[i].value]=rv.value[i+1].value
|
||
|
lua_exports[rv.value[i].value]=function(...) return from(rv.value[i+1].value(to(...))) end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
return {returned=rv,exports=exports,lua_exports=lua_exports}
|
||
|
end
|
||
|
local function run(x,lua)
|
||
|
return interpret(into_ast(tokenize(x)),lua)
|
||
|
end
|
||
|
return {run=run, interpret=interpret, into_ast=into_ast, tokenize=tokenize}
|