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 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