sol 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/Gemfile +4 -0
- data/LICENSE +339 -0
- data/README.md +29 -0
- data/Rakefile +40 -0
- data/bin/sol +4 -0
- data/lib/sol.rb +7 -0
- data/lib/sol/cli.rb +57 -0
- data/lib/sol/example.sol +9 -0
- data/lib/sol/grammar.tab.rb +456 -0
- data/lib/sol/grammar.y +178 -0
- data/lib/sol/interpreter.rb +187 -0
- data/lib/sol/lexer.rb +163 -0
- data/lib/sol/nodes.rb +79 -0
- data/lib/sol/parser.rb +639 -0
- data/lib/sol/parser.y +639 -0
- data/lib/sol/runtime.rb +13 -0
- data/lib/sol/runtime/bootstrap.rb +156 -0
- data/lib/sol/runtime/class.rb +69 -0
- data/lib/sol/runtime/context.rb +42 -0
- data/lib/sol/runtime/function.rb +42 -0
- data/lib/sol/runtime/object.rb +32 -0
- data/lib/sol/spec/lexer_test.rb +68 -0
- data/lib/sol/spec/parser_test.rb +38 -0
- data/lib/sol/version.rb +5 -0
- data/sol.gemspec +25 -0
- metadata +113 -0
data/lib/sol/runtime.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
=begin
|
2
|
+
require_relative "runtime/object"
|
3
|
+
require_relative "runtime/class"
|
4
|
+
require_relative "runtime/method"
|
5
|
+
require_relative "runtime/context"
|
6
|
+
require_relative "runtime/bootstrap"
|
7
|
+
=end
|
8
|
+
|
9
|
+
Dir[File.dirname(__FILE__) + "/runtime/*.rb"].each { |file| require file }
|
10
|
+
|
11
|
+
module Sol
|
12
|
+
|
13
|
+
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
require_relative "class"
|
2
|
+
require_relative "context"
|
3
|
+
|
4
|
+
module Sol
|
5
|
+
|
6
|
+
module RuntimeModel
|
7
|
+
|
8
|
+
# Bootstrap the runtime. This is where we assemble all the classes and objects together to form the runtime
|
9
|
+
# Whats happening in the runtime
|
10
|
+
sol_class = SolClass.new
|
11
|
+
|
12
|
+
sol_class.runtime_class = sol_class
|
13
|
+
|
14
|
+
object_class = SolClass.new
|
15
|
+
|
16
|
+
object_class.runtime_class = sol_class
|
17
|
+
|
18
|
+
Runtime = Context.new(object_class.new)
|
19
|
+
|
20
|
+
Runtime["Class"] = sol_class
|
21
|
+
|
22
|
+
Runtime["Object"] = object_class
|
23
|
+
|
24
|
+
Runtime["Number"] = SolClass.new
|
25
|
+
|
26
|
+
Runtime["String"] = SolClass.new
|
27
|
+
|
28
|
+
# Everything is an object in our language, even true, false and null. So they nneed to have a class too.
|
29
|
+
Runtime["TrueClass"] = SolClass.new
|
30
|
+
|
31
|
+
Runtime["FalseClass"] = SolClass.new
|
32
|
+
|
33
|
+
Runtime["NullClass"] = SolClass.new
|
34
|
+
|
35
|
+
Runtime["true"] = Runtime["TrueClass"].new_with_value(true)
|
36
|
+
|
37
|
+
Runtime["false"] = Runtime["FalseClass"].new_with_value(false)
|
38
|
+
|
39
|
+
Runtime["null"] = Runtime["TrueClass"].new_with_value(nil)
|
40
|
+
|
41
|
+
# Add a few core methods to the runtime
|
42
|
+
|
43
|
+
# Print an object to the console, e.g print("hello, world")
|
44
|
+
Runtime["Object"].runtime_methods["print"] = proc do |receiver, arguments|
|
45
|
+
|
46
|
+
puts arguments.first.ruby_value
|
47
|
+
|
48
|
+
Runtime["Null"]
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
# Exit
|
53
|
+
Runtime["Object"].runtime_methods["exit"] = proc do |receiver|
|
54
|
+
|
55
|
+
Runtime["Null"]
|
56
|
+
|
57
|
+
exit(0)
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
# Quit
|
62
|
+
Runtime["Object"].runtime_methods["quit"] = proc do |receiver|
|
63
|
+
|
64
|
+
Runtime["Null"]
|
65
|
+
|
66
|
+
exit(0)
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
# Or (||)
|
71
|
+
Runtime["Number"].runtime_methods["||"] = proc do |receiver, arguments|
|
72
|
+
|
73
|
+
Runtime["Number"].new_with_value(receiver.ruby_value || arguments.first.ruby_value)
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
# And (&&)
|
78
|
+
Runtime["Number"].runtime_methods["&&"] = proc do |receiver, arguments|
|
79
|
+
|
80
|
+
Runtime["Number"].new_with_value(receiver.ruby_value && arguments.first.ruby_value)
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
# Equal to (==)
|
85
|
+
Runtime["Number"].runtime_methods["=="] = proc do |receiver, arguments|
|
86
|
+
|
87
|
+
Runtime["Number"].new_with_value(receiver.ruby_value == arguments.first.ruby_value)
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
# Does not equal to (!=)
|
92
|
+
Runtime["Number"].runtime_methods["!="] = proc do |receiver, arguments|
|
93
|
+
|
94
|
+
Runtime["Number"].new_with_value(receiver.ruby_value != arguments.first.ruby_value)
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
# Greater than (>)
|
99
|
+
Runtime["Number"].runtime_methods[">"] = proc do |receiver, arguments|
|
100
|
+
|
101
|
+
Runtime["Number"].new_with_value(receiver.ruby_value > arguments.first.ruby_value)
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
# Greater than or equal to (>=)
|
106
|
+
Runtime["Number"].runtime_methods[">="] = proc do |receiver, arguments|
|
107
|
+
|
108
|
+
Runtime["Number"].new_with_value(receiver.ruby_value >= arguments.first.ruby_value)
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
# Less than (<)
|
113
|
+
Runtime["Number"].runtime_methods["<"] = proc do |receiver, arguments|
|
114
|
+
|
115
|
+
Runtime["Number"].new_with_value(receiver.ruby_value < arguments.first.ruby_value)
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
# Less than or equal to (>=)
|
120
|
+
Runtime["Number"].runtime_methods["<="] = proc do |receiver, arguments|
|
121
|
+
|
122
|
+
Runtime["Number"].new_with_value(receiver.ruby_value <= arguments.first.ruby_value)
|
123
|
+
|
124
|
+
end
|
125
|
+
|
126
|
+
# Add
|
127
|
+
Runtime["Number"].runtime_methods["+"] = proc do |receiver, arguments|
|
128
|
+
|
129
|
+
Runtime["Number"].new_with_value(receiver.ruby_value + arguments.first.ruby_value)
|
130
|
+
|
131
|
+
end
|
132
|
+
|
133
|
+
# Minus
|
134
|
+
Runtime["Number"].runtime_methods["-"] = proc do |receiver, arguments|
|
135
|
+
|
136
|
+
Runtime["Number"].new_with_value(receiver.ruby_value - arguments.first.ruby_value)
|
137
|
+
|
138
|
+
end
|
139
|
+
|
140
|
+
# Times
|
141
|
+
Runtime["Number"].runtime_methods["*"] = proc do |receiver, arguments|
|
142
|
+
|
143
|
+
Runtime["Number"].new_with_value(receiver.ruby_value * arguments.first.ruby_value)
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
# Divide
|
148
|
+
Runtime["Number"].runtime_methods["/"] = proc do |receiver, arguments|
|
149
|
+
|
150
|
+
Runtime["Number"].new_with_value(receiver.ruby_value / arguments.first.ruby_value)
|
151
|
+
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require_relative "object"
|
2
|
+
|
3
|
+
module Sol
|
4
|
+
|
5
|
+
module RuntimeModel
|
6
|
+
|
7
|
+
# Represents a Sol class in the Ruby world. Classes are objects in Sol so they inherit from SolObject
|
8
|
+
class SolClass < SolObject
|
9
|
+
|
10
|
+
attr_reader :runtime_methods
|
11
|
+
|
12
|
+
# Create a new class. Number is an instance of Class for example
|
13
|
+
def initialize
|
14
|
+
|
15
|
+
@runtime_methods = {}
|
16
|
+
|
17
|
+
# Check if we're bootstrapping (launching the runtime). During this process the
|
18
|
+
# runtime is not fully initialised and core classes do not yet exists, so we defer
|
19
|
+
# using those once the language is bootstrapped.
|
20
|
+
# This solves the chicken-or-the-egg problem with the Class class. We can
|
21
|
+
# initialise Class then set Class.class = Class.
|
22
|
+
if defined?(Runtime) # RuntimeModel is a temporary name
|
23
|
+
|
24
|
+
runtime_class = Runtime["Class"]
|
25
|
+
|
26
|
+
else
|
27
|
+
|
28
|
+
runtime_class = nil
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
super(runtime_class)
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
# Lookup a method
|
37
|
+
def lookup(method_name)
|
38
|
+
|
39
|
+
method = @runtime_methods[method_name]
|
40
|
+
|
41
|
+
unless method
|
42
|
+
|
43
|
+
raise "Method not found: #{method_name}"
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
return method
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
# Create a new instance of the class
|
52
|
+
def new
|
53
|
+
|
54
|
+
SolObject.new(self)
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
# Create an instance of this Sol class that holds a Ruby value
|
59
|
+
def new_with_value(value)
|
60
|
+
|
61
|
+
SolObject.new(self, value)
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Sol
|
2
|
+
|
3
|
+
module RuntimeModel
|
4
|
+
|
5
|
+
class Context
|
6
|
+
|
7
|
+
attr_reader :locals, :current_self, :current_class
|
8
|
+
|
9
|
+
# We store constants as class variable (class variables start with @@ and instance
|
10
|
+
# variables start with @ in Ruby) since they are globally accessible. If you want to
|
11
|
+
# implement namespacing of constants, you could store it in the instance of this class.
|
12
|
+
@@constants = {}
|
13
|
+
|
14
|
+
def initialize(current_self, current_class=current_self.runtime_class)
|
15
|
+
|
16
|
+
@locals = {}
|
17
|
+
|
18
|
+
@current_self = current_self
|
19
|
+
|
20
|
+
@current_class = current_class
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
# Shortcuts to access constants, Runtime[...] instead of Runtime.constants[...]
|
25
|
+
|
26
|
+
def [](name)
|
27
|
+
|
28
|
+
@@constants[name]
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
def []=(name, value)
|
33
|
+
|
34
|
+
@@constants[name] = value
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Sol
|
2
|
+
|
3
|
+
module RuntimeModel
|
4
|
+
|
5
|
+
require_relative "object"
|
6
|
+
|
7
|
+
# Represents a method defined in the runtime
|
8
|
+
class SolFunction < SolObject
|
9
|
+
|
10
|
+
def initialize(params, body)
|
11
|
+
|
12
|
+
@params = params
|
13
|
+
|
14
|
+
@body = body
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
def call(reciever, arguments)
|
19
|
+
|
20
|
+
# Create a context of evaluation in which the method will execute
|
21
|
+
context = Context.new(reciever)
|
22
|
+
|
23
|
+
if @params.class == "NullClass"
|
24
|
+
|
25
|
+
# Assign arguments to local variables
|
26
|
+
@params.to_enum.each_with_index do |param, index|
|
27
|
+
|
28
|
+
context.locals[param] = arguments[index]
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
return @body.eval(context)
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Sol
|
2
|
+
|
3
|
+
module RuntimeModel
|
4
|
+
|
5
|
+
# Represents an Sol object instance in the Ruby world
|
6
|
+
class SolObject
|
7
|
+
|
8
|
+
attr_accessor :runtime_class, :ruby_value
|
9
|
+
|
10
|
+
# Each object have a class (named runtime_class to prevents errors with Ruby's class method).
|
11
|
+
# Optionally an object can hold a Ruby value (e.g strings and numbers)
|
12
|
+
def initialize(runtime_class, ruby_value=self)
|
13
|
+
|
14
|
+
@runtime_class = runtime_class
|
15
|
+
|
16
|
+
@ruby_value = ruby_value
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
# Call a method on the object
|
21
|
+
def call(method, arguments=[])
|
22
|
+
|
23
|
+
# Like a typical Class-based runtime model, we store methods in the class of the object
|
24
|
+
@runtime_class.lookup(method).call(self, arguments)
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require "minitest/spec"
|
2
|
+
require "minitest/autorun"
|
3
|
+
|
4
|
+
require_relative "../lexer" # Reqiure lexer in above folder
|
5
|
+
|
6
|
+
describe "lexer" do
|
7
|
+
|
8
|
+
before do
|
9
|
+
|
10
|
+
@lexer = Sol::Lexer.new
|
11
|
+
|
12
|
+
@literals = ["(", ")", ".", "!", "+", "-", "*", "/"]
|
13
|
+
|
14
|
+
@operators = ["+", "-", "*", "/", "&&", "==", "!=", "<=", ">="]
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
it "can accept all keywords" do
|
19
|
+
|
20
|
+
@lexer.KEYWORDS.each do |keyword|
|
21
|
+
|
22
|
+
assert_equal [[keyword.upcase.to_sym, keyword]], @lexer.tokenise(keyword)
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
it "can accept numbers" do
|
29
|
+
|
30
|
+
assert_equal [[:NUMBER, 123]], @lexer.tokenise("123")
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
it "can accept double qouted strings" do
|
35
|
+
|
36
|
+
# Escaped strings are the best
|
37
|
+
|
38
|
+
assert_equal [[:STRING, "hello"]], @lexer.tokenise("\"hello\"")
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
it "can accept single qouted strings" do
|
43
|
+
|
44
|
+
assert_equal [[:STRING, 'hello']], @lexer.tokenise("'hello'")
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
it "can accept operators" do
|
49
|
+
|
50
|
+
@operators.each do |operator|
|
51
|
+
|
52
|
+
assert_equal [[operator, operator]], @lexer.tokenise(operator)
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
it "can accept literals" do
|
59
|
+
|
60
|
+
@literals.each do |literal|
|
61
|
+
|
62
|
+
assert_equal [[literal, literal]], @lexer.tokenise(literal)
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "minitest/autorun"
|
2
|
+
require "minitest/spec"
|
3
|
+
|
4
|
+
require_relative "../parser.rb" # Reqiure parser in above folder
|
5
|
+
|
6
|
+
describe "parser" do
|
7
|
+
|
8
|
+
before do
|
9
|
+
|
10
|
+
@parser = Sol::Parser.new
|
11
|
+
|
12
|
+
@operators = ["||", "&&", "==", "!=", ">", ">=", "<", "<=", "+", "-", "*", "/"]
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
it "can do operations" do
|
17
|
+
|
18
|
+
@operators.each do |operator|
|
19
|
+
|
20
|
+
@parser.parse "1#{operator}1", show_tokens = false
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
it "can do functions" do
|
27
|
+
|
28
|
+
@parser.parse "func hello() {1 + 1}", show_tokens = false
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
it "can do if statements" do
|
33
|
+
|
34
|
+
@parser.parse "if 1 == 1 { 1 + 1 }", show_tokens = false
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|