natalie_parser 1.2.0 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +23 -0
- data/include/natalie_parser/node/colon2_node.hpp +1 -0
- data/include/natalie_parser/node/for_node.hpp +50 -0
- data/include/natalie_parser/node/hash_node.hpp +8 -3
- data/include/natalie_parser/node/hash_pattern_node.hpp +1 -1
- data/include/natalie_parser/node/match_node.hpp +6 -0
- data/include/natalie_parser/node/node.hpp +1 -0
- data/include/natalie_parser/node/unary_op_node.hpp +1 -1
- data/include/natalie_parser/node.hpp +1 -0
- data/include/natalie_parser/parser.hpp +3 -1
- data/include/natalie_parser/token.hpp +18 -0
- data/lib/natalie_parser/version.rb +1 -1
- data/src/lexer.cpp +5 -1
- data/src/node/match_node.cpp +5 -0
- data/src/parser.cpp +86 -35
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '0598d42a8e0b83aa5a340d1615474a4c5a8e7b209032bf574a230778e4d3ce5e'
|
4
|
+
data.tar.gz: 475cce9e2f078ab440674b48e3b99ba48f163f34b889b376cf681f74a489ce53
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 59cd20f85484845036f934ca5b40c020ad35655824950c036a697bf0bc437d3ebd35eb25bacc7c6f062d096c01b58dea5a1ebd688d0d101192ee7f43b9881b25
|
7
|
+
data.tar.gz: 53bcc9b34ab02943dda1602141ae949e4fa1dcf1c229dfeda8ef710b01d1d9d9c97137ce7e5b08e88b121025009c9a9fd71edbc77eead37f69f37b794fab02f8
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,28 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 2.1.0 (2022-08-12)
|
4
|
+
|
5
|
+
- FEAT: Parse for loops
|
6
|
+
- FIX: Fix bug parsing defined? with parens
|
7
|
+
- FIX: Fix parsing of keyword splat next to other keyword args
|
8
|
+
- FIX: Parse block pass after bare/implicit hash
|
9
|
+
- FIX: Parse if statements with match conditions
|
10
|
+
- FIX: Parse regexps with leading space preceeded by keywords
|
11
|
+
- FIX: Parse symbol key after super keyword
|
12
|
+
- FIX: Parse unless statements with match conditions
|
13
|
+
- FIX: Parse while/until statements with match conditions
|
14
|
+
- FIX: Reset block association level inside array
|
15
|
+
|
16
|
+
## 2.0.0 (2022-06-24)
|
17
|
+
|
18
|
+
- FEAT: Differentiate between bare/implicit hash and explicit one
|
19
|
+
- FIX: Fix calling colon2
|
20
|
+
- FIX: Parse implicit method calls with nth ref argument
|
21
|
+
|
22
|
+
## 1.2.1 (2022-06-16)
|
23
|
+
|
24
|
+
- FIX: Fix regression with unary/infix operators (+/-)
|
25
|
+
|
3
26
|
## 1.2.0 (2022-06-16)
|
4
27
|
|
5
28
|
- CHORE: Enable true random fuzzing
|
@@ -23,6 +23,7 @@ public:
|
|
23
23
|
virtual Type type() const override { return Type::Colon2; }
|
24
24
|
|
25
25
|
virtual bool is_assignable() const override { return true; }
|
26
|
+
virtual bool is_callable() const override { return true; }
|
26
27
|
|
27
28
|
const SharedPtr<Node> left() const { return m_left; }
|
28
29
|
SharedPtr<String> name() const { return m_name; }
|
@@ -0,0 +1,50 @@
|
|
1
|
+
#pragma once
|
2
|
+
|
3
|
+
#include "natalie_parser/node/block_node.hpp"
|
4
|
+
#include "natalie_parser/node/node.hpp"
|
5
|
+
#include "natalie_parser/node/node_with_args.hpp"
|
6
|
+
|
7
|
+
namespace NatalieParser {
|
8
|
+
|
9
|
+
using namespace TM;
|
10
|
+
|
11
|
+
class ForNode : public Node {
|
12
|
+
public:
|
13
|
+
ForNode(const Token &token, SharedPtr<Node> expr, SharedPtr<Node> vars, SharedPtr<BlockNode> body)
|
14
|
+
: Node { token }
|
15
|
+
, m_expr { expr }
|
16
|
+
, m_vars { vars }
|
17
|
+
, m_body { body } {
|
18
|
+
assert(m_expr);
|
19
|
+
assert(m_vars);
|
20
|
+
}
|
21
|
+
|
22
|
+
virtual Type type() const override { return Type::For; }
|
23
|
+
|
24
|
+
const SharedPtr<Node> expr() const { return m_expr; }
|
25
|
+
const SharedPtr<Node> vars() const { return m_vars; }
|
26
|
+
const SharedPtr<BlockNode> body() const { return m_body; }
|
27
|
+
|
28
|
+
virtual void transform(Creator *creator) const override {
|
29
|
+
creator->set_type("for");
|
30
|
+
creator->append(m_expr);
|
31
|
+
switch (m_vars->type()) {
|
32
|
+
case Node::Type::Identifier:
|
33
|
+
creator->with_assignment(true, [&]() { creator->append(*m_vars); });
|
34
|
+
break;
|
35
|
+
case Node::Type::MultipleAssignment:
|
36
|
+
creator->append(m_vars);
|
37
|
+
break;
|
38
|
+
default:
|
39
|
+
TM_UNREACHABLE();
|
40
|
+
}
|
41
|
+
if (!m_body->is_empty())
|
42
|
+
creator->append(m_body->without_unnecessary_nesting());
|
43
|
+
}
|
44
|
+
|
45
|
+
protected:
|
46
|
+
SharedPtr<Node> m_expr {};
|
47
|
+
SharedPtr<Node> m_vars {};
|
48
|
+
SharedPtr<BlockNode> m_body {};
|
49
|
+
};
|
50
|
+
}
|
@@ -11,8 +11,9 @@ using namespace TM;
|
|
11
11
|
|
12
12
|
class HashNode : public Node {
|
13
13
|
public:
|
14
|
-
HashNode(const Token &token)
|
15
|
-
: Node { token }
|
14
|
+
HashNode(const Token &token, bool bare)
|
15
|
+
: Node { token }
|
16
|
+
, m_bare { bare } { }
|
16
17
|
|
17
18
|
virtual Type type() const override { return Type::Hash; }
|
18
19
|
|
@@ -23,12 +24,16 @@ public:
|
|
23
24
|
const Vector<SharedPtr<Node>> &nodes() const { return m_nodes; }
|
24
25
|
|
25
26
|
virtual void transform(Creator *creator) const override {
|
26
|
-
|
27
|
+
if (m_bare)
|
28
|
+
creator->set_type("bare_hash");
|
29
|
+
else
|
30
|
+
creator->set_type("hash");
|
27
31
|
for (auto node : m_nodes)
|
28
32
|
creator->append(node);
|
29
33
|
}
|
30
34
|
|
31
35
|
protected:
|
32
36
|
Vector<SharedPtr<Node>> m_nodes {};
|
37
|
+
bool m_bare { false };
|
33
38
|
};
|
34
39
|
}
|
@@ -13,6 +13,12 @@ using namespace TM;
|
|
13
13
|
|
14
14
|
class MatchNode : public Node {
|
15
15
|
public:
|
16
|
+
MatchNode(const Token &token, SharedPtr<RegexpNode> regexp)
|
17
|
+
: Node { token }
|
18
|
+
, m_regexp { regexp } {
|
19
|
+
assert(m_regexp);
|
20
|
+
}
|
21
|
+
|
16
22
|
MatchNode(const Token &token, SharedPtr<RegexpNode> regexp, SharedPtr<Node> arg, bool regexp_on_left)
|
17
23
|
: Node { token }
|
18
24
|
, m_regexp { regexp }
|
@@ -20,7 +20,7 @@ public:
|
|
20
20
|
|
21
21
|
virtual Type type() const override { return Type::UnaryOp; }
|
22
22
|
|
23
|
-
virtual bool is_callable() const override { return
|
23
|
+
virtual bool is_callable() const override { return false; }
|
24
24
|
virtual bool can_accept_a_block() const override { return false; }
|
25
25
|
|
26
26
|
const SharedPtr<String> op() const { return m_op; }
|
@@ -31,6 +31,7 @@
|
|
31
31
|
#include "natalie_parser/node/false_node.hpp"
|
32
32
|
#include "natalie_parser/node/fixnum_node.hpp"
|
33
33
|
#include "natalie_parser/node/float_node.hpp"
|
34
|
+
#include "natalie_parser/node/for_node.hpp"
|
34
35
|
#include "natalie_parser/node/forward_args_node.hpp"
|
35
36
|
#include "natalie_parser/node/hash_node.hpp"
|
36
37
|
#include "natalie_parser/node/hash_pattern_node.hpp"
|
@@ -101,12 +101,14 @@ private:
|
|
101
101
|
SharedPtr<Node> parse_encoding(LocalsHashmap &);
|
102
102
|
SharedPtr<Node> parse_end_block(LocalsHashmap &);
|
103
103
|
SharedPtr<Node> parse_file_constant(LocalsHashmap &);
|
104
|
+
SharedPtr<Node> parse_for(LocalsHashmap &);
|
104
105
|
SharedPtr<Node> parse_forward_args(LocalsHashmap &);
|
105
106
|
SharedPtr<Node> parse_group(LocalsHashmap &);
|
106
107
|
SharedPtr<Node> parse_hash(LocalsHashmap &);
|
107
|
-
SharedPtr<Node> parse_hash_inner(LocalsHashmap &, Precedence, Token::Type, SharedPtr<Node> = {});
|
108
|
+
SharedPtr<Node> parse_hash_inner(LocalsHashmap &, Precedence, Token::Type, bool, SharedPtr<Node> = {});
|
108
109
|
SharedPtr<Node> parse_identifier(LocalsHashmap &);
|
109
110
|
SharedPtr<Node> parse_if(LocalsHashmap &);
|
111
|
+
SharedPtr<Node> parse_if_branch(LocalsHashmap &, bool);
|
110
112
|
void parse_interpolated_body(LocalsHashmap &, InterpolatedNode &, Token::Type);
|
111
113
|
SharedPtr<Node> parse_interpolated_regexp(LocalsHashmap &);
|
112
114
|
int parse_regexp_options(String &);
|
@@ -775,6 +775,7 @@ public:
|
|
775
775
|
case Token::Type::NilKeyword:
|
776
776
|
case Token::Type::Not:
|
777
777
|
case Token::Type::NotKeyword:
|
778
|
+
case Token::Type::NthRef:
|
778
779
|
case Token::Type::PercentLowerI:
|
779
780
|
case Token::Type::PercentLowerW:
|
780
781
|
case Token::Type::PercentUpperI:
|
@@ -807,6 +808,22 @@ public:
|
|
807
808
|
}
|
808
809
|
}
|
809
810
|
|
811
|
+
bool can_precede_regexp_literal() const {
|
812
|
+
switch (m_type) {
|
813
|
+
case Type::ElsifKeyword:
|
814
|
+
case Type::IfKeyword:
|
815
|
+
case Type::RescueKeyword:
|
816
|
+
case Type::ReturnKeyword:
|
817
|
+
case Type::UnlessKeyword:
|
818
|
+
case Type::UntilKeyword:
|
819
|
+
case Type::WhenKeyword:
|
820
|
+
case Type::WhileKeyword:
|
821
|
+
return true;
|
822
|
+
default:
|
823
|
+
return false;
|
824
|
+
}
|
825
|
+
}
|
826
|
+
|
810
827
|
bool can_precede_symbol_key() const {
|
811
828
|
switch (m_type) {
|
812
829
|
case Type::BareName:
|
@@ -817,6 +834,7 @@ public:
|
|
817
834
|
case Type::LParen:
|
818
835
|
case Type::Pipe:
|
819
836
|
case Type::PipePipe:
|
837
|
+
case Type::SuperKeyword:
|
820
838
|
return true;
|
821
839
|
default:
|
822
840
|
return false;
|
data/src/lexer.cpp
CHANGED
@@ -270,7 +270,11 @@ Token Lexer::build_next_token() {
|
|
270
270
|
default: {
|
271
271
|
switch (current_char()) {
|
272
272
|
case ' ':
|
273
|
-
|
273
|
+
if (m_last_token.is_keyword() && m_last_token.can_precede_regexp_literal()) {
|
274
|
+
return consume_regexp('/', '/');
|
275
|
+
} else {
|
276
|
+
return Token { Token::Type::Slash, m_file, m_token_line, m_token_column, m_whitespace_precedes };
|
277
|
+
}
|
274
278
|
case '=':
|
275
279
|
advance();
|
276
280
|
return Token { Token::Type::SlashEqual, m_file, m_token_line, m_token_column, m_whitespace_precedes };
|
data/src/node/match_node.cpp
CHANGED
data/src/parser.cpp
CHANGED
@@ -337,7 +337,7 @@ SharedPtr<Node> Parser::parse_array(LocalsHashmap &locals) {
|
|
337
337
|
return array.static_cast_as<Node>();
|
338
338
|
}
|
339
339
|
if (token.type() == Token::Type::SymbolKey) {
|
340
|
-
array->add_node(parse_hash_inner(locals, Precedence::HASH, Token::Type::RBracket));
|
340
|
+
array->add_node(parse_hash_inner(locals, Precedence::HASH, Token::Type::RBracket, true));
|
341
341
|
expect(Token::Type::RBracket, "array closing bracket");
|
342
342
|
advance();
|
343
343
|
return array.static_cast_as<Node>();
|
@@ -345,7 +345,7 @@ SharedPtr<Node> Parser::parse_array(LocalsHashmap &locals) {
|
|
345
345
|
auto value = parse_expression(Precedence::ARRAY, locals);
|
346
346
|
token = current_token();
|
347
347
|
if (token.is_hash_rocket()) {
|
348
|
-
array->add_node(parse_hash_inner(locals, Precedence::HASH, Token::Type::RBracket, value));
|
348
|
+
array->add_node(parse_hash_inner(locals, Precedence::HASH, Token::Type::RBracket, true, value));
|
349
349
|
expect(Token::Type::RBracket, "array closing bracket");
|
350
350
|
advance();
|
351
351
|
return array.static_cast_as<Node>();
|
@@ -354,11 +354,17 @@ SharedPtr<Node> Parser::parse_array(LocalsHashmap &locals) {
|
|
354
354
|
return {};
|
355
355
|
};
|
356
356
|
auto ret = add_node();
|
357
|
-
if (ret)
|
357
|
+
if (ret) {
|
358
|
+
m_call_depth.pop();
|
359
|
+
return ret;
|
360
|
+
}
|
358
361
|
while (current_token().is_comma()) {
|
359
362
|
advance();
|
360
363
|
ret = add_node();
|
361
|
-
if (ret)
|
364
|
+
if (ret) {
|
365
|
+
m_call_depth.pop();
|
366
|
+
return ret;
|
367
|
+
}
|
362
368
|
}
|
363
369
|
expect(Token::Type::RBracket, "array closing bracket");
|
364
370
|
advance(); // ]
|
@@ -1058,7 +1064,16 @@ SharedPtr<Node> Parser::parse_def(LocalsHashmap &locals) {
|
|
1058
1064
|
SharedPtr<Node> Parser::parse_defined(LocalsHashmap &locals) {
|
1059
1065
|
auto token = current_token();
|
1060
1066
|
advance();
|
1061
|
-
|
1067
|
+
bool bare = true;
|
1068
|
+
if (current_token().is_lparen()) {
|
1069
|
+
advance();
|
1070
|
+
bare = false;
|
1071
|
+
}
|
1072
|
+
auto arg = parse_expression(bare ? Precedence::BARE_CALL_ARG : Precedence::CALL_ARG, locals);
|
1073
|
+
if (!bare) {
|
1074
|
+
expect(Token::Type::RParen, "defined? closing paren");
|
1075
|
+
advance();
|
1076
|
+
}
|
1062
1077
|
return new DefinedNode { token, arg };
|
1063
1078
|
}
|
1064
1079
|
|
@@ -1248,6 +1263,22 @@ SharedPtr<Node> Parser::parse_file_constant(LocalsHashmap &) {
|
|
1248
1263
|
return new StringNode { token, token.file() };
|
1249
1264
|
}
|
1250
1265
|
|
1266
|
+
SharedPtr<Node> Parser::parse_for(LocalsHashmap &locals) {
|
1267
|
+
auto token = current_token();
|
1268
|
+
advance();
|
1269
|
+
auto vars = parse_assignment_identifier(false, locals);
|
1270
|
+
if (current_token().type() == Token::Type::Comma) {
|
1271
|
+
vars = parse_multiple_assignment_expression(vars, locals);
|
1272
|
+
}
|
1273
|
+
expect(Token::Type::InKeyword, "for in");
|
1274
|
+
advance();
|
1275
|
+
auto expr = parse_expression(Precedence::LOWEST, locals);
|
1276
|
+
auto body = parse_body(locals, Precedence::LOWEST);
|
1277
|
+
expect(Token::Type::EndKeyword, "for end");
|
1278
|
+
advance();
|
1279
|
+
return new ForNode { token, expr, vars, body };
|
1280
|
+
}
|
1281
|
+
|
1251
1282
|
SharedPtr<Node> Parser::parse_forward_args(LocalsHashmap &locals) {
|
1252
1283
|
auto token = current_token();
|
1253
1284
|
if (!locals.get("..."))
|
@@ -1299,39 +1330,46 @@ SharedPtr<Node> Parser::parse_hash(LocalsHashmap &locals) {
|
|
1299
1330
|
advance();
|
1300
1331
|
SharedPtr<Node> hash;
|
1301
1332
|
if (current_token().type() == Token::Type::RCurlyBrace)
|
1302
|
-
hash = new HashNode { token };
|
1333
|
+
hash = new HashNode { token, false };
|
1303
1334
|
else
|
1304
|
-
hash = parse_hash_inner(locals, Precedence::HASH, Token::Type::RCurlyBrace);
|
1335
|
+
hash = parse_hash_inner(locals, Precedence::HASH, Token::Type::RCurlyBrace, false);
|
1305
1336
|
expect(Token::Type::RCurlyBrace, "hash closing curly brace");
|
1306
1337
|
advance();
|
1307
1338
|
return hash;
|
1308
1339
|
}
|
1309
1340
|
|
1310
|
-
SharedPtr<Node> Parser::parse_hash_inner(LocalsHashmap &locals, Precedence precedence, Token::Type closing_token_type, SharedPtr<Node> first_key) {
|
1341
|
+
SharedPtr<Node> Parser::parse_hash_inner(LocalsHashmap &locals, Precedence precedence, Token::Type closing_token_type, bool bare, SharedPtr<Node> first_key) {
|
1311
1342
|
auto token = current_token();
|
1312
|
-
SharedPtr<HashNode> hash = new HashNode { token };
|
1343
|
+
SharedPtr<HashNode> hash = new HashNode { token, bare };
|
1344
|
+
|
1345
|
+
auto add_value = [&](SharedPtr<Node> key) {
|
1346
|
+
if (key->is_symbol_key()) {
|
1347
|
+
hash->add_node(parse_expression(precedence, locals));
|
1348
|
+
} else if (key->type() == Node::Type::KeywordSplat) {
|
1349
|
+
// kwsplat already added
|
1350
|
+
} else {
|
1351
|
+
expect(Token::Type::HashRocket, "hash rocket");
|
1352
|
+
advance(); // =>
|
1353
|
+
hash->add_node(parse_expression(precedence, locals));
|
1354
|
+
}
|
1355
|
+
};
|
1356
|
+
|
1313
1357
|
if (!first_key)
|
1314
1358
|
first_key = parse_expression(precedence, locals);
|
1315
1359
|
hash->add_node(first_key);
|
1316
|
-
|
1317
|
-
|
1318
|
-
advance();
|
1319
|
-
}
|
1320
|
-
hash->add_node(parse_expression(precedence, locals));
|
1360
|
+
add_value(first_key);
|
1361
|
+
|
1321
1362
|
while (current_token().is_comma()) {
|
1322
1363
|
advance();
|
1323
1364
|
if (current_token().type() == closing_token_type)
|
1324
1365
|
break;
|
1325
|
-
if (current_token().type() == Token::Type::
|
1366
|
+
if (current_token().type() == Token::Type::Ampersand) // &block
|
1326
1367
|
break;
|
1327
1368
|
auto key = parse_expression(precedence, locals);
|
1328
1369
|
hash->add_node(key);
|
1329
|
-
|
1330
|
-
expect(Token::Type::HashRocket, "hash rocket");
|
1331
|
-
advance();
|
1332
|
-
}
|
1333
|
-
hash->add_node(parse_expression(precedence, locals));
|
1370
|
+
add_value(key);
|
1334
1371
|
}
|
1372
|
+
|
1335
1373
|
return hash.static_cast_as<Node>();
|
1336
1374
|
}
|
1337
1375
|
|
@@ -1345,9 +1383,16 @@ SharedPtr<Node> Parser::parse_identifier(LocalsHashmap &locals) {
|
|
1345
1383
|
};
|
1346
1384
|
|
1347
1385
|
SharedPtr<Node> Parser::parse_if(LocalsHashmap &locals) {
|
1386
|
+
return parse_if_branch(locals, true);
|
1387
|
+
}
|
1388
|
+
|
1389
|
+
SharedPtr<Node> Parser::parse_if_branch(LocalsHashmap &locals, bool parse_match_condition) {
|
1348
1390
|
auto token = current_token();
|
1349
1391
|
advance();
|
1350
1392
|
SharedPtr<Node> condition = parse_expression(Precedence::LOWEST, locals);
|
1393
|
+
if (parse_match_condition && condition->type() == Node::Type::Regexp) {
|
1394
|
+
condition = new MatchNode { condition->token(), condition.static_cast_as<RegexpNode>() };
|
1395
|
+
}
|
1351
1396
|
if (current_token().type() == Token::Type::ThenKeyword) {
|
1352
1397
|
advance(); // then
|
1353
1398
|
} else {
|
@@ -1356,7 +1401,7 @@ SharedPtr<Node> Parser::parse_if(LocalsHashmap &locals) {
|
|
1356
1401
|
SharedPtr<Node> true_expr = parse_if_body(locals);
|
1357
1402
|
SharedPtr<Node> false_expr;
|
1358
1403
|
if (current_token().is_elsif_keyword()) {
|
1359
|
-
false_expr =
|
1404
|
+
false_expr = parse_if_branch(locals, false);
|
1360
1405
|
return new IfNode { current_token(), condition, true_expr, false_expr };
|
1361
1406
|
} else {
|
1362
1407
|
if (current_token().is_else_keyword()) {
|
@@ -1639,12 +1684,6 @@ SharedPtr<Node> Parser::parse_keyword_splat(LocalsHashmap &locals) {
|
|
1639
1684
|
return new KeywordSplatNode { token, parse_expression(Precedence::SPLAT, locals) };
|
1640
1685
|
}
|
1641
1686
|
|
1642
|
-
SharedPtr<Node> Parser::parse_keyword_splat_wrapped_in_hash(LocalsHashmap &locals) {
|
1643
|
-
SharedPtr<HashNode> hash = new HashNode { current_token() };
|
1644
|
-
hash->add_node(parse_keyword_splat(locals));
|
1645
|
-
return hash.static_cast_as<Node>();
|
1646
|
-
}
|
1647
|
-
|
1648
1687
|
SharedPtr<String> Parser::parse_method_name(LocalsHashmap &) {
|
1649
1688
|
SharedPtr<String> name = new String("");
|
1650
1689
|
auto token = current_token();
|
@@ -2313,7 +2352,7 @@ void Parser::parse_call_args(NodeWithArgs &node, LocalsHashmap &locals, bool bar
|
|
2313
2352
|
if (bare && node.can_accept_a_block())
|
2314
2353
|
m_call_depth.last()++;
|
2315
2354
|
auto arg = parse_expression(bare ? Precedence::BARE_CALL_ARG : Precedence::CALL_ARG, locals);
|
2316
|
-
if (current_token().is_hash_rocket() || arg->is_symbol_key()) {
|
2355
|
+
if (current_token().is_hash_rocket() || arg->is_symbol_key() || arg->type() == Node::Type::KeywordSplat) {
|
2317
2356
|
node.add_arg(parse_call_hash_args(locals, bare, closing_token_type, arg));
|
2318
2357
|
} else {
|
2319
2358
|
node.add_arg(arg);
|
@@ -2325,7 +2364,7 @@ void Parser::parse_call_args(NodeWithArgs &node, LocalsHashmap &locals, bool bar
|
|
2325
2364
|
break;
|
2326
2365
|
}
|
2327
2366
|
arg = parse_expression(bare ? Precedence::BARE_CALL_ARG : Precedence::CALL_ARG, locals);
|
2328
|
-
if (current_token().is_hash_rocket() || arg->is_symbol_key()) {
|
2367
|
+
if (current_token().is_hash_rocket() || arg->is_symbol_key() || arg->type() == Node::Type::KeywordSplat) {
|
2329
2368
|
node.add_arg(parse_call_hash_args(locals, bare, closing_token_type, arg));
|
2330
2369
|
break;
|
2331
2370
|
} else {
|
@@ -2333,16 +2372,20 @@ void Parser::parse_call_args(NodeWithArgs &node, LocalsHashmap &locals, bool bar
|
|
2333
2372
|
}
|
2334
2373
|
}
|
2335
2374
|
}
|
2375
|
+
if (current_token().type() == Token::Type::Ampersand) {
|
2376
|
+
node.add_arg(parse_block_pass(locals));
|
2377
|
+
}
|
2336
2378
|
if (bare && node.can_accept_a_block())
|
2337
2379
|
m_call_depth.last()--;
|
2338
2380
|
}
|
2339
2381
|
|
2340
|
-
SharedPtr<Node> Parser::parse_call_hash_args(LocalsHashmap &locals, bool
|
2382
|
+
SharedPtr<Node> Parser::parse_call_hash_args(LocalsHashmap &locals, bool bare_call, Token::Type closing_token_type, SharedPtr<Node> first_arg) {
|
2383
|
+
bool bare_hash = true; // we got here via foo(1, a: 'b') so it's always a "bare" hash
|
2341
2384
|
SharedPtr<Node> hash;
|
2342
|
-
if (
|
2343
|
-
hash = parse_hash_inner(locals, Precedence::BARE_CALL_ARG, closing_token_type, first_arg);
|
2385
|
+
if (bare_call)
|
2386
|
+
hash = parse_hash_inner(locals, Precedence::BARE_CALL_ARG, closing_token_type, bare_hash, first_arg);
|
2344
2387
|
else
|
2345
|
-
hash = parse_hash_inner(locals, Precedence::CALL_ARG, closing_token_type, first_arg);
|
2388
|
+
hash = parse_hash_inner(locals, Precedence::CALL_ARG, closing_token_type, bare_hash, first_arg);
|
2346
2389
|
if (current_token().type() == Token::Type::StarStar)
|
2347
2390
|
hash.static_cast_as<HashNode>()->add_node(parse_keyword_splat(locals));
|
2348
2391
|
return hash;
|
@@ -2699,6 +2742,9 @@ SharedPtr<Node> Parser::parse_unless(LocalsHashmap &locals) {
|
|
2699
2742
|
auto token = current_token();
|
2700
2743
|
advance();
|
2701
2744
|
SharedPtr<Node> condition = parse_expression(Precedence::LOWEST, locals);
|
2745
|
+
if (condition->type() == Node::Type::Regexp) {
|
2746
|
+
condition = new MatchNode { condition->token(), condition.static_cast_as<RegexpNode>() };
|
2747
|
+
}
|
2702
2748
|
next_expression();
|
2703
2749
|
SharedPtr<Node> false_expr = parse_if_body(locals);
|
2704
2750
|
SharedPtr<Node> true_expr;
|
@@ -2717,6 +2763,9 @@ SharedPtr<Node> Parser::parse_while(LocalsHashmap &locals) {
|
|
2717
2763
|
auto token = current_token();
|
2718
2764
|
advance();
|
2719
2765
|
SharedPtr<Node> condition = parse_expression(Precedence::LOWEST, locals);
|
2766
|
+
if (condition->type() == Node::Type::Regexp) {
|
2767
|
+
condition = new MatchNode { condition->token(), condition.static_cast_as<RegexpNode>() };
|
2768
|
+
}
|
2720
2769
|
next_expression();
|
2721
2770
|
SharedPtr<BlockNode> body = parse_body(locals, Precedence::LOWEST);
|
2722
2771
|
expect(Token::Type::EndKeyword, "while end");
|
@@ -2769,6 +2818,8 @@ Parser::parse_null_fn Parser::null_denotation(Token::Type type) {
|
|
2769
2818
|
return &Parser::parse_end_block;
|
2770
2819
|
case Type::FILEKeyword:
|
2771
2820
|
return &Parser::parse_file_constant;
|
2821
|
+
case Type::ForKeyword:
|
2822
|
+
return &Parser::parse_for;
|
2772
2823
|
case Type::LParen:
|
2773
2824
|
return &Parser::parse_group;
|
2774
2825
|
case Type::LCurlyBrace:
|
@@ -2790,7 +2841,7 @@ Parser::parse_null_fn Parser::null_denotation(Token::Type type) {
|
|
2790
2841
|
case Type::InterpolatedSymbolBegin:
|
2791
2842
|
return &Parser::parse_interpolated_symbol;
|
2792
2843
|
case Type::StarStar:
|
2793
|
-
return &Parser::
|
2844
|
+
return &Parser::parse_keyword_splat;
|
2794
2845
|
case Type::Bignum:
|
2795
2846
|
case Type::Fixnum:
|
2796
2847
|
case Type::Float:
|
@@ -2886,7 +2937,7 @@ Parser::parse_left_fn Parser::left_denotation(Token &token, SharedPtr<Node> left
|
|
2886
2937
|
return &Parser::parse_infix_expression;
|
2887
2938
|
case Type::Minus:
|
2888
2939
|
case Type::Plus:
|
2889
|
-
if (peek_token().whitespace_precedes() || !left->is_callable())
|
2940
|
+
if (!token.whitespace_precedes() || peek_token().whitespace_precedes() || !left->is_callable())
|
2890
2941
|
return &Parser::parse_infix_expression;
|
2891
2942
|
break;
|
2892
2943
|
case Type::DoKeyword:
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: natalie_parser
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tim Morgan
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-08-12 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: NatalieParser is a zero-dependency, from-scratch, hand-written recursive
|
14
14
|
descent parser for the Ruby Programming Language.
|
@@ -65,6 +65,7 @@ files:
|
|
65
65
|
- include/natalie_parser/node/false_node.hpp
|
66
66
|
- include/natalie_parser/node/fixnum_node.hpp
|
67
67
|
- include/natalie_parser/node/float_node.hpp
|
68
|
+
- include/natalie_parser/node/for_node.hpp
|
68
69
|
- include/natalie_parser/node/forward_args_node.hpp
|
69
70
|
- include/natalie_parser/node/hash_node.hpp
|
70
71
|
- include/natalie_parser/node/hash_pattern_node.hpp
|