mvsk 1.0.1 → 1.0.2
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/mvsk +42 -0
- data/lib/musk/lexer/helper.rb +27 -0
- data/lib/musk/lexer/keywords.rb +51 -0
- data/lib/musk/lexer.rb +309 -0
- data/lib/musk/nodes.rb +214 -0
- data/lib/musk/parser.rb +170 -0
- data/lib/musk/token.rb +48 -0
- data/lib/musk/types.rb +24 -0
- data/lib/musk/version.rb +3 -0
- data/lib/musk.rb +79 -0
- data/lib/mvsk.rb +1 -2
- metadata +29 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f04de2ea639c5201033077c0853085693663dcb0c8ffe2bcc4dde87ae0824fc2
|
|
4
|
+
data.tar.gz: 609ab7ff6ef10298d6d263a77b6594f4575e16c967116ace4bc2d4c328ab5fb5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 25dd4c8a0a1a9891f287ec03bfe849556994e5c6ba34e374cf2abd01d2e38f949d8fd766b7f190735bb132b15d4c79ff0b9755f9b4d997db35bd2ead960a9535
|
|
7
|
+
data.tar.gz: 8691243c770fb63eb12eb502a9dbd09b7448d6d7808b0ca5680efa7868f639916b358533fa89a5b1f102faac7c1cc591908ab38eb2565443219326c5cf2f7015
|
data/bin/mvsk
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
require 'musk'
|
|
3
|
+
|
|
4
|
+
args = ARGV.reject { |s| s.start_with?('-') }
|
|
5
|
+
opts = ARGV - args
|
|
6
|
+
|
|
7
|
+
if args.length == 1 and opts.empty?
|
|
8
|
+
Musk.run_file(args.first)
|
|
9
|
+
elsif ARGV.length.zero?
|
|
10
|
+
Musk.run_prompt
|
|
11
|
+
else
|
|
12
|
+
require 'pastel'
|
|
13
|
+
require 'optparse'
|
|
14
|
+
|
|
15
|
+
OptionParser.new do |op|
|
|
16
|
+
op.version = Musk::VERSION
|
|
17
|
+
op.banner = <<~USAGE
|
|
18
|
+
#{0}
|
|
19
|
+
|
|
20
|
+
Usage:
|
|
21
|
+
|
|
22
|
+
mvsk <file> [options]
|
|
23
|
+
|
|
24
|
+
Options:
|
|
25
|
+
USAGE
|
|
26
|
+
|
|
27
|
+
op.on '--tokens', 'print tokens' do
|
|
28
|
+
puts 'tokens'
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
op.on '--nodes', 'print nodes' do
|
|
32
|
+
puts 'nodes'
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
op.summary_width = 22
|
|
36
|
+
|
|
37
|
+
if args.length > 1
|
|
38
|
+
puts op
|
|
39
|
+
exit
|
|
40
|
+
end
|
|
41
|
+
end.parse!
|
|
42
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Musk
|
|
2
|
+
class Lexer
|
|
3
|
+
module Helper
|
|
4
|
+
def is_alpha_numeric(char)
|
|
5
|
+
is_alpha(char) || is_digit(char)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def is_alpha(char)
|
|
9
|
+
('A' <= char && char <= 'Z') ||
|
|
10
|
+
('a' <= char && char <= 'z') ||
|
|
11
|
+
('_' == char)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def is_digit(char)
|
|
15
|
+
'0' <= char && char <= '9'
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def is_bool(str)
|
|
19
|
+
str == 'true' || str == 'false'
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def is_nil(str)
|
|
23
|
+
str == 'nil'
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module Musk
|
|
2
|
+
class Lexer
|
|
3
|
+
module Keywords
|
|
4
|
+
RESERVED_WORDS = {
|
|
5
|
+
:if => Token::If,
|
|
6
|
+
:else => Token::Else,
|
|
7
|
+
:switch => Token::Switch,
|
|
8
|
+
:case => Token::Case,
|
|
9
|
+
:default => Token::Default,
|
|
10
|
+
:for => Token::For,
|
|
11
|
+
:in => Token::In,
|
|
12
|
+
:loop => Token::Loop,
|
|
13
|
+
:break => Token::Break,
|
|
14
|
+
:continue => Token::Continue,
|
|
15
|
+
:class => Token::Class,
|
|
16
|
+
:extends => Token::Extends,
|
|
17
|
+
:super => Token::Super,
|
|
18
|
+
:self => Token::Self,
|
|
19
|
+
:get => Token::Get,
|
|
20
|
+
:set => Token::Set,
|
|
21
|
+
:fun => Token::Fun,
|
|
22
|
+
:return => Token::Return,
|
|
23
|
+
:let => Token::Let,
|
|
24
|
+
:mut => Token::Mut,
|
|
25
|
+
:and => Token::And,
|
|
26
|
+
:or => Token::Or,
|
|
27
|
+
:is => Token::Is,
|
|
28
|
+
:not => Token::Not,
|
|
29
|
+
:import => Token::Import,
|
|
30
|
+
:as => Token::As,
|
|
31
|
+
:throw => Token::Throw,
|
|
32
|
+
:try => Token::Try
|
|
33
|
+
}
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# var tokens = [...]string{
|
|
39
|
+
# Int: "<int>",
|
|
40
|
+
# Float: "<float>",
|
|
41
|
+
# Identifier: "Identifier",
|
|
42
|
+
# String: "<string>",
|
|
43
|
+
# Rune: "<rune>",
|
|
44
|
+
# Bool: "<bool>",
|
|
45
|
+
# Nil: "nil",
|
|
46
|
+
# HashBang: "#!",
|
|
47
|
+
# Pipeline: "|>",
|
|
48
|
+
# Tilde: "~",
|
|
49
|
+
# Println: "println",
|
|
50
|
+
# Illegal: "Illegal",
|
|
51
|
+
# EOF: "EOF",
|
data/lib/musk/lexer.rb
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
require 'musk/lexer/keywords'
|
|
2
|
+
require 'musk/lexer/helper'
|
|
3
|
+
|
|
4
|
+
module Musk
|
|
5
|
+
class Lexer
|
|
6
|
+
include Helper
|
|
7
|
+
include Keywords
|
|
8
|
+
|
|
9
|
+
attr_reader :tokens
|
|
10
|
+
|
|
11
|
+
def initialize(code)
|
|
12
|
+
@tokens = []
|
|
13
|
+
@input = code
|
|
14
|
+
@line = 1
|
|
15
|
+
@start = 0
|
|
16
|
+
@current = 0
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def scan_tokens
|
|
20
|
+
until at_end?
|
|
21
|
+
@start = @current
|
|
22
|
+
scan_token
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
add_token(Token::EOF, '')
|
|
26
|
+
@tokens
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def next_token
|
|
30
|
+
until at_end?
|
|
31
|
+
@start = @current
|
|
32
|
+
break if scan_token
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
@token_i = 0 if @token_i.nil?
|
|
36
|
+
@token_i += 1
|
|
37
|
+
|
|
38
|
+
if @token_i <= @tokens.size
|
|
39
|
+
@tokens.last
|
|
40
|
+
else
|
|
41
|
+
nil
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def scan_token
|
|
48
|
+
char = advance
|
|
49
|
+
|
|
50
|
+
return false if skip_whitespace(char)
|
|
51
|
+
return false if skip_comment(char)
|
|
52
|
+
|
|
53
|
+
single_token(char) ||
|
|
54
|
+
double_token(char) ||
|
|
55
|
+
triple_token(char) ||
|
|
56
|
+
string_token(char) ||
|
|
57
|
+
number_token(char) ||
|
|
58
|
+
identifier_token(char)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def skip_whitespace(char)
|
|
62
|
+
if char == "\n"
|
|
63
|
+
@line += 1
|
|
64
|
+
return true
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
char == "\r" ||
|
|
68
|
+
char == "\t" ||
|
|
69
|
+
char == ' '
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def skip_comment(char)
|
|
73
|
+
if char == '/' && match?('/')
|
|
74
|
+
until peek == "\n" || at_end?
|
|
75
|
+
advance
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
true
|
|
79
|
+
else
|
|
80
|
+
false
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def identifier_token(char)
|
|
85
|
+
if is_alpha(char)
|
|
86
|
+
while is_alpha_numeric(peek)
|
|
87
|
+
advance
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
lexeme = @input[@start...@current]
|
|
91
|
+
|
|
92
|
+
if is_bool(lexeme)
|
|
93
|
+
add_token_bool(lexeme)
|
|
94
|
+
elsif is_nil(lexeme)
|
|
95
|
+
add_token(Token::Nil, 'nil')
|
|
96
|
+
else
|
|
97
|
+
if type = RESERVED_WORDS[lexeme.to_sym]
|
|
98
|
+
add_token(type, lexeme)
|
|
99
|
+
else
|
|
100
|
+
add_token(Token::Identifier, lexeme)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
true
|
|
105
|
+
else
|
|
106
|
+
add_token(Token::Illegal, nil, "unexpected '#{char}'")
|
|
107
|
+
false
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def string_token(char)
|
|
112
|
+
if char == '"'
|
|
113
|
+
until peek == '"' || at_end?
|
|
114
|
+
if peek == "\n"
|
|
115
|
+
@line += 1
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
advance
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
if at_end?
|
|
122
|
+
add_token(Token::Illegal, nil, 'unterminated string')
|
|
123
|
+
return false
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
advance
|
|
127
|
+
add_token_string(@input[@start...@current])
|
|
128
|
+
|
|
129
|
+
true
|
|
130
|
+
else
|
|
131
|
+
false
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def number_token(char)
|
|
136
|
+
if is_digit(char)
|
|
137
|
+
while is_digit(peek)
|
|
138
|
+
advance
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
if peek == '.' && is_digit(peek_next)
|
|
142
|
+
advance
|
|
143
|
+
|
|
144
|
+
while is_digit(peek)
|
|
145
|
+
advance
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
add_token_float(@input[@start...@current])
|
|
149
|
+
else
|
|
150
|
+
add_token_int(@input[@start...@current])
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
true
|
|
154
|
+
else
|
|
155
|
+
false
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def single_token(char)
|
|
160
|
+
case char
|
|
161
|
+
when '+' then add_token(Token::Plus)
|
|
162
|
+
when '-' then add_token(Token::Minus)
|
|
163
|
+
when '*' then add_token(Token::Star)
|
|
164
|
+
when '/' then add_token(Token::Slash)
|
|
165
|
+
when '%' then add_token(Token::Modulo)
|
|
166
|
+
when ',' then add_token(Token::Comma)
|
|
167
|
+
when ':' then add_token(Token::Colon)
|
|
168
|
+
when ';' then add_token(Token::Semi)
|
|
169
|
+
when '.' then add_token(Token::Dot)
|
|
170
|
+
when '{' then add_token(Token::LeftBrace)
|
|
171
|
+
when '}' then add_token(Token::RightBrace)
|
|
172
|
+
when '[' then add_token(Token::LeftSquare)
|
|
173
|
+
when ']' then add_token(Token::RightSquare)
|
|
174
|
+
when '(' then add_token(Token::LeftParen)
|
|
175
|
+
when ')' then add_token(Token::RightParen)
|
|
176
|
+
else
|
|
177
|
+
return false
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
true
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def double_token(char)
|
|
184
|
+
case char
|
|
185
|
+
when '!'
|
|
186
|
+
if match?('=')
|
|
187
|
+
add_token(Token::BangEq)
|
|
188
|
+
else
|
|
189
|
+
add_token(Token::Bang)
|
|
190
|
+
end
|
|
191
|
+
when '='
|
|
192
|
+
if match?('=')
|
|
193
|
+
add_token(Token::EqualEq)
|
|
194
|
+
else
|
|
195
|
+
add_token(Token::Equal)
|
|
196
|
+
end
|
|
197
|
+
when '>'
|
|
198
|
+
if match?('=')
|
|
199
|
+
add_token(Token::GreaterEq)
|
|
200
|
+
else
|
|
201
|
+
add_token(Token::Greater)
|
|
202
|
+
end
|
|
203
|
+
when '<'
|
|
204
|
+
if match?('=')
|
|
205
|
+
add_token(Token::LessEq)
|
|
206
|
+
else
|
|
207
|
+
add_token(Token::Less)
|
|
208
|
+
end
|
|
209
|
+
when '?'
|
|
210
|
+
if match?('.')
|
|
211
|
+
add_token(Token::QuestionDot)
|
|
212
|
+
else
|
|
213
|
+
add_token(Token::Question)
|
|
214
|
+
end
|
|
215
|
+
else
|
|
216
|
+
return false
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
true
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def triple_token(char)
|
|
223
|
+
case char
|
|
224
|
+
when '&'
|
|
225
|
+
if match?('&')
|
|
226
|
+
if match?('=')
|
|
227
|
+
add_token(Token::Illegal)
|
|
228
|
+
else
|
|
229
|
+
add_token(Token::And)
|
|
230
|
+
end
|
|
231
|
+
else
|
|
232
|
+
add_token(Token::BitwiseAnd)
|
|
233
|
+
end
|
|
234
|
+
when '|'
|
|
235
|
+
if match?('|')
|
|
236
|
+
if match?('=')
|
|
237
|
+
add_token(Token::Illegal)
|
|
238
|
+
else
|
|
239
|
+
add_token(Token::Or)
|
|
240
|
+
end
|
|
241
|
+
else
|
|
242
|
+
if match?('>')
|
|
243
|
+
add_token(Token::Pipeline)
|
|
244
|
+
else
|
|
245
|
+
add_token(Token::BitwiseOr)
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
else
|
|
249
|
+
return false
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
true
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def add_token_bool(lexeme)
|
|
256
|
+
value = lexeme.eql?('true')
|
|
257
|
+
add_token(Token::Bool, lexeme, value)
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def add_token_string(lexeme)
|
|
261
|
+
value = lexeme[1, lexeme.length - 2]
|
|
262
|
+
add_token(Token::String, lexeme, value)
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def add_token_float(lexeme)
|
|
266
|
+
add_token(Token::Float, lexeme, lexeme.to_f)
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def add_token_int(lexeme)
|
|
270
|
+
add_token(Token::Int, lexeme, lexeme.to_i)
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def add_token(type, lexeme = nil, literal = nil)
|
|
274
|
+
lexeme = @input[@start...@current] if lexeme.nil?
|
|
275
|
+
@tokens.push(Token.new(type, lexeme, literal, @line))
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def match?(char)
|
|
279
|
+
return false if at_end?
|
|
280
|
+
return false if @input[@current] != char
|
|
281
|
+
|
|
282
|
+
@current += 1
|
|
283
|
+
true
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def peek
|
|
287
|
+
return "\0" if at_end?
|
|
288
|
+
@input[@current]
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def peek_next
|
|
292
|
+
if @current + 1 < @input.length
|
|
293
|
+
@input[@current + 1]
|
|
294
|
+
else
|
|
295
|
+
"\0"
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def advance
|
|
300
|
+
char = @input[@current]
|
|
301
|
+
@current += 1
|
|
302
|
+
char
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def at_end?
|
|
306
|
+
@current >= @input.length
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
end
|
data/lib/musk/nodes.rb
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
module Musk
|
|
2
|
+
module Nodes
|
|
3
|
+
class Node
|
|
4
|
+
def accept(visitor)
|
|
5
|
+
raise NotImplementedError
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
class Program < Node; end
|
|
10
|
+
class Expression < Node; end
|
|
11
|
+
class Statement < Node; end
|
|
12
|
+
class Declaration < Statement; end
|
|
13
|
+
|
|
14
|
+
class BlockStatement < Statement
|
|
15
|
+
attr_reader :statements
|
|
16
|
+
|
|
17
|
+
def initialize(statements:)
|
|
18
|
+
@statements = statements
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def accept(visitor)
|
|
22
|
+
visitor.visit_block_stmt(self)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class IfStatement < Statement
|
|
27
|
+
# // Condition Expression
|
|
28
|
+
# // Then Statement
|
|
29
|
+
# // Else Statement
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
class ForStatement < Statement
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
class LoopStatement < Statement
|
|
36
|
+
# // Condition Expression
|
|
37
|
+
# // Body Statement
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class ContinueStatement < Statement
|
|
41
|
+
# Label Expression
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
class BreakStatement < Statement
|
|
45
|
+
# Label Expression
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
class ReturnStatement < Statement
|
|
49
|
+
attr_reader :keyword, :value
|
|
50
|
+
|
|
51
|
+
def initialize(keyword:, value:)
|
|
52
|
+
@keyword = keyword
|
|
53
|
+
@value = value
|
|
54
|
+
# Argument Expression
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def accept(visitor)
|
|
58
|
+
visitor.visit_return_stmt(self)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
class ExpressionStatement < Statement
|
|
63
|
+
attr_reader :expression
|
|
64
|
+
|
|
65
|
+
def initialize(expression:)
|
|
66
|
+
@expression = expression
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def accept(visitor)
|
|
70
|
+
visitor.visit_expression_stmt(self)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
class ClassDeclaration < Declaration
|
|
75
|
+
# // Name token.Token
|
|
76
|
+
# // Methods []ClassMethod
|
|
77
|
+
# // SuperClass *VariableExpression
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
class VariableDeclaration < Declaration
|
|
81
|
+
# // Name token.Token
|
|
82
|
+
# // Initializer Expression
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
class FunctionDeclaration < Declaration
|
|
86
|
+
attr_reader :name, :params, :body
|
|
87
|
+
|
|
88
|
+
def initialize(name:, params:, body:)
|
|
89
|
+
@name = name
|
|
90
|
+
@params = params
|
|
91
|
+
@body = body
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def accept(visitor)
|
|
95
|
+
visitor.visit_function_stmt(self)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
class Function < Expression
|
|
100
|
+
attr_reader :params, :body
|
|
101
|
+
|
|
102
|
+
def initialize(params:, body:)
|
|
103
|
+
@params = params
|
|
104
|
+
@body = body
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
class Assignment < Expression
|
|
109
|
+
# Operator token.Token
|
|
110
|
+
# Right Expression
|
|
111
|
+
# Left Expression
|
|
112
|
+
# //> expr-assign
|
|
113
|
+
# static class Assign extends Expr {
|
|
114
|
+
# Assign(Token name, Expr value) {
|
|
115
|
+
# this.name = name;
|
|
116
|
+
# this.value = value;
|
|
117
|
+
# <R> R accept(Visitor<R> visitor) {
|
|
118
|
+
# return visitor.visitAssignExpr(this);
|
|
119
|
+
# final Token name;
|
|
120
|
+
# final Expr value;
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# type Variable struct {
|
|
124
|
+
# Name string
|
|
125
|
+
# Expression
|
|
126
|
+
# }
|
|
127
|
+
|
|
128
|
+
# type Identifier struct {
|
|
129
|
+
# Name string
|
|
130
|
+
# Expression
|
|
131
|
+
# }
|
|
132
|
+
|
|
133
|
+
class Call < Expression
|
|
134
|
+
attr_reader :callee, :args
|
|
135
|
+
|
|
136
|
+
def initialize(callee:, args:)
|
|
137
|
+
@callee = callee
|
|
138
|
+
@args = args
|
|
139
|
+
# final Token paren;
|
|
140
|
+
# this.paren = paren;
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def accept(visitor)
|
|
144
|
+
visitor.visit_call_expr(self)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
class Logical < Expression
|
|
149
|
+
attr_reader :left, :operator, :right
|
|
150
|
+
|
|
151
|
+
def initialize(left:, operator:, right:)
|
|
152
|
+
@left = left
|
|
153
|
+
@operator = operator
|
|
154
|
+
@right = right
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def accept(visitor)
|
|
158
|
+
visitor.visit_logical_expr(self)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
class Grouping < Expression
|
|
163
|
+
attr_reader :expr
|
|
164
|
+
|
|
165
|
+
def initialize(expr:)
|
|
166
|
+
@expr = expr
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def accept(visitor)
|
|
170
|
+
visitor.visit_grouping_expr(self)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
class Binary < Expression
|
|
175
|
+
attr_reader :left, :operator, :right
|
|
176
|
+
|
|
177
|
+
def initialize(left:, operator:, right:)
|
|
178
|
+
@left = left
|
|
179
|
+
@operator = operator
|
|
180
|
+
@right = right
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def accept(visitor)
|
|
184
|
+
visitor.visit_binary_expr(self)
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
class Unary < Expression
|
|
189
|
+
attr_reader :operator, :right
|
|
190
|
+
|
|
191
|
+
def initialize(operator:, right:)
|
|
192
|
+
@operator = operator
|
|
193
|
+
@right = right
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def accept(visitor)
|
|
197
|
+
visitor.visit_unary_expr(self)
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
class Literal < Expression
|
|
202
|
+
attr_reader :type, :value
|
|
203
|
+
|
|
204
|
+
def initialize(type:, value:)
|
|
205
|
+
@type = type
|
|
206
|
+
@value = value
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def accept(visitor)
|
|
210
|
+
visitor.visit_literal_expr(self)
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
data/lib/musk/parser.rb
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
module Musk
|
|
2
|
+
class Parser
|
|
3
|
+
def initialize(tokens)
|
|
4
|
+
@tokens = tokens
|
|
5
|
+
@current = 0
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def parse
|
|
9
|
+
statements = []
|
|
10
|
+
statements.push(declaration) until at_end?
|
|
11
|
+
# statements.add(statement());
|
|
12
|
+
statements
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def declaration
|
|
18
|
+
begin
|
|
19
|
+
# if (match(CLASS)) return classDeclaration();
|
|
20
|
+
# if (match(FUN)) return function("function");
|
|
21
|
+
# if (match(VAR)) return varDeclaration();
|
|
22
|
+
statement
|
|
23
|
+
rescue ParseError
|
|
24
|
+
synchronize
|
|
25
|
+
nil
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def statement
|
|
30
|
+
return if_statement if match?(Token::If)
|
|
31
|
+
return for_statement if match?(Token::For)
|
|
32
|
+
return loop_statement if match?(Token::Loop)
|
|
33
|
+
return return_statement if match?(Token::Return)
|
|
34
|
+
|
|
35
|
+
# if (match(LEFT_BRACE)) return new Stmt.Block(block());
|
|
36
|
+
# return expressionStatement();
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# private Stmt expressionStatement() {
|
|
40
|
+
# Expr expr = expression();
|
|
41
|
+
# consume(SEMICOLON, "Expect ';' after expression.");
|
|
42
|
+
# return new Stmt.Expression(expr);
|
|
43
|
+
# }
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def synchronize
|
|
47
|
+
advance
|
|
48
|
+
|
|
49
|
+
until at_end?
|
|
50
|
+
return if previous.type == Token::Semi
|
|
51
|
+
return if peek.type == Token::Class
|
|
52
|
+
return if peek.type == Token::Fun
|
|
53
|
+
return if peek.type == Token::Return
|
|
54
|
+
return if peek.type == Token::Let
|
|
55
|
+
return if peek.type == Token::If
|
|
56
|
+
return if peek.type == Token::Switch
|
|
57
|
+
return if peek.type == Token::For
|
|
58
|
+
return if peek.type == Token::Loop
|
|
59
|
+
|
|
60
|
+
advance
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def expression
|
|
66
|
+
assignment
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def assignment
|
|
70
|
+
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# private Expr unary() {
|
|
74
|
+
# if (match(BANG, MINUS)) {
|
|
75
|
+
# Token operator = previous();
|
|
76
|
+
# Expr right = unary();
|
|
77
|
+
# return new Expr.Unary(operator, right);
|
|
78
|
+
# }
|
|
79
|
+
# return call();
|
|
80
|
+
# }
|
|
81
|
+
|
|
82
|
+
def primary
|
|
83
|
+
return literal(previous) if match?(Token::Nil, Token::Bool)
|
|
84
|
+
return literal(previous) if match?(Token::Int, Token::Float)
|
|
85
|
+
return literal(previous) if match?(Token::String)
|
|
86
|
+
|
|
87
|
+
if match?(Token::Super)
|
|
88
|
+
# Token keyword = previous();
|
|
89
|
+
consume(Token::Dot, "expect '.' after 'super'")
|
|
90
|
+
method = consume(Token::Identifier, 'expect superclass method name')
|
|
91
|
+
return Nodes::Super.new()
|
|
92
|
+
# .(keyword, method);
|
|
93
|
+
# .{Method: tok.Value}
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
if match?(Token::Self)
|
|
97
|
+
return Nodes::Self.new(previous)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
if match?(Token::Identifier)
|
|
101
|
+
# tok := p.previous()
|
|
102
|
+
# return ast.Variable{Name: tok.Value}
|
|
103
|
+
# return new Expr.Variable(previous());
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
if match?(Token::LeftParen)
|
|
107
|
+
exp = primary
|
|
108
|
+
# expr = expression();
|
|
109
|
+
consume(Token::RightParen, "expect ')' after expression")
|
|
110
|
+
return Nodes::Grouping.new(expr: exp)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# // panic(ParseError{token: p.peek(), message: "Expect expression"})
|
|
114
|
+
# panic("error")
|
|
115
|
+
# }
|
|
116
|
+
# throw error(peek(), "Expect expression.");
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def literal(token)
|
|
120
|
+
Nodes::Literal.new(type: token.type, value: token.literal)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def consume(type, message)
|
|
124
|
+
return advance if check?(type)
|
|
125
|
+
error peek, message
|
|
126
|
+
raise ParseError
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def error(tok, message)
|
|
130
|
+
if tok.type == Token::EOF
|
|
131
|
+
# report(token.line, " at end", message);
|
|
132
|
+
else
|
|
133
|
+
# report(token.line, " at '" + token.lexeme + "'", message);
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def match?(*types)
|
|
138
|
+
types.each do |type|
|
|
139
|
+
if check?(type)
|
|
140
|
+
advance
|
|
141
|
+
return true
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
false
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def check?(type)
|
|
149
|
+
return false if at_end?
|
|
150
|
+
peek.type == type
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def advance
|
|
154
|
+
@current += 1 unless at_end?
|
|
155
|
+
previous
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def previous
|
|
159
|
+
@tokens[@current - 1]
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def peek
|
|
163
|
+
@tokens[@current]
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def at_end?
|
|
167
|
+
peek.type == Token::EOF
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
data/lib/musk/token.rb
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module Musk
|
|
2
|
+
class Token
|
|
3
|
+
class << self
|
|
4
|
+
def token_type(*types)
|
|
5
|
+
types.each do |type|
|
|
6
|
+
const_set type, type.to_s
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
attr_reader :type, :lexeme, :literal, :line
|
|
12
|
+
|
|
13
|
+
token_type :Plus, :Minus, :Star, :Slash, :Modulo,
|
|
14
|
+
:Comma, :Colon, :Semi, :Dot,
|
|
15
|
+
:LeftBrace, :RightBrace,
|
|
16
|
+
:LeftSquare, :RightSquare,
|
|
17
|
+
:LeftParen, :RightParen
|
|
18
|
+
|
|
19
|
+
token_type :Bang, :BangEq, :Equal, :EqualEq,
|
|
20
|
+
:Greater, :GreaterEq, :Less, :LessEq,
|
|
21
|
+
:BitwiseAnd, :BitwiseOr, :And, :Or,
|
|
22
|
+
:Question, :QuestionDot,
|
|
23
|
+
:Pipeline
|
|
24
|
+
|
|
25
|
+
token_type :Int, :Float, :String, :Char, :Bool, :Nil,
|
|
26
|
+
:Identifier, :Illegal, :EOF
|
|
27
|
+
|
|
28
|
+
token_type :If, :Else, :Switch, :Case, :Default,
|
|
29
|
+
:For, :In, :Loop, :Break, :Continue,
|
|
30
|
+
:Class, :Extends, :Super, :Self,
|
|
31
|
+
:Get, :Set, :Fun, :Return,
|
|
32
|
+
:Let, :Mut, :Is, :Not,
|
|
33
|
+
:Import, :As,
|
|
34
|
+
:Throw, :Try
|
|
35
|
+
|
|
36
|
+
def initialize(type, lexeme, literal = nil, line)
|
|
37
|
+
@type = type
|
|
38
|
+
@lexeme = lexeme
|
|
39
|
+
@literal = literal
|
|
40
|
+
@line = line
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def to_s
|
|
44
|
+
return @type if @literal.nil?
|
|
45
|
+
"#{@type} #{@lexeme} #{@literal}"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
data/lib/musk/types.rb
ADDED
data/lib/musk/version.rb
ADDED
data/lib/musk.rb
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
require 'musk/version'
|
|
2
|
+
require 'musk/token'
|
|
3
|
+
require 'musk/lexer'
|
|
4
|
+
require 'musk/nodes'
|
|
5
|
+
require 'musk/parser'
|
|
6
|
+
|
|
7
|
+
module Musk
|
|
8
|
+
class ParseError < RuntimeError; end
|
|
9
|
+
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
def run_prompt
|
|
13
|
+
require 'pastel'
|
|
14
|
+
require 'readline'
|
|
15
|
+
|
|
16
|
+
stty_save = `stty -g`.chomp
|
|
17
|
+
|
|
18
|
+
begin
|
|
19
|
+
while buf = Readline.readline('> ', true)
|
|
20
|
+
break if buf.nil?
|
|
21
|
+
break if buf.strip == 'exit'
|
|
22
|
+
|
|
23
|
+
print run(buf), "\n"
|
|
24
|
+
# run(line); //> reset-had-error
|
|
25
|
+
# hadError = false;
|
|
26
|
+
end
|
|
27
|
+
rescue Interrupt
|
|
28
|
+
system 'stty', stty_save
|
|
29
|
+
ensure
|
|
30
|
+
puts
|
|
31
|
+
exit
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def run_file(path)
|
|
36
|
+
if File.exist?(path) && File.file?(path)
|
|
37
|
+
puts 'fiasd'
|
|
38
|
+
else
|
|
39
|
+
warn "No such file #{path}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# run(File.read(path))
|
|
43
|
+
# # if (hadError) System.exit(65);
|
|
44
|
+
# # if (hadRuntimeError) System.exit(70);
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def run(code)
|
|
48
|
+
lexer = Lexer.new(code)
|
|
49
|
+
tokens = lexer.scan_tokens
|
|
50
|
+
tokens.each { |token| puts token }
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# ["And",
|
|
55
|
+
# "Class",
|
|
56
|
+
# "Else",
|
|
57
|
+
# "False",
|
|
58
|
+
# "Fun",
|
|
59
|
+
# "For",
|
|
60
|
+
# "If",
|
|
61
|
+
# "Nil",
|
|
62
|
+
# "Or",
|
|
63
|
+
# "Print",
|
|
64
|
+
# "Return",
|
|
65
|
+
# "Super",
|
|
66
|
+
# "This",
|
|
67
|
+
# "True",
|
|
68
|
+
# "Var",
|
|
69
|
+
# "While"]
|
|
70
|
+
# Int
|
|
71
|
+
# Float
|
|
72
|
+
# Bool
|
|
73
|
+
# String
|
|
74
|
+
# Char
|
|
75
|
+
# Unit
|
|
76
|
+
# Tuple
|
|
77
|
+
# List
|
|
78
|
+
# Array
|
|
79
|
+
# Function
|
data/lib/mvsk.rb
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
end
|
|
1
|
+
# https://elm-lang.org/docs/syntax
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mvsk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- razeos at tossdev
|
|
@@ -9,13 +9,38 @@ autorequire:
|
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
11
|
date: 2023-07-18 00:00:00.000000000 Z
|
|
12
|
-
dependencies:
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: pastel
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: 0.8.0
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: 0.8.0
|
|
13
27
|
description:
|
|
14
28
|
email:
|
|
15
|
-
executables:
|
|
29
|
+
executables:
|
|
30
|
+
- mvsk
|
|
16
31
|
extensions: []
|
|
17
32
|
extra_rdoc_files: []
|
|
18
33
|
files:
|
|
34
|
+
- bin/mvsk
|
|
35
|
+
- lib/musk.rb
|
|
36
|
+
- lib/musk/lexer.rb
|
|
37
|
+
- lib/musk/lexer/helper.rb
|
|
38
|
+
- lib/musk/lexer/keywords.rb
|
|
39
|
+
- lib/musk/nodes.rb
|
|
40
|
+
- lib/musk/parser.rb
|
|
41
|
+
- lib/musk/token.rb
|
|
42
|
+
- lib/musk/types.rb
|
|
43
|
+
- lib/musk/version.rb
|
|
19
44
|
- lib/mvsk.rb
|
|
20
45
|
homepage:
|
|
21
46
|
licenses:
|
|
@@ -39,5 +64,5 @@ requirements: []
|
|
|
39
64
|
rubygems_version: 3.1.2
|
|
40
65
|
signing_key:
|
|
41
66
|
specification_version: 4
|
|
42
|
-
summary:
|
|
67
|
+
summary: A simple programming language
|
|
43
68
|
test_files: []
|