h0rs3r4dish (owner)

Forks

Revisions

gist: 220035 Download_button fork
public
Description:
LISP in <200 lines of Ruby
Public Clone URL: git://gist.github.com/220035.git
Embed All Files: show embed
rlsp.rb #
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
#!/usr/bin/ruby
 
RLSP_VERSION = "1.4.2"
 
class Lambda
  attr_accessor :args, :body
  def initialize(args=[],body="")
    @args = (args.class == Array) ? args : [args]
    @body = body
  end
end
 
def strip_array arr
  while arr.length == 1 && arr[0].class == Array
    arr = arr[0]
  end
  arr
end
 
def strip_whitespace(str)
  str.split("\n").map { |l| (l[0].chr == ';') ? "\n" : l unless l[0] == nil }.join("\n").gsub(/[\n\t\r]+/,' ').gsub(/\s{2,}/,' ').strip
end
 
def split_to_lines(str)
  ret = []
  level = 0
  for i in 0...str.length
    cur = str[i].chr
    if cur == '(' then
      ret.push String.new if level == 0
      level += 1
    elsif cur == ')' then
      ret[-1] << ")" if level == 1
      level -= 1
    end
    ret[-1] << cur if level > 0
  end
  ret
end
 
def parse(str)
  return str if str.class != String
  str = str.sub(/^\(/,'').sub(/\)$/,'')
  return $1 if str =~ /^"([^"]+)"$/
  return str.to_i if str =~ /^(\-|)\d+$/
  return str.to_f if str =~ /^(\-|)\d+\.\d+$/
  return $1 == 't' if str =~ /^(t|f)$/
  return str.intern if !str.index(' ') && !str.index('(') && !str.empty?
  splstr = str.split(' ')
  splstr.each_index do |i|
    if splstr[i] =~ /^"[^"]+$/ then
      a = splstr[i+1]
      while a !~ /^[^"]+"$/
        splstr[i] += ' ' + a
        splstr.delete(a)
        a = splstr[i+1]
      end
      splstr[i] += ' ' + a
      splstr.delete(a)
    elsif splstr[i] =~ /^\([^)]+$/ then
      stack = splstr[i].scan('(').length - splstr[i].scan(')').length
      while stack > 0
        a = splstr[i+1]
        splstr[i] += ' ' + a
        stack += a.scan('(').length - a.scan(')').length
        splstr.delete_at(i+1)
      end
    end
  end
  splstr.map { |item| parse item }
end
 
def lisp_eval(exp, env)
  return exp if ![Array,Symbol].index exp.class
  if exp.class == Symbol
    return lisp_eval(env[exp],env) if env.has_key? exp
    return exp
  end
  return exp if exp.length == 0
  exp = strip_array exp
  fn = exp[0]
  args = exp[1...exp.length]
  return special lisp_eval(fn, env), args, env if is_special? fn
  lisp_apply lisp_eval(fn, env), eval_list(args, env), env
end
 
def lisp_apply(function, args, env)
  return primitive(function, args, env) if is_primitive? function
  raise "'%s' is not a function" % function unless function.class == Lambda
  a = eval_list(args,env)
  for i in 0...args.length
    env[function.args[i]] = a[i]
  end
  lisp_eval(function.body,env)
end
 
def eval_list(items, env)
  return lisp_eval(items,env) if items.class != Array
  items.map { |x| lisp_eval(x, env) }
end
 
def is_primitive? fn
  [:print, :last, :first, :rest, :list, :read, :eval, :+, :-, :/, :*, :'=', :atom?, :'!=', :'<', :do].index fn
end
 
def primitive(name, args, env)
  args = eval_list(args,env)
  case name
    when :print
      puts args.join(' ')
    when :first
      args = strip_array args
      args[0]
    when :last
      args = strip_array args
      args[-1]
    when :rest
      args = strip_array args
      args[1...args.length]
    when :list
      args
    when :read
      gets
    when :eval
      args = strip_array args
      lisp_eval(parse(strip_whitespace(args[0])),env)
    when :atom?
      args[0].class != Array
    when :+
      args = strip_array args
      args[0] + args[1]
    when :-
      args = strip_array args
      args[0] - args[1]
    when :/
      args = strip_array args
      args[0] / args[1].to_f
    when :*
      args = strip_array args
      args[0] * args[1]
    when :'='
      args[0] == args[1]
    when :'!='
      args[0] != args[1]
    when :'<'
      args[0] < args[1]
    when :do
      args[-1]
  end
end
 
def is_special? fn
  [:if,:while,:def,:fn,:defn].index(fn)
end
 
def special(name, args, env)
  case name
    when :if
      if lisp_eval(args[0],env) then
        lisp_eval(args[1],env)
      elsif args.length == 3
        lisp_eval(args[2],env)
      end
    when :while
      while lisp_eval(args[0],env)
        lisp_eval(args[1],env)
      end
    when :def
      env[lisp_eval(args[0],env)] = args[1]
      nil
    when :fn
      Lambda.new(lisp_eval(args[0],env), args[1])
    when :defn
      env[lisp_eval(args[0],env)] = Lambda.new(lisp_eval(args[1],env),args[2])
  end
end
 
e = {}
 
puts "rlsp version %s" % RLSP_VERSION
if ARGV.length > 0 then
  program = IO.readlines(ARGV.shift).join("\n")
  split_to_lines(strip_whitespace program).each { |l| lisp_eval parse(l), e }
else
  while true
    print "rlsp> "
    line = gets
    break if line == nil
    line.chomp!
    while line.scan('(').length > line.scan(')').length
      line += gets.chomp
    end
    begin
      split_to_lines(strip_whitespace(line)).each { |l| puts lisp_eval(parse(l), e) }
    rescue Exception => er
      puts "\t"+er.backtrace[0]+': '+er.message
    end
  end
end