sol 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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