lisp-interpreter-rb/minlisp.rb
2021-04-14 09:25:06 +08:00

117 lines
2.5 KiB
Ruby

# It's a minimal lisp interpreter written in Ruby-lang
# Author: @vonhyou
# Start at: Apr. 10, 2021
##### Parser
# :parse
# :tokenize
# :read_tokens
# :atom
#####
def parse(program)
read_tokens(tokenize(program))
end
def tokenize(program)
# Convert scripts to token lists
replacements = { '(' => ' ( ', ')' => ' ) ' }
program.gsub(Regexp.union(replacements.keys), replacements)
.split(' ')
end
def make_list(tokens)
lst = []
lst << read_tokens(tokens) while tokens[0] != ')'
tokens.shift
lst
end
def read_tokens(tokens)
# read expressions from token
raise SyntaxError, 'Unexpected EOF' if tokens.empty?
token = tokens.shift
case token
when '('
make_list tokens
when ')'
raise SyntaxError, "Unexpected ')'"
else
atom token
end
end
def atom(token)
# Analyse numbers and symbols
is_integer = ->(atom) { atom.match?(/^-?\d+$/) }
is_float = ->(atom) { atom.match?(/^(-?\d+)(\.\d+)?$/) }
return Integer token if is_integer.call token
return Float token if is_float.call token
token.to_sym
end
##### Environments
def generate_env
lisp_env = {
'+': ->(args) { args.sum }, # args.inject(0, :+)
'-': ->(*args) { eval args.join('-') },
'*': ->(*args) { eval args.join('*') },
'/': ->(*args) { eval args.join('/') },
'>': ->(args) { args[0] > args[1] },
'<': ->(args) { args[0] < args[1] },
'=': ->(args) { args[0] == args[1] },
'>=': ->(args) { args[0] >= args[1] },
'<=': ->(args) { args[0] <= args[1] },
'min': ->(*args) { args.min },
'max': ->(*args) { args.max },
'car': ->(arr) { arr[0] },
'cdr': ->(arr) { arr[1..-1] },
'cons': ->(arr) { arr },
'quote': ->(arr) { arr },
'print': ->(arg) { p arg },
'begin': ->(*_args) { true }
}
end
##### Lisp Eval
$global_env = generate_env
def lisp_eval(elem, env = $global_env)
if elem.instance_of? Symbol
env[elem]
elsif elem.instance_of?(Integer) || elem.instance_of?(Float)
elem
elsif elem[0] == :def
_, sym, exp = elem
env[sym] = lisp_eval(exp, env)
elsif elem[0] == :if
_, cod, if_true, if_false = elem
exp = lisp_eval(cod, env) ? if_true : if_false
lisp_eval exp, env
else
args = []
elem[1..-1].each { |arg| args << lisp_eval(arg, env) }
p lisp_eval(elem[0], env)
lisp_eval(elem[0], env).call args
end
end
##### REPL
def repl(prompt='minlisp ƛ>> ')
loop do
print prompt
val = lisp_eval(parse(gets.chomp))
print_value val unless val.nil?
end
end
def print_value(value)
puts ";Value: #{value.to_s}"
end
# repl()