natalie_parser 2.0.0 → 2.1.0
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/CHANGELOG.md +13 -0
- data/include/natalie_parser/node/for_node.hpp +50 -0
- data/include/natalie_parser/node/match_node.hpp +6 -0
- data/include/natalie_parser/node/node.hpp +1 -0
- data/include/natalie_parser/node.hpp +1 -0
- data/include/natalie_parser/parser.hpp +2 -0
- data/include/natalie_parser/token.hpp +17 -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 +74 -24
- 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,18 @@
|
|
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
|
+
|
3
16
|
## 2.0.0 (2022-06-24)
|
4
17
|
|
5
18
|
- FEAT: Differentiate between bare/implicit hash and explicit one
|
@@ -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
|
+
}
|
@@ -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 }
|
@@ -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
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 &);
|
@@ -808,6 +808,22 @@ public:
|
|
808
808
|
}
|
809
809
|
}
|
810
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
|
+
|
811
827
|
bool can_precede_symbol_key() const {
|
812
828
|
switch (m_type) {
|
813
829
|
case Type::BareName:
|
@@ -818,6 +834,7 @@ public:
|
|
818
834
|
case Type::LParen:
|
819
835
|
case Type::Pipe:
|
820
836
|
case Type::PipePipe:
|
837
|
+
case Type::SuperKeyword:
|
821
838
|
return true;
|
822
839
|
default:
|
823
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
@@ -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("..."))
|
@@ -1310,28 +1341,35 @@ SharedPtr<Node> Parser::parse_hash(LocalsHashmap &locals) {
|
|
1310
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
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(), true };
|
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,6 +2372,9 @@ 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
|
}
|
@@ -2700,6 +2742,9 @@ SharedPtr<Node> Parser::parse_unless(LocalsHashmap &locals) {
|
|
2700
2742
|
auto token = current_token();
|
2701
2743
|
advance();
|
2702
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
|
+
}
|
2703
2748
|
next_expression();
|
2704
2749
|
SharedPtr<Node> false_expr = parse_if_body(locals);
|
2705
2750
|
SharedPtr<Node> true_expr;
|
@@ -2718,6 +2763,9 @@ SharedPtr<Node> Parser::parse_while(LocalsHashmap &locals) {
|
|
2718
2763
|
auto token = current_token();
|
2719
2764
|
advance();
|
2720
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
|
+
}
|
2721
2769
|
next_expression();
|
2722
2770
|
SharedPtr<BlockNode> body = parse_body(locals, Precedence::LOWEST);
|
2723
2771
|
expect(Token::Type::EndKeyword, "while end");
|
@@ -2770,6 +2818,8 @@ Parser::parse_null_fn Parser::null_denotation(Token::Type type) {
|
|
2770
2818
|
return &Parser::parse_end_block;
|
2771
2819
|
case Type::FILEKeyword:
|
2772
2820
|
return &Parser::parse_file_constant;
|
2821
|
+
case Type::ForKeyword:
|
2822
|
+
return &Parser::parse_for;
|
2773
2823
|
case Type::LParen:
|
2774
2824
|
return &Parser::parse_group;
|
2775
2825
|
case Type::LCurlyBrace:
|
@@ -2791,7 +2841,7 @@ Parser::parse_null_fn Parser::null_denotation(Token::Type type) {
|
|
2791
2841
|
case Type::InterpolatedSymbolBegin:
|
2792
2842
|
return &Parser::parse_interpolated_symbol;
|
2793
2843
|
case Type::StarStar:
|
2794
|
-
return &Parser::
|
2844
|
+
return &Parser::parse_keyword_splat;
|
2795
2845
|
case Type::Bignum:
|
2796
2846
|
case Type::Fixnum:
|
2797
2847
|
case Type::Float:
|
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: 2.
|
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
|