taoism 1.0.1 → 1.0.3
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.
- checksums.yaml +4 -4
- data/bin/tao +46 -1
- data/bin/taoism +5 -0
- data/lib/taoism/console.rb +72 -0
- data/lib/taoism/environment.rb +44 -0
- data/lib/taoism/evaluator.rb +385 -0
- data/lib/taoism/lexer.rb +278 -0
- data/lib/taoism/nodes.rb +54 -0
- data/lib/taoism/parser.rb +1355 -0
- data/lib/taoism/runtime.rb +45 -0
- data/lib/taoism/token.rb +7 -0
- data/lib/taoism/token_type.rb +63 -0
- data/lib/taoism/types.rb +20 -0
- data/lib/taoism/version.rb +3 -0
- data/lib/taoism.rb +11 -1
- metadata +35 -16
- data/lib/tao/lexer/scanner/position.rb +0 -7
- data/lib/tao/lexer/scanner/position_tracking.rb +0 -19
- data/lib/tao/lexer/scanner.rb +0 -50
- data/lib/tao/lexer/tokenizer.rb +0 -17
- data/lib/tao/lexer.rb +0 -6
- data/lib/tao/token/types.rb +0 -34
- data/lib/tao/token.rb +0 -8
- data/lib/tao/version.rb +0 -3
data/lib/taoism/lexer.rb
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
module Taoism
|
|
2
|
+
class Lexer
|
|
3
|
+
KEYWORDS = {
|
|
4
|
+
'if' => TokenType::IF,
|
|
5
|
+
'else' => TokenType::ELSE,
|
|
6
|
+
'loop' => TokenType::LOOP,
|
|
7
|
+
'leave' => TokenType::LEAVE,
|
|
8
|
+
'switch' => TokenType::SWITCH,
|
|
9
|
+
'package' => TokenType::PACKAGE,
|
|
10
|
+
'import' => TokenType::IMPORT,
|
|
11
|
+
'const' => TokenType::CONST,
|
|
12
|
+
'data' => TokenType::DATA,
|
|
13
|
+
'fun' => TokenType::FUN,
|
|
14
|
+
'let' => TokenType::LET,
|
|
15
|
+
'mut' => TokenType::MUT,
|
|
16
|
+
'try' => TokenType::TRY,
|
|
17
|
+
'return' => TokenType::RETURN,
|
|
18
|
+
'and' => TokenType::AND,
|
|
19
|
+
'or' => TokenType::OR,
|
|
20
|
+
'not' => TokenType::NOT,
|
|
21
|
+
}.freeze
|
|
22
|
+
|
|
23
|
+
PUNCTUATION = {
|
|
24
|
+
'{' => TokenType::LBRACE,
|
|
25
|
+
'}' => TokenType::RBRACE,
|
|
26
|
+
'[' => TokenType::LSQUARE,
|
|
27
|
+
']' => TokenType::RSQUARE,
|
|
28
|
+
'(' => TokenType::LPAREN,
|
|
29
|
+
')' => TokenType::RPAREN,
|
|
30
|
+
',' => TokenType::COMMA,
|
|
31
|
+
':' => TokenType::COLON,
|
|
32
|
+
';' => TokenType::SEMI,
|
|
33
|
+
'.' => TokenType::DOT,
|
|
34
|
+
}.freeze
|
|
35
|
+
|
|
36
|
+
attr_reader :errors
|
|
37
|
+
|
|
38
|
+
def initialize(source)
|
|
39
|
+
@source = source
|
|
40
|
+
@offset = 0
|
|
41
|
+
@line = 1
|
|
42
|
+
@col = 0
|
|
43
|
+
@errors = ErrorHandler.new
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def next_token
|
|
47
|
+
loop do
|
|
48
|
+
return eof_token if at_end?
|
|
49
|
+
@start = pos
|
|
50
|
+
|
|
51
|
+
char = advance
|
|
52
|
+
next if whitespace?(char)
|
|
53
|
+
|
|
54
|
+
if char == '/' && peek == '/'
|
|
55
|
+
skip_line_comment
|
|
56
|
+
next
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
return scan_token(char)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def skip_line_comment
|
|
64
|
+
advance
|
|
65
|
+
until at_end? || peek == "\n"
|
|
66
|
+
advance
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def scan_token(char)
|
|
71
|
+
type = PUNCTUATION[char]
|
|
72
|
+
return make_token(type) if type
|
|
73
|
+
|
|
74
|
+
case char
|
|
75
|
+
when '+' then make_token(TokenType::PLUS)
|
|
76
|
+
when '-'
|
|
77
|
+
match('>') ? make_token(TokenType::ARROW) : make_token(TokenType::MINUS)
|
|
78
|
+
when '*' then make_token(TokenType::STAR)
|
|
79
|
+
when '/' then make_token(TokenType::SLASH)
|
|
80
|
+
when '%' then make_token(TokenType::MODULO)
|
|
81
|
+
when '>'
|
|
82
|
+
match('=') ? make_token(TokenType::GREATEREQ) : make_token(TokenType::GREATER)
|
|
83
|
+
when '<'
|
|
84
|
+
match('=') ? make_token(TokenType::LESSEQ) : make_token(TokenType::LESS)
|
|
85
|
+
when '='
|
|
86
|
+
if match('=')
|
|
87
|
+
make_token(TokenType::EQUALEQ)
|
|
88
|
+
elsif match('>')
|
|
89
|
+
make_token(TokenType::EQARROW)
|
|
90
|
+
else
|
|
91
|
+
make_token(TokenType::EQUAL)
|
|
92
|
+
end
|
|
93
|
+
when '!'
|
|
94
|
+
match('=') ? make_token(TokenType::BANGEQ) : make_token(TokenType::BANG)
|
|
95
|
+
when '&'
|
|
96
|
+
match('&') ? make_token(TokenType::AND) : make_token(TokenType::ILLEGAL)
|
|
97
|
+
when '|'
|
|
98
|
+
if match('>')
|
|
99
|
+
make_token(TokenType::PIPE)
|
|
100
|
+
elsif match('|')
|
|
101
|
+
make_token(TokenType::OR)
|
|
102
|
+
else
|
|
103
|
+
make_token(TokenType::ILLEGAL)
|
|
104
|
+
end
|
|
105
|
+
when '?'
|
|
106
|
+
match('.') ? make_token(TokenType::QDOT) : make_token(TokenType::ILLEGAL)
|
|
107
|
+
when '0'..'9' then number_token
|
|
108
|
+
when '"' then string_token
|
|
109
|
+
else
|
|
110
|
+
alpha?(char) ? identifier_token : make_token(TokenType::ILLEGAL)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def identifier_token
|
|
115
|
+
while alpha_numeric?(peek)
|
|
116
|
+
advance
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
str = lexeme
|
|
120
|
+
|
|
121
|
+
if bool_token?(str)
|
|
122
|
+
make_token(TokenType::BOOL)
|
|
123
|
+
elsif none_token?(str)
|
|
124
|
+
make_token(TokenType::NONE)
|
|
125
|
+
else
|
|
126
|
+
if type = KEYWORDS[str]
|
|
127
|
+
make_token(type)
|
|
128
|
+
else
|
|
129
|
+
make_token(TokenType::IDENTIFIER)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def string_token
|
|
135
|
+
loop do
|
|
136
|
+
if at_end?
|
|
137
|
+
@errors.add(:unterminated_string, "unterminated string", pos)
|
|
138
|
+
return make_token(TokenType::ILLEGAL, "")
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
case peek
|
|
142
|
+
when '"'
|
|
143
|
+
advance
|
|
144
|
+
return make_token(TokenType::STRING)
|
|
145
|
+
when "\n"
|
|
146
|
+
@errors.add(:unterminated_string, "unterminated string", pos)
|
|
147
|
+
return make_token(TokenType::ILLEGAL, "")
|
|
148
|
+
when '\\'
|
|
149
|
+
advance
|
|
150
|
+
if peek == '('
|
|
151
|
+
advance
|
|
152
|
+
while alpha_numeric?(peek)
|
|
153
|
+
advance
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
@errors.add(:bad_interpolation, "expected ')'", pos) unless peek == ')'
|
|
157
|
+
advance if peek == ')'
|
|
158
|
+
|
|
159
|
+
else
|
|
160
|
+
advance unless at_end?
|
|
161
|
+
end
|
|
162
|
+
else
|
|
163
|
+
advance
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def number_token
|
|
169
|
+
type = TokenType::INT
|
|
170
|
+
|
|
171
|
+
while digit?(peek) || peek == '_'
|
|
172
|
+
advance
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
if peek == '.' && digit?(peek_next)
|
|
176
|
+
type = TokenType::FLOAT
|
|
177
|
+
advance
|
|
178
|
+
|
|
179
|
+
while digit?(peek) || peek == '_'
|
|
180
|
+
advance
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
make_token(type)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def make_token(type, text = lexeme)
|
|
188
|
+
Token.new(type, text, nil, @start, pos)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def eof_token
|
|
192
|
+
Token.new(TokenType::EOF, "", nil, @start, pos)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def lexeme
|
|
196
|
+
@source[@start.offset...@offset]
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def pos
|
|
200
|
+
Token::Position.new(@offset, @line, @col)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def match(expected)
|
|
204
|
+
return false if peek != expected
|
|
205
|
+
advance
|
|
206
|
+
true
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def peek
|
|
210
|
+
return "\0" if at_end?
|
|
211
|
+
@source[@offset]
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def peek_next
|
|
215
|
+
if @offset.next < @source.length
|
|
216
|
+
@source[@offset + 1]
|
|
217
|
+
else
|
|
218
|
+
"\0"
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def advance
|
|
223
|
+
char = @source[@offset]
|
|
224
|
+
@offset += 1
|
|
225
|
+
@col += 1
|
|
226
|
+
char
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def at_end?
|
|
230
|
+
@offset >= @source.length
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def bool_token?(str)
|
|
234
|
+
str == 'True' || str == 'False'
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def none_token?(str)
|
|
238
|
+
str == 'None'
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def whitespace?(char)
|
|
242
|
+
case char
|
|
243
|
+
when ' ', "\t", "\r" then true
|
|
244
|
+
when "\n"
|
|
245
|
+
@line += 1
|
|
246
|
+
@col = 0
|
|
247
|
+
true
|
|
248
|
+
else false
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def alpha_numeric?(char)
|
|
253
|
+
alpha?(char) || digit?(char)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def alpha?(char)
|
|
257
|
+
('A' <= char && char <= 'Z') ||
|
|
258
|
+
('a' <= char && char <= 'z') ||
|
|
259
|
+
char == '_'
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def digit?(char)
|
|
263
|
+
'0' <= char && char <= '9'
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
class ErrorHandler
|
|
267
|
+
attr_reader :errors
|
|
268
|
+
|
|
269
|
+
def initialize
|
|
270
|
+
@errors = []
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def add(code, message = "", pos = nil)
|
|
274
|
+
@errors << LexicalError.new(code, message, pos)
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
end
|
data/lib/taoism/nodes.rb
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module Taoism
|
|
2
|
+
module Nodes
|
|
3
|
+
# ── Top Level ──
|
|
4
|
+
Program = Struct.new(:stmts, :start, :end)
|
|
5
|
+
|
|
6
|
+
# ── Statements ──
|
|
7
|
+
Let = Struct.new(:name, :value, :mutable, :start, :end)
|
|
8
|
+
LetPair = Struct.new(:first, :second, :expr, :start, :end)
|
|
9
|
+
Assign = Struct.new(:target, :value, :start, :end)
|
|
10
|
+
Const = Struct.new(:name, :value, :start, :end)
|
|
11
|
+
Import = Struct.new(:path, :start, :end)
|
|
12
|
+
Package = Struct.new(:name, :start, :end)
|
|
13
|
+
Return = Struct.new(:value, :start, :end)
|
|
14
|
+
Leave = Struct.new(:start, :end)
|
|
15
|
+
ExprStmt = Struct.new(:expr, :start, :end)
|
|
16
|
+
|
|
17
|
+
# ── Function ──
|
|
18
|
+
FunDef = Struct.new(:name, :params, :body, :receiver, :start, :end)
|
|
19
|
+
Receiver = Struct.new(:param_name, :type_name, :start, :end)
|
|
20
|
+
|
|
21
|
+
# ── Data ──
|
|
22
|
+
DataDef = Struct.new(:name, :fields, :start, :end)
|
|
23
|
+
Field = Struct.new(:name, :default, :start, :end)
|
|
24
|
+
|
|
25
|
+
# ── Block ──
|
|
26
|
+
Block = Struct.new(:stmts, :start, :end)
|
|
27
|
+
|
|
28
|
+
# ── Expressions ──
|
|
29
|
+
BinaryOp = Struct.new(:left, :op, :right, :start, :end)
|
|
30
|
+
UnaryOp = Struct.new(:op, :operand, :start, :end)
|
|
31
|
+
Call = Struct.new(:callee, :args, :start, :end)
|
|
32
|
+
Index = Struct.new(:target, :index, :start, :end)
|
|
33
|
+
DotExpr = Struct.new(:target, :name, :start, :end)
|
|
34
|
+
SafeDot = Struct.new(:target, :name, :start, :end)
|
|
35
|
+
Identifier = Struct.new(:name, :start, :end)
|
|
36
|
+
|
|
37
|
+
# ── Literals ──
|
|
38
|
+
IntLit = Struct.new(:value, :start, :end)
|
|
39
|
+
FloatLit = Struct.new(:value, :start, :end)
|
|
40
|
+
StringLit = Struct.new(:value, :start, :end)
|
|
41
|
+
BoolLit = Struct.new(:value, :start, :end)
|
|
42
|
+
NoneLit = Struct.new(:start, :end)
|
|
43
|
+
ListLit = Struct.new(:elements, :start, :end)
|
|
44
|
+
|
|
45
|
+
# ── Special Forms ──
|
|
46
|
+
Try = Struct.new(:call, :start, :end)
|
|
47
|
+
|
|
48
|
+
# ── Control Flow ──
|
|
49
|
+
If = Struct.new(:cond, :then_body, :else_body, :start, :end)
|
|
50
|
+
Loop = Struct.new(:body, :start, :end)
|
|
51
|
+
Switch = Struct.new(:value, :arms, :else_body, :start, :end)
|
|
52
|
+
SwitchArm = Struct.new(:pattern, :value, :start, :end)
|
|
53
|
+
end
|
|
54
|
+
end
|