Last active
March 29, 2024 10:42
-
-
Save fperrad/dbf7e101c633535e5beabcec98b96b0d to your computer and use it in GitHub Desktop.
picol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
proc fib {x} { | |
if {<= $x 1} { | |
return 1 | |
} else { | |
+ [fib [- $x 1]] [fib [- $x 2]] | |
} | |
} | |
puts [fib 20] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
set a "pu" | |
set b {ts} | |
$a$b "Hello World!" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/env lua | |
-- Tcl in ~ 500 lines of code. | |
local Parser = {} | |
function Parser:next() | |
self.pos = self.pos + 1 | |
self.c = string.sub(self.text, self.pos, self.pos) | |
end | |
function Parser:parse_sep() | |
while string.match(self.c, '[ \t\n\r]') do | |
self:next() | |
end | |
self.type = 'SEP' | |
end | |
function Parser:parse_eol() | |
while string.match(self.c, '[ \t\n\r;]') do | |
self:next() | |
end | |
self.type = 'EOL' | |
end | |
function Parser:parse_command() | |
self:next() | |
local level = 1 | |
local start = self.pos | |
local blevel = 0 | |
while self.c ~= '' do | |
if self.c == '\\' then | |
self:next() | |
elseif self.c == '{' then | |
blevel = blevel + 1 | |
elseif self.c == '}' then | |
if blevel > 0 then | |
blevel = blevel - 1 | |
end | |
elseif self.c == '[' and blevel == 0 then | |
level = level + 1 | |
elseif self.c == ']' and blevel == 0 then | |
level = level - 1 | |
if level == 0 then | |
self:next() | |
break | |
end | |
end | |
self:next() | |
end | |
self.token = string.sub(self.text, start, self.pos - 2) | |
self.type = 'CMD' | |
end | |
function Parser:parse_var() | |
self:next() | |
local start = self.pos | |
while string.match(self.c, '[%w_]') do | |
self:next() | |
end | |
self.token = string.sub(self.text, start, self.pos - 1) | |
if self.token == '' then | |
self.token = '$' | |
self.type = 'STR' | |
else | |
self.type = 'VAR' | |
end | |
end | |
function Parser:parse_brace() | |
self:next() | |
local level = 1 | |
local start = self.pos | |
while self.c ~= '' do | |
if self.c == '\\' then | |
self:next() | |
elseif self.c == '{' then | |
level = level + 1 | |
elseif self.c == '}' then | |
level = level - 1 | |
if level == 0 then | |
self:next() | |
break | |
end | |
end | |
self:next() | |
end | |
self.token = string.sub(self.text, start, self.pos - 2) | |
self.type = 'STR' | |
end | |
function Parser:parse_string() | |
local new_word = self.type == 'SEP' or self.type == 'EOL' or self.type == 'STR' | |
if new_word then | |
if self.c == '{' then | |
return self:parse_brace() | |
elseif self.c == '"' then | |
self.inside_quote = true | |
self:next() | |
end | |
end | |
local start = self.pos | |
while self.c ~= '' do | |
if self.c == '\\' then | |
self:next() | |
elseif self.c == '$' or self.c == '[' then | |
self.type = 'ESC' | |
self.token = string.sub(self.text, start, self.pos - 1) | |
return | |
elseif string.match(self.c, '[ \t\n\r;]') then | |
if not self.inside_quote then | |
self.token = string.sub(self.text, start, self.pos - 1) | |
self.type = 'ESC' | |
return | |
end | |
elseif self.c == '"' then | |
if self.inside_quote then | |
self:next() | |
self.token = string.sub(self.text, start, self.pos - 2) | |
self.inside_quote = false | |
self.type = 'ESC' | |
return | |
end | |
end | |
self:next() | |
end | |
self.token = string.sub(self.text, start, self.pos - 1) | |
self.type = 'ESC' | |
end | |
function Parser:parse_comment() | |
while self.c ~= '\n' and self.c ~= '' do | |
self:next() | |
end | |
end | |
function Parser:get_token() | |
while self.c ~= '' do | |
if string.match(self.c, '[ \t\r]') then | |
if self.inside_quote then | |
return self:parse_string() | |
else | |
return self:parse_sep() | |
end | |
elseif string.match(self.c, '[\n;]') then | |
if self.inside_quote then | |
return self:parse_string() | |
else | |
return self:parse_eol() | |
end | |
elseif self.c == '[' then | |
return self:parse_command() | |
elseif self.c == '$' then | |
return self:parse_var() | |
elseif self.c == '#' then | |
if self.type == 'EOL' then | |
self:parse_comment() | |
else | |
return self:parse_string() | |
end | |
else | |
return self:parse_string() | |
end | |
end | |
if self.type ~= 'EOL' and self.type ~= 'EOF' then | |
self.type = 'EOL' | |
else | |
self.type = 'EOF' | |
end | |
end | |
function Parser.new(text) | |
local o = setmetatable({ | |
text = text, | |
pos = 0, | |
type = 'EOL', | |
inside_quote = false, | |
}, { __index = Parser }) | |
o:next() | |
return o | |
end | |
local Interp = {} | |
local function to_number(s) | |
return tonumber(s) or 0 | |
end | |
local function to_boolean(s) | |
return tonumber(s) ~= 0 | |
end | |
local escape = { | |
['\\a'] = '\a', | |
['\\b'] = '\b', | |
['\\f'] = '\f', | |
['\\n'] = '\n', | |
['\\r'] = '\r', | |
['\\t'] = '\t', | |
['\\v'] = '\v', | |
} | |
function Interp:eval(text) | |
self.result = '' | |
local args = {} | |
local p = Parser.new(text) | |
local prev = p.type | |
p:get_token() | |
while p.type ~= 'EOF' do | |
while p.type == 'SEP' do | |
prev = p.type | |
p:get_token() | |
end | |
local v = p.token | |
if p.type == 'VAR' then | |
v = self.call_frame[v] | |
if not v then | |
self.result = "No such variable '" .. p.token .. "'" | |
return 'ERR' | |
end | |
elseif p.type == 'CMD' then | |
local ret = self:eval(v) | |
if ret ~= 'OK' then | |
return ret | |
end | |
v = self.result | |
elseif p.type == 'ESC' then | |
v = string.gsub(v, '\\[abfnrtv]', escape) | |
v = string.gsub(v, '\\x(%x%x)', function(s) | |
return string.char(tonumber(s, 16)) | |
end) | |
end | |
if p.type == 'EOL' then | |
if #args > 0 then | |
local cmd = self.commands[args[1]] | |
if not cmd then | |
self.result = "No such command '" .. args[1] .. "'" | |
return 'ERR' | |
end | |
local ret = cmd(self, args) | |
if ret ~= 'OK' then | |
return ret | |
end | |
args = {} | |
end | |
else | |
if prev == 'SEP' or prev == 'EOL' then | |
args[#args + 1] = v | |
else | |
args[#args] = args[#args] .. v | |
end | |
end | |
prev = p.type | |
p:get_token() | |
end | |
return 'OK' | |
end | |
function Interp:arity_err(name) | |
self.result = "Wrong number of args for " .. name | |
return 'ERR' | |
end | |
function Interp:register_core_commands() | |
self.commands['+'] = function(interp, args) | |
if #args ~= 3 then | |
return interp:arity_err(args[1]) | |
end | |
interp.result = tostring(to_number(args[2]) + to_number(args[3])) | |
return 'OK' | |
end | |
self.commands['-'] = function(interp, args) | |
if #args ~= 3 then | |
return interp:arity_err(args[1]) | |
end | |
interp.result = tostring(to_number(args[2]) - to_number(args[3])) | |
return 'OK' | |
end | |
self.commands['*'] = function(interp, args) | |
if #args ~= 3 then | |
return interp:arity_err(args[1]) | |
end | |
interp.result = tostring(to_number(args[2]) * to_number(args[3])) | |
return 'OK' | |
end | |
self.commands['/'] = function(interp, args) | |
if #args ~= 3 then | |
return interp:arity_err(args[1]) | |
end | |
interp.result = tostring(to_number(args[2]) / to_number(args[3])) | |
return 'OK' | |
end | |
self.commands['>'] = function(interp, args) | |
if #args ~= 3 then | |
return interp:arity_err(args[1]) | |
end | |
interp.result = to_number(args[2]) > to_number(args[3]) and '1' or '0' | |
return 'OK' | |
end | |
self.commands['>='] = function(interp, args) | |
if #args ~= 3 then | |
return interp:arity_err(args[1]) | |
end | |
interp.result = to_number(args[2]) >= to_number(args[3]) and '1' or '0' | |
return 'OK' | |
end | |
self.commands['<'] = function(interp, args) | |
if #args ~= 3 then | |
return interp:arity_err(args[1]) | |
end | |
interp.result = to_number(args[2]) < to_number(args[3]) and '1' or '0' | |
return 'OK' | |
end | |
self.commands['<='] = function(interp, args) | |
if #args ~= 3 then | |
return interp:arity_err(args[1]) | |
end | |
interp.result = to_number(args[2]) <= to_number(args[3]) and '1' or '0' | |
return 'OK' | |
end | |
self.commands['=='] = function(interp, args) | |
if #args ~= 3 then | |
return interp:arity_err(args[1]) | |
end | |
interp.result = to_number(args[2]) == to_number(args[3]) and '1' or '0' | |
return 'OK' | |
end | |
self.commands['!='] = function(interp, args) | |
if #args ~= 3 then | |
return interp:arity_err(args[1]) | |
end | |
interp.result = to_number(args[2]) ~= to_number(args[3]) and '1' or '0' | |
return 'OK' | |
end | |
self.commands['set'] = function(interp, args) | |
if #args ~= 3 then | |
return interp:arity_err(args[1]) | |
end | |
interp.call_frame[args[2]] = args[3] | |
interp.result = args[3] | |
return 'OK' | |
end | |
self.commands['puts'] = function(interp, args) | |
if #args ~= 2 then | |
return interp:arity_err(args[1]) | |
end | |
io.stdout:write(args[2], '\n') | |
return 'OK' | |
end | |
self.commands['if'] = function(interp, args) | |
if #args ~= 3 and #args ~= 5 then | |
return interp:arity_err(args[1]) | |
end | |
local ret = interp:eval(args[2]) | |
if ret ~= 'OK' then | |
return ret | |
end | |
if to_boolean(interp.result) then | |
return interp:eval(args[3]) | |
elseif #args == 5 then | |
return interp:eval(args[5]) | |
end | |
return 'OK' | |
end | |
self.commands['while'] = function(interp, args) | |
if #args ~= 3 then | |
return interp:arity_err(args[1]) | |
end | |
while true do | |
local ret = interp:eval(args[2]) | |
if ret ~= 'OK' then | |
return ret | |
end | |
if to_boolean(interp.result) then | |
ret = interp:eval(args[3]) | |
if ret == 'BREAK' then | |
return 'OK' | |
elseif ret ~= 'OK' and ret ~= 'CONTINUE' then | |
return ret | |
end | |
else | |
return 'OK' | |
end | |
end | |
end | |
self.commands['break'] = function(interp, args) | |
if #args ~= 1 then | |
return interp:arity_err(args[1]) | |
end | |
return 'BREAK' | |
end | |
self.commands['continue'] = function(interp, args) | |
if #args ~= 1 then | |
return interp:arity_err(args[1]) | |
end | |
return 'CONTINUE' | |
end | |
self.commands['return'] = function(interp, args) | |
if #args ~= 1 and #args ~= 2 then | |
return interp:arity_err(args[1]) | |
end | |
interp.result = args[2] or '' | |
return 'OK' | |
end | |
self.commands['proc'] = function(interp, args) | |
if #args ~= 4 then | |
return interp:arity_err(args[1]) | |
end | |
local name = args[2] | |
if interp.commands[name] then | |
interp.result = "Command '" .. name .. "' already defined" | |
return 'ERR' | |
end | |
local arglist = args[3] | |
local body = args[4] | |
interp.commands[name] = function(pinterp, pargs) | |
local parent = pinterp.call_frame | |
pinterp.call_frame = setmetatable({}, { __index = parent }) | |
local arity = 0 | |
for pname in arglist:gmatch('[^ ]+') do | |
arity = arity + 1 | |
if arity > (#pargs - 1) then | |
break | |
end | |
pinterp.call_frame[pname] = pargs[arity + 1] | |
end | |
if arity ~= (#pargs - 1) then | |
pinterp.call_frame = parent | |
pinterp.result = "Proc '" .. pargs[1] .. "' called with wrong arg num" | |
return 'ERR' | |
end | |
local ret = pinterp:eval(body) | |
if ret == 'RETURN' then | |
ret = 'OK' | |
end | |
pinterp.call_frame = parent | |
return ret | |
end | |
return 'OK' | |
end | |
end | |
function Interp.new() | |
local o = setmetatable({ | |
call_frame = {}, | |
commands = {}, | |
result = '', | |
}, { __index = Interp }) | |
o:register_core_commands() | |
return o | |
end | |
local interp = Interp.new() | |
if #arg > 0 then | |
local f = io.open(arg[1], 'r') | |
local text = f:read('a') | |
f:close() | |
local ret = interp:eval(text) | |
if ret ~= 'OK' then | |
io.stdout:write(interp.result, '\n') | |
end | |
else | |
while true do | |
io.stdout:write('picol> ') | |
local line = io.stdin:read('l') | |
if not line then | |
break | |
end | |
local ret = interp:eval(line) | |
if interp.result ~= '' then | |
io.stdout:write(ret, ': ', interp.result, '\n') | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- Tcl in ~ 500 lines of code. | |
local record Parser | |
enum TokenType | |
'ESC' | |
'STR' | |
'CMD' | |
'VAR' | |
'SEP' | |
'EOL' | |
'EOF' | |
end | |
text: string | |
pos: integer | |
c: string | |
token: string | |
type: TokenType | |
inside_quote: boolean | |
next: function(self: Parser): nil | |
parse_sep: function(self: Parser): nil | |
parse_eol: function(self: Parser): nil | |
parse_command: function(self: Parser): nil | |
parse_var: function(self: Parser): nil | |
parse_brace: function(self: Parser): nil | |
parse_string: function(self: Parser): nil | |
parse_comment: function(self: Parser): nil | |
get_token: function(self: Parser): nil | |
new: function(text: string): Parser | |
end | |
function Parser:next (): nil | |
self.pos = self.pos + 1 | |
self.c = string.sub(self.text, self.pos, self.pos) | |
end | |
function Parser:parse_sep (): nil | |
while string.match(self.c, '[ \t\n\r]') do | |
self:next() | |
end | |
self.type = 'SEP' | |
end | |
function Parser:parse_eol (): nil | |
while string.match(self.c, '[ \t\n\r;]') do | |
self:next() | |
end | |
self.type = 'EOL' | |
end | |
function Parser:parse_command (): nil | |
self:next() -- skip the [ | |
local level = 1 | |
local start = self.pos | |
local blevel = 0 | |
while self.c ~= '' do | |
if self.c == '\\' then | |
self:next() | |
elseif self.c == '{' then | |
blevel = blevel + 1 | |
elseif self.c == '}' then | |
if blevel > 0 then | |
blevel = blevel - 1 | |
end | |
elseif self.c == '[' and blevel == 0 then | |
level = level + 1 | |
elseif self.c == ']' and blevel == 0 then | |
level = level - 1 | |
if level == 0 then | |
self:next() | |
break | |
end | |
end | |
self:next() | |
end | |
self.token = string.sub(self.text, start, self.pos - 2) | |
self.type = 'CMD' | |
end | |
function Parser:parse_var (): nil | |
self:next() -- skip the $ | |
local start = self.pos | |
while string.match(self.c, '[%w_]') do | |
self:next() | |
end | |
self.token = string.sub(self.text, start, self.pos - 1) | |
if self.token == '' then | |
self.token = '$' | |
self.type = 'STR' | |
else | |
self.type = 'VAR' | |
end | |
end | |
function Parser:parse_brace (): nil | |
self:next() -- skip the { | |
local level = 1 | |
local start = self.pos | |
while self.c ~= '' do | |
if self.c == '\\' then | |
self:next() | |
elseif self.c == '{' then | |
level = level + 1 | |
elseif self.c == '}' then | |
level = level - 1 | |
if level == 0 then | |
self:next() | |
break | |
end | |
end | |
self:next() | |
end | |
self.token = string.sub(self.text, start, self.pos - 2) | |
self.type = 'STR' | |
end | |
function Parser:parse_string (): nil | |
local new_word = self.type == 'SEP' or self.type == 'EOL' or self.type == 'STR' | |
if new_word then | |
if self.c == '{' then | |
return self:parse_brace() | |
elseif self.c == '"' then | |
self.inside_quote = true | |
self:next() | |
end | |
end | |
local start = self.pos | |
while self.c ~= '' do | |
if self.c == '\\' then | |
self:next() | |
elseif self.c == '$' or self.c == '[' then | |
self.type = 'ESC' | |
self.token = string.sub(self.text, start, self.pos - 1) | |
return | |
elseif string.match(self.c, '[ \t\n\r;]') then | |
if not self.inside_quote then | |
self.token = string.sub(self.text, start, self.pos - 1) | |
self.type = 'ESC' | |
return | |
end | |
elseif self.c == '"' then | |
if self.inside_quote then | |
self:next() | |
self.token = string.sub(self.text, start, self.pos - 2) | |
self.inside_quote = false | |
self.type = 'ESC' | |
return | |
end | |
end | |
self:next() | |
end | |
self.token = string.sub(self.text, start, self.pos - 1) | |
self.type = 'ESC' | |
end | |
function Parser:parse_comment (): nil | |
while self.c ~= '\n' and self.c ~= '' do | |
self:next() | |
end | |
end | |
function Parser:get_token (): nil | |
while self.c ~= '' do | |
if string.match(self.c, '[ \t\r]') then | |
if self.inside_quote then | |
return self:parse_string() | |
else | |
return self:parse_sep() | |
end | |
elseif string.match(self.c, '[\n;]') then | |
if self.inside_quote then | |
return self:parse_string() | |
else | |
return self:parse_eol() | |
end | |
elseif self.c == '[' then | |
return self:parse_command() | |
elseif self.c == '$' then | |
return self:parse_var() | |
elseif self.c == '#' then | |
if self.type == 'EOL' then | |
self:parse_comment() | |
else | |
return self:parse_string() | |
end | |
else | |
return self:parse_string() | |
end | |
end | |
if self.type ~= 'EOL' and self.type ~= 'EOF' then | |
self.type = 'EOL' | |
else | |
self.type = 'EOF' | |
end | |
end | |
function Parser.new (text: string): Parser | |
local o = setmetatable({ | |
text = text, | |
pos = 0, | |
type = 'EOL', | |
inside_quote = false, | |
}, { __index = Parser}) as Parser | |
o:next() | |
return o | |
end | |
local record Interp | |
enum RetCode | |
'OK' | |
'ERR' | |
'RETURN' | |
'BREAK' | |
'CONTINUE' | |
end | |
type Command = function(interp: Interp, args: {string}): RetCode | |
call_frame: {string:string} | |
commands: {string:Command} | |
result: string | |
eval: function(self: Interp, text: string): RetCode | |
arity_err: function(self: Interp, name: string): RetCode | |
register_core_commands: function(self: Interp) | |
new: function(): Interp | |
end | |
local function to_number (s: string): number | |
return tonumber(s) or 0 | |
end | |
local function to_boolean (s: string): boolean | |
return tonumber(s) ~= 0 | |
end | |
local escape = { | |
['\\a'] = '\a', | |
['\\b'] = '\b', | |
['\\f'] = '\f', | |
['\\n'] = '\n', | |
['\\r'] = '\r', | |
['\\t'] = '\t', | |
['\\v'] = '\v', | |
} | |
function Interp:eval (text: string): Interp.RetCode | |
self.result = '' | |
local args: {string} = {} | |
local p = Parser.new(text) | |
local prev = p.type | |
p:get_token() | |
while p.type ~= 'EOF' do | |
while p.type == 'SEP' do | |
prev = p.type | |
p:get_token() | |
end | |
local v = p.token | |
if p.type == 'VAR' then | |
v = self.call_frame[v] | |
if not v then | |
self.result = "No such variable '" .. p.token .. "'" | |
return 'ERR' | |
end | |
elseif p.type == 'CMD' then | |
local ret = self:eval(v) | |
if ret ~= 'OK' then | |
return ret | |
end | |
v = self.result | |
elseif p.type == 'ESC' then | |
v = string.gsub(v, '\\[abfnrtv]', escape) | |
v = string.gsub(v, '\\x(%x%x)', function (s: string): string | |
return string.char(tonumber(s, 16)) | |
end) | |
end | |
if p.type == 'EOL' then | |
if #args > 0 then | |
local cmd = self.commands[args[1]] | |
if not cmd then | |
self.result = "No such command '" .. args[1] .. "'" | |
return 'ERR' | |
end | |
local ret = cmd(self, args) | |
if ret ~= 'OK' then | |
return ret | |
end | |
args = {} | |
end | |
else | |
if prev == 'SEP' or prev == 'EOL' then | |
args[#args+1] = v | |
else | |
args[#args] = args[#args] .. v | |
end | |
end | |
prev = p.type | |
p:get_token() | |
end | |
return 'OK' | |
end | |
function Interp:arity_err (name: string): Interp.RetCode | |
self.result = "Wrong number of args for " .. name | |
return 'ERR' | |
end | |
function Interp:register_core_commands () | |
self.commands['+'] = function (interp: Interp, args: {string}): Interp.RetCode | |
if #args ~= 3 then | |
return interp:arity_err(args[1]) | |
end | |
interp.result = tostring(to_number(args[2]) + to_number(args[3])) | |
return 'OK' | |
end | |
self.commands['-'] = function (interp: Interp, args: {string}): Interp.RetCode | |
if #args ~= 3 then | |
return interp:arity_err(args[1]) | |
end | |
interp.result = tostring(to_number(args[2]) - to_number(args[3])) | |
return 'OK' | |
end | |
self.commands['*'] = function (interp: Interp, args: {string}): Interp.RetCode | |
if #args ~= 3 then | |
return interp:arity_err(args[1]) | |
end | |
interp.result = tostring(to_number(args[2]) * to_number(args[3])) | |
return 'OK' | |
end | |
self.commands['/'] = function (interp: Interp, args: {string}): Interp.RetCode | |
if #args ~= 3 then | |
return interp:arity_err(args[1]) | |
end | |
interp.result = tostring(to_number(args[2]) / to_number(args[3])) | |
return 'OK' | |
end | |
self.commands['>'] = function (interp: Interp, args: {string}): Interp.RetCode | |
if #args ~= 3 then | |
return interp:arity_err(args[1]) | |
end | |
interp.result = to_number(args[2]) > to_number(args[3]) and '1' or '0' | |
return 'OK' | |
end | |
self.commands['>='] = function (interp: Interp, args: {string}): Interp.RetCode | |
if #args ~= 3 then | |
return interp:arity_err(args[1]) | |
end | |
interp.result = to_number(args[2]) >= to_number(args[3]) and '1' or '0' | |
return 'OK' | |
end | |
self.commands['<'] = function (interp: Interp, args: {string}): Interp.RetCode | |
if #args ~= 3 then | |
return interp:arity_err(args[1]) | |
end | |
interp.result = to_number(args[2]) < to_number(args[3]) and '1' or '0' | |
return 'OK' | |
end | |
self.commands['<='] = function (interp: Interp, args: {string}): Interp.RetCode | |
if #args ~= 3 then | |
return interp:arity_err(args[1]) | |
end | |
interp.result = to_number(args[2]) <= to_number(args[3]) and '1' or '0' | |
return 'OK' | |
end | |
self.commands['=='] = function (interp: Interp, args: {string}): Interp.RetCode | |
if #args ~= 3 then | |
return interp:arity_err(args[1]) | |
end | |
interp.result = to_number(args[2]) == to_number(args[3]) and '1' or '0' | |
return 'OK' | |
end | |
self.commands['!='] = function (interp: Interp, args: {string}): Interp.RetCode | |
if #args ~= 3 then | |
return interp:arity_err(args[1]) | |
end | |
interp.result = to_number(args[2]) ~= to_number(args[3]) and '1' or '0' | |
return 'OK' | |
end | |
self.commands['set'] = function (interp: Interp, args: {string}): Interp.RetCode | |
if #args ~= 3 then | |
return interp:arity_err(args[1]) | |
end | |
interp.call_frame[args[2]] = args[3] | |
interp.result = args[3] | |
return 'OK' | |
end | |
self.commands['puts'] = function (interp: Interp, args: {string}): Interp.RetCode | |
if #args ~= 2 then | |
return interp:arity_err(args[1]) | |
end | |
io.stdout:write(args[2], '\n') | |
return 'OK' | |
end | |
self.commands['if'] = function (interp: Interp, args: {string}): Interp.RetCode | |
if #args ~= 3 and #args ~= 5 then | |
return interp:arity_err(args[1]) | |
end | |
local ret = interp:eval(args[2]) | |
if ret ~= 'OK' then | |
return ret | |
end | |
if to_boolean(interp.result) then | |
return interp:eval(args[3]) | |
elseif #args == 5 then | |
return interp:eval(args[5]) | |
end | |
return 'OK' | |
end | |
self.commands['while'] = function (interp: Interp, args: {string}): Interp.RetCode | |
if #args ~= 3 then | |
return interp:arity_err(args[1]) | |
end | |
while true do | |
local ret = interp:eval(args[2]) | |
if ret ~= 'OK' then | |
return ret | |
end | |
if to_boolean(interp.result) then | |
ret = interp:eval(args[3]) | |
if ret == 'BREAK' then | |
return 'OK' | |
elseif ret ~= 'OK' and ret ~= 'CONTINUE' then | |
return ret | |
end | |
else | |
return 'OK' | |
end | |
end | |
end | |
self.commands['break'] = function (interp: Interp, args: {string}): Interp.RetCode | |
if #args ~= 1 then | |
return interp:arity_err(args[1]) | |
end | |
return 'BREAK' | |
end | |
self.commands['continue'] = function (interp: Interp, args: {string}): Interp.RetCode | |
if #args ~= 1 then | |
return interp:arity_err(args[1]) | |
end | |
return 'CONTINUE' | |
end | |
self.commands['return'] = function (interp: Interp, args: {string}): Interp.RetCode | |
if #args ~= 1 and #args ~= 2 then | |
return interp:arity_err(args[1]) | |
end | |
interp.result = args[2] or '' | |
return 'OK' | |
end | |
self.commands['proc'] = function (interp: Interp, args: {string}): Interp.RetCode | |
if #args ~= 4 then | |
return interp:arity_err(args[1]) | |
end | |
local name = args[2] | |
if interp.commands[name] then | |
interp.result = "Command '" .. name .. "' already defined" | |
return 'ERR' | |
end | |
local arglist = args[3] | |
local body = args[4] | |
interp.commands[name] = function (pinterp: Interp, pargs: {string}): Interp.RetCode | |
local parent = pinterp.call_frame | |
pinterp.call_frame = setmetatable({}, { __index = parent }) | |
local arity = 0 | |
for pname in arglist:gmatch('[^ ]+') do | |
arity = arity + 1 | |
if arity > (#pargs - 1) then | |
break | |
end | |
pinterp.call_frame[pname] = pargs[arity + 1] | |
end | |
if arity ~= (#pargs - 1) then | |
pinterp.call_frame = parent | |
pinterp.result = "Proc '" ..pargs[1] .. "' called with wrong arg num" | |
return 'ERR' | |
end | |
local ret = pinterp:eval(body) | |
if ret == 'RETURN' then | |
ret = 'OK' | |
end | |
pinterp.call_frame = parent | |
return ret | |
end | |
return 'OK' | |
end | |
end | |
function Interp.new (): Interp | |
local o = setmetatable({ | |
call_frame = {}, | |
commands = {}, | |
result = '', | |
}, { __index = Interp}) as Interp | |
o:register_core_commands() | |
return o | |
end | |
local interp = Interp.new() | |
if #arg > 0 then | |
local f = io.open(arg[1], 'r') | |
local text = f:read('a') | |
f:close() | |
local ret = interp:eval(text) | |
if ret ~= 'OK' then | |
io.stdout:write(interp.result, '\n') | |
end | |
else | |
while true do | |
io.stdout:write('picol> ') | |
local line = io.stdin:read('l') | |
if not line then | |
break | |
end | |
local ret = interp:eval(line) | |
if interp.result ~= '' then | |
io.stdout:write(ret, ': ', interp.result, '\n') | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
proc square {x} { | |
* $x $x | |
} | |
set a 1 | |
while {<= $a 10} { | |
if {== $a 5} { | |
puts {Missing five!} | |
set a [+ $a 1] | |
continue | |
} | |
puts "I can compute that $a*$a = [square $a]" | |
set a [+ $a 1] | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
see http://oldblog.antirez.com/post/picol.html