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.
@@ -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