mirror of
https://github.com/vonhyou/lisp-interpreter.git
synced 2025-06-08 02:02:01 +00:00
131 lines
3 KiB
Ruby
131 lines
3 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
|
|
|
|
# p parse '(def 1 2 (c 3.3 (r f r) e))'
|
|
|
|
##### 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 },
|
|
'print': ->(arg) { p arg },
|
|
'begin': ->(*_args) { true }
|
|
}
|
|
end
|
|
|
|
# puts lisp_env[:+].call 1, 2, 3
|
|
# puts lisp_env[:-].call 1, 2, 3
|
|
# puts lisp_env[:*].call 2, 3, 4
|
|
# puts lisp_env[:/].call 9, 5, 1
|
|
# puts lisp_env[:>].call 1, 2
|
|
# p lisp_env[:car].call [1, 2, 3]
|
|
# p lisp_env[:cdr].call [1, 2, 3]
|
|
|
|
##### 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
|
|
|
|
|
|
# p lisp_eval(parse '(/ (+ 1 (* 2 3) 1 1 (+ 1 (- 7 2) 1)) 4)')
|
|
# lisp_eval(parse '(begin (def var1 7) (print (if (> var1 1) (+ 1 30) (- 10 2))))')
|
|
|
|
##### 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()
|
|
# lisp_eval(parse('(begin (def var1 7) (if (> var1 8) (+ 3 11) (/ 7 3)))'))
|