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,178 @@
1
+ class Parser
2
+
3
+ #Declare tokens by the lexer
4
+
5
+ token IF
6
+ token FUNC
7
+ token NEWLINE
8
+ token NUMBER
9
+ token STRING
10
+ token TRUE FALSE NULL
11
+ token IDENTIFIER
12
+ token CONSTANT
13
+
14
+ prechigh
15
+ left "."
16
+ left "!"
17
+ left "*" "/"
18
+ left "+" "-"
19
+ left ">" ">=" "<" "<="
20
+ left "==" "!="
21
+ left "&&"
22
+ left "||"
23
+ left "="
24
+ left ","
25
+ preclow
26
+
27
+ rule
28
+
29
+ # All rules are declared in this format:
30
+ #
31
+ # RuleName:
32
+ # OtherRule TOKEN AnotherRule { code to run when this matches }
33
+ # | OtherRule { ... }
34
+ # ;
35
+ #
36
+ # In the code section (inside the {...} on the right):
37
+ # - Assign to "result" the value returned by the rule.
38
+
39
+ # All parsinng will end in this rule, being the trunk of the AST
40
+ Root:
41
+ /* nothing */ { result = Nodes.new([]) }
42
+ | Expressions { result = val[0] }
43
+ ;
44
+
45
+ # Any list of expressions are seperated by line breaks
46
+ Expressions:
47
+ Expression { result = Nodes.new(val) }
48
+ | Expressions Expression { result = val[0] << val[1] }
49
+ # To ignore trailing line breaks
50
+ | Expressions Terminator { result = val[0] }
51
+ | Terminator { result = Nodes.new([]) }
52
+ ;
53
+
54
+ # All types of expressions in our language
55
+ Expression:
56
+ Literal
57
+ | Call
58
+ | Operator
59
+ | Constant
60
+ | Assign
61
+ | Func
62
+ | If
63
+ | "(" Expression ")" { result = val[1] }
64
+ ;
65
+
66
+ # All tokens that can terminate and expression
67
+ Terminator:
68
+ NEWLINE
69
+ | ";"
70
+ ;
71
+
72
+ # All hardcoded value
73
+ Literal:
74
+ NUMBER { result = NumberNode.new(val[0]) }
75
+ | STRING { result = StringNode.new(val[0]) }
76
+ | TRUE { result = TrueNode.new }
77
+ | FALSE { result = FalseNode.new }
78
+ | NULL { result = NullNode.new }
79
+ ;
80
+
81
+ # A method call
82
+ Call:
83
+ # Method
84
+ # IDENTIFIER { result = CallNode.new(nil, val[0], []) }
85
+ # method arguments are optional
86
+ IDENTIFIER "(" ArgList ")" { result = CallNode.new(nil, val[0], val[2]) }
87
+ # receiver.method
88
+ # | Expression "." IDENTIFIER { result = CallNode.new(val[0], val[2], []) }
89
+ # receiver.method(arguments) arguments are optional
90
+ | Expression "." IDENTIFIER "(" ArgList ")" { result = CallNode.new(val[0], val[2], val[4]) }
91
+ ;
92
+
93
+ ArgList:
94
+ /* Nothing */ { result = [] }
95
+ | Expression { result = val }
96
+ | ArgList "," Expression { result = val[0] << val[2]}
97
+ ;
98
+
99
+ Operator:
100
+ # Binary operators
101
+ Expression "||" Expression { result = CallNode.new(val[0], val[1], [val[2]]) }
102
+ | Expression '&&' Expression { result = CallNode.new(val[0], val[1], [val[2]]) }
103
+ | Expression '==' Expression { result = CallNode.new(val[0], val[1], [val[2]]) }
104
+ | Expression '!=' Expression { result = CallNode.new(val[0], val[1], [val[2]]) }
105
+ | Expression '>' Expression { result = CallNode.new(val[0], val[1], [val[2]]) }
106
+ | Expression '>=' Expression { result = CallNode.new(val[0], val[1], [val[2]]) }
107
+ | Expression '<' Expression { result = CallNode.new(val[0], val[1], [val[2]]) }
108
+ | Expression '<=' Expression { result = CallNode.new(val[0], val[1], [val[2]]) }
109
+ | Expression '+' Expression { result = CallNode.new(val[0], val[1], [val[2]]) }
110
+ | Expression '-' Expression { result = CallNode.new(val[0], val[1], [val[2]]) }
111
+ | Expression '*' Expression { result = CallNode.new(val[0], val[1], [val[2]]) }
112
+ | Expression '/' Expression { result = CallNode.new(val[0], val[1], [val[2]]) }
113
+ ;
114
+
115
+ Constant:
116
+ CONSTANT { result = GetConstantNode.new(val[0]) }
117
+ ;
118
+
119
+ # Assignment to a variable or constant
120
+ Assign:
121
+ IDENTIFIER "=" Expression { result = SetLocalNode.new(val[0], val[2]) }
122
+ | CONSTANT "=" Expression { result = SetLocalNode.new(val[0], val[2]) }
123
+ ;
124
+
125
+ # Method definition
126
+ Func:
127
+ # ParamList is optional
128
+ FUNC IDENTIFIER "(" ParamList ")" Block { result = FuncNode.new(val[1], val[3], val[5]) }
129
+ ;
130
+
131
+ ParamList:
132
+ /* Nothing */
133
+ | IDENTIFIER { result = [] }
134
+ | ParamList "," IDENTIFIER { result = val }
135
+ ;
136
+
137
+ # If block
138
+ If:
139
+ IF Expression Block { result = IfNode.new(val[1], val[2]) }
140
+ ;
141
+
142
+ # A block of code all the work was done by the lexer
143
+ Block:
144
+ "{" Expressions "}" { result = val[1] }
145
+ ;
146
+
147
+ end
148
+
149
+ ---- header
150
+
151
+ require_relative "lexer"
152
+
153
+ require_relative "nodes"
154
+
155
+ module Sol
156
+
157
+ ---- inner
158
+
159
+ # This code will be puts as-is in the Parser class
160
+ def parse(input, show_tokens=true)
161
+
162
+ @tokens = Sol::Lexer.new.tokenise(input) # Tokenise the code using our lexer
163
+
164
+ puts @tokens.inspect if show_tokens
165
+
166
+ do_parse # Kickoff the parsing process
167
+
168
+ end
169
+
170
+ def next_token
171
+
172
+ @tokens.shift
173
+
174
+ end
175
+
176
+ ---- footer
177
+
178
+ end # End the module
@@ -0,0 +1,187 @@
1
+ require_relative "parser"
2
+ require_relative "runtime"
3
+
4
+ module Sol
5
+
6
+ class Interpreter
7
+
8
+ def initialize
9
+
10
+ @parser = Sol::Parser.new
11
+
12
+ end
13
+
14
+ def eval(input)
15
+
16
+ @parser.parse(input).eval(RuntimeModel::Runtime)
17
+
18
+ end
19
+
20
+ end
21
+
22
+ class Nodes
23
+
24
+ # This method is the "interpreter" part of our language. All nodes know how to eval
25
+ # itself and returns the result of its evaluation by implementing the "eval" method.
26
+ # The "context" variable is the environment in which the node is evaluated (local variables, current class, etc.).
27
+ def eval(context)
28
+
29
+ return_value = nil
30
+
31
+ nodes.each do |node|
32
+
33
+ return_value = node.eval(context)
34
+
35
+ end
36
+
37
+ # The last value evaluated in a method is the return value. Or null if node
38
+ return_value || RuntimeModel::Runtime["null"]
39
+
40
+ end
41
+
42
+ end
43
+
44
+ class NumberNode
45
+
46
+ def eval(context)
47
+
48
+ # Here we access the Runtime, which we'll see in the next section, to create a new instance of the Number class
49
+ RuntimeModel::Runtime["Number"].new_with_value(value)
50
+
51
+ end
52
+
53
+ end
54
+
55
+ class StringNode
56
+
57
+ def eval(context)
58
+
59
+ RuntimeModel::Runtime["String"].new_with_value(value)
60
+
61
+ end
62
+
63
+ end
64
+
65
+ class TrueNode
66
+
67
+ def eval(context)
68
+
69
+ RuntimeModel::Runtime["true"]
70
+
71
+ end
72
+
73
+ end
74
+
75
+ class FalseNode
76
+
77
+ def eval(context)
78
+
79
+ RuntimeModel::Runtime["false"]
80
+
81
+ end
82
+
83
+ end
84
+
85
+ class NullNode
86
+
87
+ def eval(context)
88
+
89
+ RuntimeModel::Runtime["null"]
90
+
91
+ end
92
+
93
+ end
94
+
95
+ class CallNode
96
+
97
+ def eval(context)
98
+
99
+ if receiver.nil? && arguments.empty? && RuntimeModel::Runtime.locals[method]
100
+
101
+ return context::Runtime.locals[method]
102
+
103
+ else
104
+
105
+ if receiver
106
+
107
+ value = receiver.eval(context)
108
+
109
+ else
110
+
111
+ value = RuntimeModel::Runtime.current_self # I think this works
112
+
113
+ end
114
+
115
+ eval_arguments = arguments.map do |arg|
116
+
117
+ arg.eval(context)
118
+
119
+ end
120
+
121
+ value.call(method, eval_arguments)
122
+
123
+ end
124
+
125
+ end
126
+
127
+ end
128
+
129
+ class GetConstantNode
130
+
131
+ def eval(context)
132
+
133
+ context[name]
134
+
135
+ end
136
+
137
+ end
138
+
139
+ class SetConstantNode
140
+
141
+ def eval(context)
142
+
143
+ context[name] = value.eval(context)
144
+
145
+ end
146
+
147
+ end
148
+
149
+ class SetLocalNode
150
+
151
+ def eval(context)
152
+
153
+ context.locals[name] = value.eval(context)
154
+
155
+ end
156
+
157
+ end
158
+
159
+ class FuncNode
160
+
161
+ def eval(context)
162
+
163
+ # Defining a method is adding a method to the current class
164
+ method = RuntimeModel::SolFunction.new(params, body)
165
+
166
+ RuntimeModel::Runtime.current_class.runtime_methods[name] = method # I think this works
167
+
168
+ end
169
+
170
+ end
171
+
172
+ class IfNode
173
+
174
+ def eval(context)
175
+
176
+ # We turn the condition node innto a Ruby value to use Ruby's "if" control structure
177
+ if condition.eval(context).ruby_value
178
+
179
+ body.eval(context)
180
+
181
+ end
182
+
183
+ end
184
+
185
+ end
186
+
187
+ end
@@ -0,0 +1,163 @@
1
+ # encoding=utf-8
2
+
3
+ require "readline"
4
+
5
+ module Sol
6
+
7
+ class Lexer
8
+
9
+ attr_reader :KEYWORDS
10
+
11
+ IDENTIFIER = /\A([a-zA-Z]\p{WORD}+\w*)/
12
+
13
+ NUMBER = /\A([0-9]+)/
14
+
15
+ STRING = /\A["'](.*?)["']/
16
+
17
+ OPERATOR = /\A(\|\||&&|==|!=|<=|>=)/
18
+
19
+ WHITESPACE = /\A([ \t\r\n]+)/
20
+
21
+ NEWLINE = /\A([\r\n])+/
22
+
23
+ def initialize
24
+
25
+ @KEYWORDS = ["func", "if", "true", "false", "null"]
26
+
27
+ end
28
+
29
+ # This is how to implement a very simple scanner.
30
+ # Scan one caracter at the time until you find something to parse.
31
+
32
+ def tokenise(input)
33
+
34
+ @input = input.chomp # Cleanup code by remove extra line breaks
35
+
36
+ @i = 0 # Current character position we're parsing
37
+
38
+ @tokens = [] # Collection of all parsed tokens in the form [:TOKEN_TYPE, value]
39
+
40
+ while @i < @input.length
41
+
42
+ @chunk = @input[@i..-1]
43
+
44
+ extract_next_token
45
+
46
+ end
47
+
48
+ return @tokens
49
+
50
+ end
51
+
52
+ def repl
53
+
54
+ loop do
55
+
56
+ line = Readline::readline('> ')
57
+
58
+ break if line.nil? || line == 'quit'
59
+
60
+ Readline::HISTORY.push(line)
61
+
62
+ puts "#{tokenise(line)}" # Brackets are for clarity purposes
63
+
64
+ end
65
+
66
+ end
67
+
68
+ private
69
+
70
+ def extract_next_token
71
+
72
+ return if identifier_token
73
+
74
+ return if number_token
75
+
76
+ return if string_token
77
+
78
+ return if whitespace_token
79
+
80
+ return literal_token
81
+
82
+ end
83
+
84
+ # Matching if, print, method names, etc.
85
+
86
+ def identifier_token
87
+
88
+ return false unless identifier = @chunk[IDENTIFIER, 1]
89
+
90
+ # Keywords are special identifiers tagged with their own name, 'if' will result
91
+ # in an [:IF, "if"] token
92
+
93
+ if @KEYWORDS.include?(identifier)
94
+
95
+ @tokens << [identifier.upcase.to_sym, identifier]
96
+
97
+ else
98
+
99
+ @tokens << [:IDENTIFIER, identifier]
100
+
101
+ end
102
+
103
+ @i += identifier.length
104
+
105
+ end
106
+
107
+ def number_token
108
+
109
+ return false unless number = @chunk[NUMBER, 1]
110
+
111
+ @tokens << [:NUMBER, number.to_i]
112
+
113
+ @i += number.length
114
+
115
+ end
116
+
117
+ def string_token
118
+
119
+ return false unless string = @chunk[STRING, 1]
120
+
121
+ @tokens << [:STRING, string]
122
+
123
+ @i += string.length + 2
124
+
125
+ end
126
+
127
+ # Ignore whitespace
128
+
129
+ def whitespace_token
130
+
131
+ return false unless whitespace = @chunk[WHITESPACE, 1]
132
+
133
+ @i += whitespace.length
134
+
135
+ end
136
+
137
+ # We treat all other single characters as a token. Eg.: ( ) , . !
138
+
139
+ def literal_token
140
+
141
+ value = @chunk[NEWLINE, 1]
142
+
143
+ if value
144
+
145
+ @tokens << ["\n", "\n"] unless @tokens.last && @tokens.last[0] == "\n"
146
+
147
+ return @i + value.length
148
+
149
+ end
150
+
151
+ value = @chunk[OPERATOR, 1]
152
+
153
+ value ||= @chunk[0, 1]
154
+
155
+ @tokens << [value, value]
156
+
157
+ @i += value.length
158
+
159
+ end
160
+
161
+ end
162
+
163
+ end