natalie_parser 2.2.0 → 2.3.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: 31bee2746c1e7a36eca16194e27467264dfbad5131075090e0b94c675ff150b1
4
- data.tar.gz: 7f9a2a77aa2e34c56faeb740900779ff4682471cb02dbd51403eb1b7f72b13a7
3
+ metadata.gz: 2de813d6f0cb6ab94d5a6bb2dd64dc8be719567d401f19ea2b6ac9e71ebaed65
4
+ data.tar.gz: 668f9574d0968781f077e407f6476f2b320195964f8c10342ab2761beb3f7587
5
5
  SHA512:
6
- metadata.gz: 74335871743111340267d41631f3eeae0281ea5f93bb2bb4a6536682a55f325162d428d986ba0182e14adfb27e830f9622048fb443f1561ee9611c988b07bd6b
7
- data.tar.gz: 2fd301c60b32db6c074946adceaa54fca3736f6e64335d5bb68c9ad01f092bf19f1ce2810fb75e4641e4b6cf7ebf7e77165538282107f214fa116254b0d37ac0
6
+ metadata.gz: 02da9b2afc4bc76c5b76b03abb48a40bb9242e2884146bedd61b7b944f358401655aeb077bf7900eaf6c3936c3d06000115d20200b378d17bf98cc18ffd28174
7
+ data.tar.gz: 5e42458d1ab43bce763d3b929b095f9df5ab620e0c9ba25caac5abb8bd44673ded90bcbcd943b94ffe6cadf65d28f5bfebb54bb50942909c4055feb5c613cb2f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.3.0 (2022-12-31)
4
+
5
+ - FIX: Add for loop variables to local scope
6
+ - FIX: Add lexer support for leading-zero (bare) octal - e.g. 0777
7
+ - FIX: Allow non-local variable in rescue
8
+ - FIX: Allow trailing comma after for loop variables
9
+ - FIX: Fix for-loop with attr assignment
10
+ - FIX: Fix lexing of symbol keys with trailing punctuation
11
+ - FIX: Fix precedence of 'do' keyword in use with 'for'
12
+ - FIX: Parse for loop with splat assignment
13
+ - FIX: Use proper make on OpenBSD
14
+
3
15
  ## 2.2.0 (2022-10-24)
4
16
 
5
17
  - FIX: Allow backreference as first arg of implicit call
data/Dockerfile CHANGED
@@ -8,7 +8,7 @@ ENV LC_ALL C.UTF-8
8
8
 
9
9
  WORKDIR natalie_parser
10
10
 
11
- COPY Gemfile /natalie_parser/
11
+ COPY Gemfile /natalie_parser/
12
12
  RUN bundle install
13
13
 
14
14
  ARG CC=gcc
data/Gemfile CHANGED
@@ -6,5 +6,5 @@ source 'https://rubygems.org'
6
6
  gem 'minitest'
7
7
  gem 'minitest-focus'
8
8
  gem 'minitest-reporters'
9
- gem 'ruby_parser'
9
+ gem 'ruby_parser', '3.19.1'
10
10
  gem 'rake'
data/README.md CHANGED
@@ -1,5 +1,6 @@
1
1
  # Natalie Parser
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/natalie_parser.svg)](https://badge.fury.io/rb/natalie_parser)
3
4
  [![github build status](https://github.com/natalie-lang/natalie_parser/actions/workflows/build.yml/badge.svg)](https://github.com/natalie-lang/natalie_parser/actions?query=workflow%3ABuild+branch%3Amaster)
4
5
  [![MIT License](https://img.shields.io/badge/license-MIT-blue)](https://github.com/natalie-lang/natalie_parser/blob/master/LICENSE)
5
6
 
data/Rakefile CHANGED
@@ -185,7 +185,8 @@ file "ext/natalie_parser/natalie_parser.#{so_ext}" => [
185
185
  else
186
186
  cxx_hacky = cxx
187
187
  end
188
- sh "CC=#{cc.inspect} CXX=#{cxx_hacky.inspect} make -C #{build_dir} -j -e V=1 2>&1 | tee #{log_file}"
188
+ make = system('which gmake 2>&1 > /dev/null') ? 'gmake' : 'make'
189
+ sh "CC=#{cc.inspect} CXX=#{cxx_hacky.inspect} #{make} -C #{build_dir} -j -e V=1 2>&1 | tee #{log_file}"
189
190
  end
190
191
 
191
192
  file 'build/fragments.hpp' => ['test/parser_test.rb', 'test/support/extract_parser_test_fragments.rb'] do
@@ -25,7 +25,7 @@ public:
25
25
 
26
26
  virtual void reset_sexp() override {
27
27
  m_sexp = rb_class_new_instance(0, nullptr, Sexp);
28
- rb_ivar_set(m_sexp, rb_intern("@file"), get_file_string(file()));
28
+ rb_ivar_set(m_sexp, rb_intern("@file"), get_file_string(*file()));
29
29
  rb_ivar_set(m_sexp, rb_intern("@line"), rb_int_new(line() + 1));
30
30
  rb_ivar_set(m_sexp, rb_intern("@column"), rb_int_new(column() + 1));
31
31
  }
@@ -139,13 +139,13 @@ public:
139
139
  private:
140
140
  VALUE m_sexp { Qnil };
141
141
 
142
- static VALUE get_file_string(SharedPtr<const String> file) {
143
- auto file_string = s_file_cache.get(*file);
142
+ static VALUE get_file_string(const String &file) {
143
+ auto file_string = s_file_cache.get(file);
144
144
  if (!file_string) {
145
- file_string = rb_str_new(file->c_str(), file->length());
145
+ file_string = rb_str_new(file.c_str(), file.length());
146
146
  // FIXME: Seems there is no way to un-register and object. :-(
147
147
  rb_gc_register_mark_object(file_string);
148
- s_file_cache.put(*file, file_string);
148
+ s_file_cache.put(file, file_string);
149
149
  }
150
150
  return file_string;
151
151
  }
@@ -27,9 +27,9 @@ VALUE initialize(int argc, VALUE *argv, VALUE self) {
27
27
  return self;
28
28
  }
29
29
 
30
- VALUE node_to_ruby(TM::SharedPtr<NatalieParser::Node> node) {
31
- NatalieParser::MRICreator creator { node.ref() };
32
- node->transform(&creator);
30
+ VALUE node_to_ruby(const NatalieParser::Node &node) {
31
+ NatalieParser::MRICreator creator { node };
32
+ node.transform(&creator);
33
33
  return creator.sexp();
34
34
  }
35
35
 
@@ -41,7 +41,7 @@ VALUE parse_on_instance(VALUE self) {
41
41
  auto parser = NatalieParser::Parser { code_string, path_string };
42
42
  try {
43
43
  auto tree = parser.tree();
44
- VALUE ast = node_to_ruby(tree);
44
+ VALUE ast = node_to_ruby(*tree);
45
45
  return ast;
46
46
  } catch (NatalieParser::Parser::SyntaxError &error) {
47
47
  rb_raise(rb_eSyntaxError, "%s", error.message());
@@ -1,7 +1,6 @@
1
1
  #pragma once
2
2
 
3
3
  #include "natalie_parser/node/block_node.hpp"
4
- #include "natalie_parser/node/identifier_node.hpp"
5
4
  #include "natalie_parser/node/node.hpp"
6
5
  #include "natalie_parser/node/node_with_args.hpp"
7
6
  #include "tm/hashmap.hpp"
@@ -23,24 +22,24 @@ public:
23
22
  m_exceptions.push(node);
24
23
  }
25
24
 
26
- void set_exception_name(SharedPtr<IdentifierNode> name) {
25
+ void set_exception_name(SharedPtr<Node> name) {
27
26
  m_name = name;
28
27
  }
29
28
 
30
29
  void set_body(SharedPtr<BlockNode> body) { m_body = body; }
31
30
 
32
- SharedPtr<Node> name_to_node() const;
31
+ SharedPtr<Node> name_to_assignment() const;
33
32
 
34
33
  bool has_name() const { return m_name; }
35
34
 
36
- const SharedPtr<IdentifierNode> name() const { return m_name; }
35
+ const SharedPtr<Node> name() const { return m_name; }
37
36
  const Vector<SharedPtr<Node>> &exceptions() const { return m_exceptions; }
38
37
  const SharedPtr<BlockNode> body() const { return m_body; }
39
38
 
40
39
  virtual void transform(Creator *creator) const override;
41
40
 
42
41
  protected:
43
- SharedPtr<IdentifierNode> m_name {};
42
+ SharedPtr<Node> m_name {};
44
43
  Vector<SharedPtr<Node>> m_exceptions {};
45
44
  SharedPtr<BlockNode> m_body {};
46
45
  };
@@ -25,22 +25,7 @@ public:
25
25
  const SharedPtr<Node> vars() const { return m_vars; }
26
26
  const SharedPtr<BlockNode> body() const { return m_body; }
27
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
- }
28
+ virtual void transform(Creator *creator) const override;
44
29
 
45
30
  protected:
46
31
  SharedPtr<Node> m_expr {};
@@ -161,6 +161,7 @@ private:
161
161
  SharedPtr<Node> parse_assignment_expression(SharedPtr<Node>, LocalsHashmap &);
162
162
  SharedPtr<Node> parse_assignment_expression_without_multiple_values(SharedPtr<Node>, LocalsHashmap &);
163
163
  SharedPtr<Node> parse_assignment_expression(SharedPtr<Node>, LocalsHashmap &, bool);
164
+ void add_assignment_locals(SharedPtr<Node>, LocalsHashmap &);
164
165
  SharedPtr<Node> parse_assignment_expression_value(bool, LocalsHashmap &, bool);
165
166
  SharedPtr<Node> parse_assignment_identifier(bool, LocalsHashmap &);
166
167
  SharedPtr<Node> parse_call_expression_without_parens(SharedPtr<Node>, LocalsHashmap &);
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class NatalieParser
4
- VERSION = '2.2.0'
4
+ VERSION = '2.3.0'
5
5
  end
data/src/lexer.cpp CHANGED
@@ -975,6 +975,10 @@ Token Lexer::consume_bare_name_or_constant(Token::Type type) {
975
975
  switch (c) {
976
976
  case '?':
977
977
  case '!':
978
+ if (peek() == ':' && m_last_token.can_precede_symbol_key()) {
979
+ advance();
980
+ type = Token::Type::SymbolKey;
981
+ }
978
982
  advance();
979
983
  buf->append_char(c);
980
984
  break;
@@ -1284,7 +1288,26 @@ Token Lexer::consume_numeric() {
1284
1288
  break;
1285
1289
  }
1286
1290
  default:
1287
- token = consume_decimal_digits_and_build_token();
1291
+ char c = peek();
1292
+
1293
+ if (isdigit(c)) {
1294
+ // bare octal case, e.g. 0777.
1295
+ // If starts with a 0 but next number is not 0..7 then that's an error.
1296
+ if (!(c >= '0' && c <= '7'))
1297
+ return Token { Token::Type::Invalid, c, m_file, m_cursor_line,
1298
+ m_cursor_column, m_whitespace_precedes };
1299
+ chars->append_char(c);
1300
+ c = next();
1301
+ do {
1302
+ chars->append_char(c);
1303
+ c = next();
1304
+ if (c == '_')
1305
+ c = next();
1306
+ } while (c >= '0' && c <= '7');
1307
+ token = chars_to_fixnum_or_bignum_token(chars, 8, 1);
1308
+ } else {
1309
+ token = consume_decimal_digits_and_build_token();
1310
+ }
1288
1311
  }
1289
1312
  } else {
1290
1313
  token = consume_decimal_digits_and_build_token();
@@ -1,14 +1,15 @@
1
1
  #include "natalie_parser/node/begin_rescue_node.hpp"
2
2
  #include "natalie_parser/node/array_node.hpp"
3
3
  #include "natalie_parser/node/assignment_node.hpp"
4
+ #include "natalie_parser/node/identifier_node.hpp"
4
5
 
5
6
  namespace NatalieParser {
6
7
 
7
- SharedPtr<Node> BeginRescueNode::name_to_node() const {
8
+ SharedPtr<Node> BeginRescueNode::name_to_assignment() const {
8
9
  assert(m_name);
9
10
  return new AssignmentNode {
10
11
  token(),
11
- m_name.static_cast_as<Node>(),
12
+ m_name,
12
13
  new IdentifierNode {
13
14
  Token { Token::Type::GlobalVariable, "$!", file(), line(), column(), false },
14
15
  false },
@@ -21,7 +22,7 @@ void BeginRescueNode::transform(Creator *creator) const {
21
22
  for (auto exception_node : m_exceptions)
22
23
  array.add_node(exception_node);
23
24
  if (m_name)
24
- array.add_node(name_to_node());
25
+ array.add_node(name_to_assignment());
25
26
  creator->append(array);
26
27
  if (m_body->nodes().is_empty())
27
28
  creator->append_nil();
@@ -0,0 +1,13 @@
1
+ #include "natalie_parser/node/for_node.hpp"
2
+
3
+ namespace NatalieParser {
4
+
5
+ void ForNode::transform(Creator *creator) const {
6
+ creator->set_type("for");
7
+ creator->append(m_expr);
8
+ creator->with_assignment(true, [&]() { creator->append(*m_vars); });
9
+ if (!m_body->is_empty())
10
+ creator->append(m_body->without_unnecessary_nesting());
11
+ }
12
+
13
+ }
data/src/parser.cpp CHANGED
@@ -434,16 +434,8 @@ void Parser::parse_rest_of_begin(BeginNode &begin_node, LocalsHashmap &locals) {
434
434
  }
435
435
  if (current_token().is_hash_rocket()) {
436
436
  advance();
437
- SharedPtr<IdentifierNode> name;
438
- switch (current_token().type()) {
439
- case Token::Type::BareName:
440
- name = new IdentifierNode { current_token(), current_token().literal_string() };
441
- advance();
442
- break;
443
- default:
444
- throw_unexpected("exception name");
445
- }
446
- name->add_to_locals(locals);
437
+ auto name = parse_expression(Precedence::ASSIGNMENT_LHS, locals);
438
+ add_assignment_locals(name, locals);
447
439
  rescue_node->set_exception_name(name);
448
440
  }
449
441
  next_expression();
@@ -910,7 +902,7 @@ SharedPtr<Node> Parser::parse_multiple_assignment_expression(SharedPtr<Node> lef
910
902
  list->add_node(left);
911
903
  while (current_token().is_comma()) {
912
904
  advance();
913
- if (current_token().is_rparen() || current_token().is_equal()) {
905
+ if (current_token().is_rparen() || current_token().is_equal() || current_token().type() == Token::Type::InKeyword) {
914
906
  // trailing comma with no additional identifier
915
907
  break;
916
908
  }
@@ -1296,13 +1288,16 @@ SharedPtr<Node> Parser::parse_line_constant(LocalsHashmap &) {
1296
1288
  SharedPtr<Node> Parser::parse_for(LocalsHashmap &locals) {
1297
1289
  auto token = current_token();
1298
1290
  advance();
1299
- auto vars = parse_assignment_identifier(false, locals);
1300
- if (current_token().type() == Token::Type::Comma) {
1291
+ auto vars = parse_assignment_identifier(true, locals);
1292
+ if (current_token().is_comma() || vars->type() == Node::Type::Splat) {
1301
1293
  vars = parse_multiple_assignment_expression(vars, locals);
1302
1294
  }
1295
+ add_assignment_locals(vars, locals);
1303
1296
  expect(Token::Type::InKeyword, "for in");
1304
1297
  advance();
1305
- auto expr = parse_expression(Precedence::LOWEST, locals, IterAllow::CURLY_ONLY);
1298
+ m_call_depth.last()++;
1299
+ auto expr = parse_expression(Precedence::BARE_CALL_ARG, locals, IterAllow::CURLY_ONLY);
1300
+ m_call_depth.last()--;
1306
1301
  if (current_token().type() == Token::Type::DoKeyword) {
1307
1302
  advance();
1308
1303
  }
@@ -2177,32 +2172,34 @@ SharedPtr<Node> Parser::parse_assignment_expression(SharedPtr<Node> left, Locals
2177
2172
  if (left->type() == Node::Type::Splat) {
2178
2173
  return parse_multiple_assignment_expression(left, locals);
2179
2174
  }
2175
+ add_assignment_locals(left, locals);
2176
+ advance();
2177
+ bool to_array = left->type() == Node::Type::MultipleAssignment;
2178
+ auto value = parse_assignment_expression_value(to_array, locals, allow_multiple);
2179
+ return new AssignmentNode { token, left, value };
2180
+ }
2181
+
2182
+ void Parser::add_assignment_locals(SharedPtr<Node> left, LocalsHashmap &locals) {
2180
2183
  switch (left->type()) {
2181
2184
  case Node::Type::Identifier: {
2182
2185
  auto left_identifier = left.static_cast_as<IdentifierNode>();
2183
2186
  left_identifier->add_to_locals(locals);
2184
- advance();
2185
- auto value = parse_assignment_expression_value(false, locals, allow_multiple);
2186
- return new AssignmentNode { token, left, value };
2187
+ break;
2187
2188
  }
2188
2189
  case Node::Type::Call:
2189
2190
  case Node::Type::Colon2:
2190
2191
  case Node::Type::Colon3:
2191
2192
  case Node::Type::SafeCall: {
2192
- advance();
2193
- auto value = parse_assignment_expression_value(false, locals, allow_multiple);
2194
- return new AssignmentNode { token, left, value };
2193
+ break;
2195
2194
  }
2196
2195
  case Node::Type::MultipleAssignment: {
2197
2196
  left.static_cast_as<MultipleAssignmentNode>()->add_locals(locals);
2198
- advance();
2199
- auto value = parse_assignment_expression_value(true, locals, allow_multiple);
2200
- return new AssignmentNode { token, left, value };
2197
+ break;
2201
2198
  }
2202
2199
  default:
2203
- throw_unexpected(left->token(), "left side of assignment");
2200
+ throw_unexpected(left->token(), "assignment identifier");
2204
2201
  }
2205
- };
2202
+ }
2206
2203
 
2207
2204
  SharedPtr<Node> Parser::parse_assignment_expression_value(bool to_array, LocalsHashmap &locals, bool allow_multiple) {
2208
2205
  auto token = current_token();
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.2.0
4
+ version: 2.3.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-10-25 00:00:00.000000000 Z
11
+ date: 2022-12-31 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.
@@ -151,6 +151,7 @@ files:
151
151
  - src/node/begin_node.cpp
152
152
  - src/node/begin_rescue_node.cpp
153
153
  - src/node/class_node.cpp
154
+ - src/node/for_node.cpp
154
155
  - src/node/interpolated_regexp_node.cpp
155
156
  - src/node/interpolated_shell_node.cpp
156
157
  - src/node/interpolated_string_node.cpp