ruspea_lang 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,36 @@
1
+ module Ruspea::Language
2
+ class Ruby < Ruspea::Runtime::Env
3
+ # This method was copied as is from:
4
+ # https://github.com/rails/rails/blob/66cabeda2c46c582d19738e1318be8d59584cc5b/activesupport/lib/active_support/inflector/methods.rb#L271
5
+ def constantize(camel_cased_word)
6
+ names = camel_cased_word.split("::")
7
+
8
+ # Trigger a built-in NameError exception including the ill-formed constant in the message.
9
+ Object.const_get(camel_cased_word) if names.empty?
10
+
11
+ # Remove the first blank element in case of '::ClassName' notation.
12
+ names.shift if names.size > 1 && names.first.empty?
13
+
14
+ names.inject(Object) do |constant, name|
15
+ if constant == Object
16
+ constant.const_get(name)
17
+ else
18
+ candidate = constant.const_get(name)
19
+ next candidate if constant.const_defined?(name, false)
20
+ next candidate unless Object.const_defined?(name)
21
+
22
+ # Go down the ancestors to check if it is owned directly. The check
23
+ # stops when we reach Object or the end of ancestors tree.
24
+ constant = constant.ancestors.inject(constant) do |const, ancestor|
25
+ break const if ancestor == Object
26
+ break ancestor if ancestor.const_defined?(name, false)
27
+ const
28
+ end
29
+
30
+ # owner is in Object, so raise
31
+ constant.const_get(name, false)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,54 @@
1
+ module Ruspea
2
+ class Printer
3
+ include Ruspea::Runtime
4
+
5
+ def call(value)
6
+ case value
7
+ when Ruspea::Interpreter::Form
8
+ call(value.value)
9
+ when String
10
+ value.inspect
11
+ when Numeric
12
+ value.inspect
13
+ when Sym
14
+ value.to_s
15
+ when List
16
+ print_list(value)
17
+ when Lm
18
+ print_fn(value)
19
+ else
20
+ value.inspect
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def print_list(list)
27
+ count = list.count
28
+
29
+ printed = list
30
+ .take(10)
31
+ .to_a
32
+ .map { |intern| call(intern) }
33
+ .join(" ")
34
+
35
+ if count > 10
36
+ "(#{printed} ...) # count: #{count}"
37
+ else
38
+ "(#{printed})"
39
+ end
40
+ end
41
+
42
+ def print_fn(fn)
43
+ params = fn.params.map { |param| call(param) }.join(" ")
44
+ body =
45
+ if fn.body.respond_to?(:call)
46
+ "#- internal -#\n #{fn.body.inspect}"
47
+ else
48
+ body = fn.body.to_a.map { |exp| call(exp) }.join("\n ")
49
+ end
50
+
51
+ "(fn [#{params}]\n #{body})"
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,85 @@
1
+ module Ruspea::Repl
2
+ class Loop
3
+ include Ruspea::Interpreter
4
+
5
+ def initialize
6
+ @reader = Reader.new
7
+ @evaler = Evaler.new
8
+ @printer = Ruspea::Printer.new
9
+ @env = Ruspea::Runtime::Env.new(Ruspea::Language::Core.new)
10
+
11
+ load_repl(@env)
12
+ end
13
+
14
+ def run(input: STDIN, env: nil)
15
+ trap "SIGINT" do
16
+ puts "See you soon."
17
+ exit(130)
18
+ end
19
+
20
+ env ||= @env
21
+ prompt_back
22
+
23
+ while(line = input.gets)
24
+ begin
25
+ interprete(line.chomp, env)
26
+ prompt_back
27
+ rescue Ruspea::Error::Standard => e
28
+ print_error e
29
+ rescue StandardError => e
30
+ puts "A Ruby exception was raised. Inspect it? Y/n"
31
+ yes = !["n", "no"].include?(input.gets.chomp.downcase)
32
+ if yes
33
+ puts "Error: #{e.message}\nBacktrace:\n"
34
+ puts "\s\s#{e.backtrace.join("\n\s\s")}"
35
+ end
36
+ prompt_back
37
+ end
38
+ end
39
+
40
+ puts "See you soon."
41
+ end
42
+
43
+ private
44
+
45
+ def load_repl(env)
46
+ Dir.glob(
47
+ File.expand_path("../*.rsp", __FILE__)).each do |file|
48
+ puts "loading repl: #{Pathname.new(file).basename}"
49
+ _, forms = @reader.call(File.read(file))
50
+ @evaler.call(forms, context: env)
51
+ end
52
+ puts "repl loaded."
53
+ end
54
+
55
+ def prompt_back(ns: "#user")
56
+ puts ""
57
+ print "#{ns}=> "
58
+ end
59
+
60
+ def print_error(e)
61
+ puts e.class
62
+ puts e.message
63
+ prompt_back
64
+ end
65
+
66
+ def should_exit?(env)
67
+ env.call Ruspea::Runtime::Sym.new("%repl_should_exit?")
68
+ rescue Ruspea::Error::Resolution
69
+ false
70
+ end
71
+
72
+ def interprete(line, env)
73
+ _, forms = @reader.call(line)
74
+ forms.each do |expression|
75
+ # puts "read: "
76
+ # pp expression
77
+ result = @evaler.call(expression, context: env)
78
+ # puts "evaled: "
79
+ # pp result
80
+ Process.kill("SIGINT", Process.pid) if should_exit?(env)
81
+ print @printer.call(result)
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,2 @@
1
+ (def exit (fn [] (%ctx %repl_should_exit? true)))
2
+ (def bye exit)
@@ -0,0 +1,119 @@
1
+ require "singleton"
2
+
3
+ module Ruspea::Runtime
4
+ class Env
5
+ class Empty
6
+ include Ruspea::Error
7
+ include Singleton
8
+
9
+ def initialize(*_)
10
+ end
11
+
12
+ def define(*_)
13
+ nil
14
+ end
15
+
16
+ def lookup(sym)
17
+ raise(Resolution.new(sym))
18
+ end
19
+
20
+ def eql?(other)
21
+ self == other
22
+ end
23
+
24
+ def around(env)
25
+ env
26
+ end
27
+
28
+ def ==(other)
29
+ return true if other.is_a? Empty
30
+ false
31
+ end
32
+ end
33
+
34
+ include Ruspea::Error
35
+
36
+ def initialize(context = nil, table = nil)
37
+ @table =
38
+ if table.nil?
39
+ {}
40
+ else
41
+ table.dup
42
+ end
43
+ @context = context || Empty.instance
44
+
45
+ @fn = Fn.new(fn_define, fn_fetch)
46
+ end
47
+
48
+ def define(sym, value)
49
+ @table[sym] = value
50
+ end
51
+
52
+ def lookup(sym)
53
+ @table.fetch(sym) { @context.lookup(sym) }
54
+ end
55
+
56
+ def call(*args, context: nil, evaler: nil)
57
+ # evaler will always send an array
58
+ # to a #call that is not a #arity
59
+ if args.is_a?(Array) && args.length == 1
60
+ args = args.first
61
+ end
62
+ @fn.call(*args, context: context, evaler: evaler)
63
+ end
64
+
65
+ def around(env)
66
+ new_context = env
67
+ .context
68
+ .around(self)
69
+
70
+ Env.new(new_context, env.table)
71
+ end
72
+
73
+ def eql?(other)
74
+ self == other
75
+ end
76
+
77
+ def ==(other)
78
+ return false if self.class != other.class
79
+ @table == other.table && @context == other.context
80
+ end
81
+
82
+ def hash
83
+ @table.hash + @context.hash + :rsp_env.hash
84
+ end
85
+
86
+ def inspect
87
+ @table.map { |k, v|
88
+ "#{k} => #{v}"
89
+ }
90
+ end
91
+
92
+ protected
93
+
94
+ attr_accessor :table, :context
95
+
96
+ private
97
+
98
+ def fn_define
99
+ @fn_define ||= Lm.new(
100
+ params: [Sym.new("sym"), Sym.new("val")],
101
+ body: ->(env, _) {
102
+ define(
103
+ env.lookup(Sym.new("sym")),
104
+ env.lookup(Sym.new("val"))
105
+ )
106
+ }
107
+ )
108
+ end
109
+
110
+ def fn_fetch
111
+ @fn_fetch ||= Lm.new(
112
+ params: [Sym.new("sym")],
113
+ body: ->(env, _) {
114
+ lookup env.lookup(Sym.new("sym"))
115
+ }
116
+ )
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,23 @@
1
+ module Ruspea::Runtime
2
+ class Fn
3
+ def initialize(*lambdas)
4
+ @arities = {}
5
+
6
+ lambdas.each { |lm| add lm }
7
+ end
8
+
9
+ def add(lm)
10
+ arities[lm.arity] = lm
11
+ end
12
+
13
+ def call(*args, context: nil, evaler: nil)
14
+ arities
15
+ .fetch(args.length)
16
+ .call(*args, context: context, evaler: evaler)
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :arities
22
+ end
23
+ end
@@ -0,0 +1,67 @@
1
+ module Ruspea::Runtime
2
+ class List
3
+ attr_reader :head, :tail, :count
4
+
5
+ def self.create(*items)
6
+ return Nill.instance if items.length == 0
7
+
8
+ new items[0], create(*items[1..items.length]), count: items.length
9
+ end
10
+
11
+ def initialize(head, tail = Nill.instance, count: 0)
12
+ @head = head
13
+ @tail = tail
14
+ @count = count
15
+ end
16
+
17
+ def cons(el)
18
+ self.class.new el, self, count: @count + 1
19
+ end
20
+
21
+ def car
22
+ head
23
+ end
24
+
25
+ def cdr
26
+ tail
27
+ end
28
+
29
+ def take(amount, from = self)
30
+ return Nill.instance if amount == 0 || from.empty?
31
+
32
+ from
33
+ .take(amount - 1, from.tail)
34
+ .cons(from.head)
35
+ end
36
+
37
+ def empty?
38
+ false
39
+ end
40
+
41
+ def to_a(list = self, array = [])
42
+ return array if list.empty?
43
+
44
+ to_a(
45
+ list.tail,
46
+ array + [list.head])
47
+ end
48
+
49
+ def eq?(other)
50
+ self == other
51
+ end
52
+
53
+ def eql?(other)
54
+ self == other
55
+ end
56
+
57
+ def ==(other)
58
+ return false if self.class != other.class
59
+ head == other.head && tail == other.tail
60
+ end
61
+
62
+ def inspect
63
+ @printer ||= Ruspea::Printer.new
64
+ @printer.call self
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,74 @@
1
+ module Ruspea::Runtime
2
+ class Lm
3
+ attr_reader :arity, :params, :body
4
+
5
+ def initialize(params: [], body: Nill.instance, closure: Env::Empty.instance)
6
+ @params = params
7
+ @arity = params.length
8
+ @body = body
9
+ @closure = closure
10
+ end
11
+
12
+ def call(*args, context: nil, evaler: nil)
13
+ context ||= Env::Empty.instance
14
+ evaler ||= Ruspea::Interpreter::Evaler.new
15
+
16
+ env, callable = env_and_callable_body(args, context, evaler)
17
+ env.define Sym.new("%ctx"), context
18
+
19
+ callable.call closure.around(env), evaler, args
20
+ end
21
+
22
+ private
23
+
24
+ attr_reader :closure
25
+
26
+ def env_and_callable_body(args, context, evaler)
27
+ if body.respond_to? :call
28
+ [
29
+ environment_with(args, context, evaler: ->(arg, _) { arg }),
30
+ ->(environment, evaler, _) { body.call environment, evaler }
31
+ ]
32
+ else
33
+ if args.length != arity
34
+ raise Ruspea::Error::Arity.new(arity, args.length)
35
+ end
36
+
37
+ [
38
+ environment = environment_with(args, context, evaler: evaler),
39
+ ->(environment, evaler, args) {
40
+ evaluate(body, context: environment, evaler: evaler)
41
+ }
42
+ ]
43
+ end
44
+ end
45
+
46
+ def environment_with(args, context, evaler:)
47
+ env = Env.new(context)
48
+ args.each_with_index.reduce(env) { |env, tuple|
49
+ arg, idx = tuple
50
+ sym =
51
+ if arg.is_a?(Ruspea::Interpreter::Form)
52
+ arg.value
53
+ else
54
+ arg
55
+ end
56
+
57
+ env.tap { |e|
58
+ e.define(
59
+ params[idx], evaler.call(sym, context: context))
60
+ }
61
+ }
62
+ end
63
+
64
+ def evaluate(forms, result = nil, evaler:, context:)
65
+ return result if forms.empty?
66
+
67
+ evaluate(
68
+ forms.tail,
69
+ evaler.call(forms.head, context: context),
70
+ evaler: evaler,
71
+ context: context)
72
+ end
73
+ end
74
+ end