sol 0.0.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,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