natalie_parser 2.0.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 +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
|