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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b35f9b98f6bbe4ec3bf8d5d9bce2c7e082504db23f442964f2a58e1ac9e11dc7
4
- data.tar.gz: 1fbf06aa6fae8400855ea0d363d77a1b27d78869266bfb17b0f45cf9b8a30d41
3
+ metadata.gz: '0598d42a8e0b83aa5a340d1615474a4c5a8e7b209032bf574a230778e4d3ce5e'
4
+ data.tar.gz: 475cce9e2f078ab440674b48e3b99ba48f163f34b889b376cf681f74a489ce53
5
5
  SHA512:
6
- metadata.gz: 936ec0ef70541afd5839dbe4450401d7e8713499c3536d9ac7e53eb5617671dd308eee8e4f458188a9b0890f5a7ed5d855b3011c5539b64eb31639b4140e07fc
7
- data.tar.gz: f6deb35c75e17e5c9fb49c6092e48453e904e7c7d4b515e2c7b5fe7fe69dc656da2faa6096b665b6021557ae8c47844ec65a9e8aca1933712f35d72f4db47c50
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 }
@@ -43,6 +43,7 @@ public:
43
43
  False,
44
44
  Fixnum,
45
45
  Float,
46
+ For,
46
47
  ForwardArgs,
47
48
  Hash,
48
49
  HashPattern,
@@ -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;
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class NatalieParser
4
- VERSION = '2.0.0'
4
+ VERSION = '2.1.0'
5
5
  end
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
- return Token { Token::Type::Slash, m_file, m_token_line, m_token_column, m_whitespace_precedes };
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 };
@@ -3,6 +3,11 @@
3
3
  namespace NatalieParser {
4
4
 
5
5
  void MatchNode::transform(Creator *creator) const {
6
+ if (!m_arg) {
7
+ creator->set_type("match");
8
+ creator->append(m_regexp.ref());
9
+ return;
10
+ }
6
11
  if (m_regexp_on_left)
7
12
  creator->set_type("match2");
8
13
  else
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) return 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) return 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
- auto arg = parse_expression(Precedence::BARE_CALL_ARG, locals);
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
- if (!first_key->is_symbol_key()) {
1317
- expect(Token::Type::HashRocket, "hash rocket");
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::StarStar) // **kwsplat
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
- if (!key->is_symbol_key()) {
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 = parse_if(locals);
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::parse_keyword_splat_wrapped_in_hash;
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.0.0
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-06-24 00:00:00.000000000 Z
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