natalie_parser 1.2.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: db5a804188bcf10ccfbadada61b82e266f227650c5a0b53929f719975d967999
4
- data.tar.gz: b14cc0efffacc5219f7bcd5174116d0f302f7d56c4c69bfc13214b7b653adbb7
3
+ metadata.gz: '0598d42a8e0b83aa5a340d1615474a4c5a8e7b209032bf574a230778e4d3ce5e'
4
+ data.tar.gz: 475cce9e2f078ab440674b48e3b99ba48f163f34b889b376cf681f74a489ce53
5
5
  SHA512:
6
- metadata.gz: fb144d0f89276c280a3e7500f765cf98df9cffd4cfa8f0d0ed8d4a6d3a0d98f21b6b27d9d32b6b053a89edc0c092921e7d1215a546b7322162bd5bab7c37cf25
7
- data.tar.gz: b467baf8f5619ea58d6ce8fb2802d63509b51b020459de8b2c8a53ed7af8927b45f3191ddda1429456d875f892be0ffc79f426915395f968210b90a030aab8f6
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
- creator->set_type("hash");
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
  }
@@ -14,7 +14,7 @@ using namespace TM;
14
14
  class HashPatternNode : public HashNode {
15
15
  public:
16
16
  HashPatternNode(const Token &token)
17
- : HashNode { token } { }
17
+ : HashNode { token, true } { }
18
18
 
19
19
  virtual Type type() const override { return Type::HashPattern; }
20
20
 
@@ -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,
@@ -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 true; }
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;
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class NatalieParser
4
- VERSION = '1.2.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
@@ -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) 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("..."))
@@ -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
- 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() };
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 bare, Token::Type closing_token_type, SharedPtr<Node> first_arg) {
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 (bare)
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::parse_keyword_splat_wrapped_in_hash;
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.2.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-17 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