natalie_parser 1.2.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: 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