sol 0.0.1

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