ruspea_lang 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/ruspea.rb ADDED
@@ -0,0 +1,9 @@
1
+ require "pathname"
2
+ require "zeitwerk"
3
+ Zeitwerk::Loader.for_gem.setup
4
+
5
+ module Ruspea
6
+ def self.root
7
+ Pathname.new File.expand_path("../", __FILE__)
8
+ end
9
+ end
@@ -0,0 +1,19 @@
1
+ module Ruspea
2
+ class Code
3
+ def initialize
4
+ @reader = Ruspea::Interpreter::Reader.new
5
+ @evaler = Ruspea::Interpreter::Evaler.new
6
+ @printer = Ruspea::Printer.new
7
+ end
8
+
9
+ def load(file_path)
10
+ raise "#{file_path} is not a file" if !File.exists?(file_path)
11
+ exec(File.read(file_path))
12
+ end
13
+
14
+ def run(code, env: Ruspea::Language::Core.new)
15
+ _, forms = @reader.call(code)
16
+ @evaler.call(forms, context: env)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,7 @@
1
+ module Ruspea::Error
2
+ class Arity < Standard
3
+ def initialize(expected, received)
4
+ super "Expected #{expected} args, but received #{received}"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Ruspea::Error
2
+ class Resolution < Standard
3
+ def initialize(symbol)
4
+ super "Unable to resolve: #{symbol} in the current context"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ module Ruspea::Error
2
+ class Standard < StandardError; end
3
+ end
@@ -0,0 +1,4 @@
1
+ module Ruspea::Error
2
+ class Syntax < Standard
3
+ end
4
+ end
@@ -0,0 +1,67 @@
1
+ module Ruspea::Interpreter
2
+ class Evaler
3
+ include Ruspea::Runtime
4
+ include Ruspea::Error
5
+
6
+ def call(value, context: Env::Empty.instance)
7
+ return call(value.value, context: context) if value.is_a?(Form)
8
+
9
+ case value
10
+ when Sym
11
+ context.lookup value
12
+ when Numeric
13
+ value
14
+ when String
15
+ value
16
+ when NilClass
17
+ nil
18
+ when TrueClass
19
+ true
20
+ when FalseClass
21
+ false
22
+ when Array
23
+ value.map { |f|
24
+ call(f, context: context)
25
+ }
26
+ when List
27
+ fn = fn_from_invocation(value, context)
28
+ # TODO: raise if ! respond_to?(:call)
29
+
30
+ arguments =
31
+ if fn.respond_to?(:arity)
32
+ if fn.arity == 1 && value.tail.count > 1
33
+ [value.tail]
34
+ else
35
+ value.tail.to_a
36
+ end
37
+ else
38
+ if fn.is_a? Fn
39
+ value.tail.to_a
40
+ else
41
+ [value.tail.to_a]
42
+ end
43
+ end
44
+
45
+ fn.call(*arguments, context: context, evaler: self)
46
+ else
47
+ raise "Unrecognized expression"
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def fn_from_invocation(form, context)
54
+ name =
55
+ if form.head.is_a?(Ruspea::Interpreter::Form)
56
+ form.head.value
57
+ else
58
+ form.head
59
+ end
60
+ context
61
+ .lookup(name)
62
+ .tap { |fn|
63
+ raise Resolution.new(name) if fn.nil?
64
+ }
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,27 @@
1
+ module Ruspea::Interpreter
2
+ class Form
3
+ attr_reader :value, :meta
4
+
5
+ def initialize(value, meta = {closed: true})
6
+ @value = value
7
+ @meta = meta
8
+ end
9
+
10
+ def eq?(other)
11
+ self == other
12
+ end
13
+
14
+ def eql?(other)
15
+ self == other
16
+ end
17
+
18
+ def ==(other)
19
+ return false if self.class != other.class
20
+ value == other.value && meta == other.meta
21
+ end
22
+
23
+ def inspect
24
+ "Form< #{value.inspect} >"
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,127 @@
1
+ module Ruspea::Interpreter
2
+ class Reader
3
+ include Ruspea::Runtime
4
+ include Ruspea::Error
5
+
6
+ def call(source, forms = [])
7
+ return [source, forms] if source.empty?
8
+
9
+ code = to_list(source)
10
+
11
+ rest, new_form =
12
+ case code.head
13
+ when HIFEN
14
+ if DIGIT.match?(code.tail.head)
15
+ numberify(code.tail, code.head)
16
+ else
17
+ symbolize(code)
18
+ end
19
+ when DELIMITERS
20
+ # Closing lists, arrays, etc...
21
+ return [code, forms]
22
+ when SEPARATOR
23
+ # ignore separators
24
+ [code.tail, nil]
25
+ when STRING
26
+ stringfy(code.tail)
27
+ when DIGIT
28
+ numberify(code.tail, code.head)
29
+ when LIST_OPEN
30
+ listify(code.tail)
31
+ when ARRAY_OPEN
32
+ arraify(code.tail)
33
+ when QUOTE
34
+ quotify(code.tail)
35
+ else
36
+ symbolize(code)
37
+ end
38
+
39
+ new_forms = new_form.nil? ? forms : forms + [new_form]
40
+ call(rest, new_forms)
41
+ end
42
+
43
+ private
44
+
45
+ HIFEN = /\A-/
46
+ SEPARATOR = /\A[,\s]/
47
+ STRING = /\A"/
48
+ DIGIT = /\A\d/
49
+ NUMERIC = /\A[\d_]/
50
+ QUOTE = /\A'/
51
+ LIST_OPEN = /\A\(/
52
+ LIST_CLOSE = /\A\)/
53
+ ARRAY_OPEN = /\A\[/
54
+ ARRAY_CLOSE = /\A\]/
55
+ DELIMITERS = Regexp.union(LIST_CLOSE, ARRAY_CLOSE)
56
+ ENDER = Regexp.union(SEPARATOR, DELIMITERS)
57
+
58
+ def to_list(source)
59
+ return source if source.is_a?(Ruspea::Runtime::List)
60
+
61
+ Ruspea::Runtime::List.create(*source.chars)
62
+ end
63
+
64
+ def stringfy(code, string = "")
65
+ finished = code.head == "\"" || code.empty?
66
+ return [code.tail, Form.new(string, closed: !code.empty?)] if finished
67
+
68
+ stringfy(code.tail, string + code.head)
69
+ end
70
+
71
+ def numberify(code, number = "")
72
+ return floatfy(code.tail, number + code.head) if code.head == "."
73
+
74
+ finished = code.empty? || !NUMERIC.match?(code.head)
75
+ return [code, Form.new(Integer(number))] if finished
76
+
77
+ numberify(code.tail, number + code.head)
78
+ end
79
+
80
+ def floatfy(code, number)
81
+ finished = code.empty? || !NUMERIC.match?(code.head)
82
+ return [code.tail, Form.new(Float(number))] if finished
83
+
84
+ floatfy(code.tail, number + code.head)
85
+ end
86
+
87
+ def listify(code)
88
+ more_code, forms = call(code)
89
+ closed = LIST_CLOSE.match?(more_code.head)
90
+ form = Form.new(List.create(*forms), closed: closed)
91
+
92
+ [more_code.tail, form]
93
+ end
94
+
95
+ def arraify(code)
96
+ more_code, forms = call(code)
97
+ form = Form.new(forms, closed: ARRAY_CLOSE.match?(more_code.head))
98
+
99
+ [more_code.tail, form]
100
+ end
101
+
102
+ def quotify(code)
103
+ more_code, content = call(code)
104
+
105
+ invocation = List.create(
106
+ Form.new(Sym.new("quote")),
107
+ *content
108
+ )
109
+
110
+ [more_code, Form.new(invocation)]
111
+ end
112
+
113
+ def symbolize(code, word = "")
114
+ finished = code.empty? || ENDER.match?(code.head)
115
+ return [code, Form.new(read_word(word))] if finished
116
+
117
+ symbolize(code.tail, word + code.head)
118
+ end
119
+
120
+ def read_word(word)
121
+ return word == "true" if word == "true" || word == "false"
122
+ return nil if word == "nil"
123
+
124
+ Sym.new(word)
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,185 @@
1
+ module Ruspea::Language
2
+ class Core < Ruspea::Runtime::Env
3
+ include Ruspea::Runtime
4
+
5
+ def initialize(*args)
6
+ super(*args)
7
+
8
+ @ruby = Ruby.new
9
+
10
+ define Sym.new("quote"), fn_quote
11
+ define Sym.new("def"), fn_def
12
+ define Sym.new("fn"), fn_fn
13
+ define Sym.new("cond"), fn_cond
14
+
15
+ define Sym.new("::"), fn_constantize
16
+ define Sym.new("."), fn_dot
17
+ define Sym.new("eval"), fn_eval
18
+
19
+ load_standard
20
+ end
21
+
22
+ private
23
+
24
+ attr_reader :ruby
25
+
26
+ def load_standard
27
+ reader = Ruspea::Interpreter::Reader.new
28
+ evaler = Ruspea::Interpreter::Evaler.new
29
+ Dir.glob(
30
+ Ruspea.root.join("language/*.rsp").to_s
31
+ ).each do |file|
32
+ _, forms = reader.call(
33
+ File.read(file))
34
+ evaler.call(forms, context: self)
35
+ end
36
+ end
37
+
38
+ def fn_quote
39
+ Lm.new(
40
+ params: [Sym.new("expression")],
41
+ body: ->(env, _) {
42
+ env.call(Sym.new("expression"))
43
+ }
44
+ )
45
+ end
46
+
47
+ def fn_def
48
+ Lm.new(
49
+ params: [Sym.new("sym"), Sym.new("val")],
50
+ body: ->(env, evaler) {
51
+ caller_context = env.call(Sym.new("%ctx"))
52
+ sym = env.call(Sym.new("sym"))
53
+ value_form = env.call(Sym.new("val"))
54
+ value = evaler.call(value_form, context: env)
55
+
56
+ caller_context.call(sym, value)
57
+ }
58
+ )
59
+ end
60
+
61
+ def fn_fn
62
+ Lm.new(
63
+ params: [Sym.new("declaration")],
64
+ body: ->(env, evaler) {
65
+ declaration = env.call(Sym.new("declaration"))
66
+ caller_context = env.call(Sym.new("%ctx"))
67
+
68
+ params = declaration.head.value.map { |arg| arg.value }
69
+ Lm.new(
70
+ params: params,
71
+ body: declaration.tail,
72
+ closure: caller_context
73
+ )
74
+ }
75
+ )
76
+ end
77
+
78
+ def fn_cond
79
+ Lm.new(
80
+ params: [Sym.new("tuples")],
81
+ body: ->(env, evaler) {
82
+ conditions = env.lookup(Sym.new("tuples"))
83
+ find_true(conditions, evaler, env)
84
+ }
85
+ )
86
+ end
87
+
88
+ def find_true(conditions, evaler, context)
89
+ return nil if conditions.empty?
90
+ tuple = conditions.head.value
91
+
92
+ raise "NOPE: what is the expression?" if tuple.tail.empty?
93
+
94
+ if evaler.call(tuple.head, context: context) == true
95
+ tuple.tail.to_a.reduce(nil) { |result, form|
96
+ evaler.call(form, context: context)
97
+ }
98
+ else
99
+ find_true(conditions.tail, evaler, context)
100
+ end
101
+ end
102
+
103
+ def fn_dot
104
+ Lm.new(
105
+ params: [Sym.new("path")],
106
+ body: ->(env, evaler) {
107
+ context = env.lookup(Sym.new("%ctx"))
108
+ path = env.lookup(Sym.new("path"))
109
+
110
+ target =
111
+ begin
112
+ evaler.call(path.head, context: context)
113
+ rescue Ruspea::Error::Resolution
114
+ # if form is a symbol not solvable in the current context,
115
+ # then we use it as the name of the "contantizable" thing.
116
+ path.head.value.to_s
117
+ end
118
+
119
+ method = path.tail.head.value.to_s
120
+
121
+ has_args = !path.tail.tail.empty?
122
+ if has_args
123
+ args = path.tail.tail
124
+ .to_a
125
+ .map { |form|
126
+ evaler.call(form, context: context) }
127
+ target.send(method, *args)
128
+ else
129
+ target.send(method)
130
+ end
131
+ }
132
+ )
133
+ end
134
+
135
+ def fn_constantize
136
+ Lm.new(
137
+ params: [Sym.new("path")],
138
+ body: ->(env, evaler) {
139
+ path = env.lookup(Sym.new("path"))
140
+ Array(path)
141
+ .map { |target| target.to_s }
142
+ .reduce(nil) { |value, component|
143
+ value = ruby.constantize(component)
144
+ }
145
+ }
146
+ )
147
+ end
148
+
149
+ def fn_eval
150
+ fn = Fn.new
151
+
152
+ fn.add(
153
+ Lm.new(
154
+ params: [Sym.new("forms")],
155
+ body: ->(env, evaler) {
156
+ forms = evaler.call(
157
+ env.lookup(Sym.new("forms")), context: env)
158
+ forms.reduce(nil) { |_, form|
159
+ evaler.call(form, context: env)
160
+ }
161
+ }
162
+ )
163
+ )
164
+
165
+ fn.add(
166
+ Lm.new(
167
+ params: [Sym.new("forms"), Sym.new("context")],
168
+ body: ->(env, evaler) {
169
+ context = evaler.call(
170
+ env.lookup(Sym.new("context")), context: env)
171
+
172
+ forms = evaler.call(
173
+ env.lookup(Sym.new("forms")), context: env)
174
+
175
+ forms.reduce(nil) { |_, form|
176
+ evaler.call(form, context: context)
177
+ }
178
+ }
179
+ )
180
+ )
181
+
182
+ fn
183
+ end
184
+ end
185
+ end