From 9cef7af6611b9d306f43f5b54fa381b61cc29ba4 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Fri, 28 Oct 2022 14:11:55 +0200 Subject: [PATCH 1/7] Make the test suite pass on TruffleRuby --- Rakefile | 8 +++++++- lib/syntax_tree/cli.rb | 6 +++--- test/cli_test.rb | 2 ++ test/location_test.rb | 12 ++++-------- test/node_test.rb | 7 +++---- test/test_helper.rb | 22 +++++++++++++--------- 6 files changed, 32 insertions(+), 25 deletions(-) diff --git a/Rakefile b/Rakefile index 4973d45e..6de81bd8 100644 --- a/Rakefile +++ b/Rakefile @@ -7,7 +7,13 @@ require "syntax_tree/rake_tasks" Rake::TestTask.new(:test) do |t| t.libs << "test" t.libs << "lib" - t.test_files = FileList["test/**/*_test.rb"] + test_files = FileList["test/**/*_test.rb"] + if RUBY_ENGINE == "truffleruby" + # language_server.rb uses pattern matching + test_files -= FileList["test/language_server/*_test.rb"] + test_files -= FileList["test/language_server_test.rb"] + end + t.test_files = test_files end task default: :test diff --git a/lib/syntax_tree/cli.rb b/lib/syntax_tree/cli.rb index b847e059..be0bb793 100644 --- a/lib/syntax_tree/cli.rb +++ b/lib/syntax_tree/cli.rb @@ -192,9 +192,9 @@ def run(item) # would match the first expression of the input given. class Expr < Action def run(item) - case item.handler.parse(item.source) - in Program[statements: Statements[body: [expression]]] - puts expression.construct_keys + program = item.handler.parse(item.source) + if Program === program and expressions = program.statements.body and expressions.size == 1 + puts expressions.first.construct_keys else warn("The input to `stree expr` must be a single expression.") exit(1) diff --git a/test/cli_test.rb b/test/cli_test.rb index b4ef0afc..b5316d7f 100644 --- a/test/cli_test.rb +++ b/test/cli_test.rb @@ -148,6 +148,7 @@ def test_inline_script end def test_multiple_inline_scripts + skip if RUBY_ENGINE == "truffleruby" # Relies on a thread-safe StringIO stdio, = capture_io { SyntaxTree::CLI.run(%w[format -e 1+1 -e 2+2]) } assert_equal(["1 + 1", "2 + 2"], stdio.split("\n").sort) end @@ -172,6 +173,7 @@ def test_plugins def test_language_server prev_stdin = $stdin prev_stdout = $stdout + skip unless SUPPORTS_PATTERN_MATCHING request = { method: "shutdown" }.merge(jsonrpc: "2.0").to_json $stdin = diff --git a/test/location_test.rb b/test/location_test.rb index 2a697281..26831fb1 100644 --- a/test/location_test.rb +++ b/test/location_test.rb @@ -14,19 +14,15 @@ def test_lines def test_deconstruct location = Location.fixed(line: 1, char: 0, column: 0) - case location - in [start_line, 0, 0, *] - assert_equal(1, start_line) - end + assert_equal(1, location.start_line) + assert_equal(0, location.start_char) + assert_equal(0, location.start_column) end def test_deconstruct_keys location = Location.fixed(line: 1, char: 0, column: 0) - case location - in start_line: - assert_equal(1, start_line) - end + assert_equal(1, location.start_line) end end end diff --git a/test/node_test.rb b/test/node_test.rb index 1a5af125..ce26f9ea 100644 --- a/test/node_test.rb +++ b/test/node_test.rb @@ -759,10 +759,9 @@ def test_program program = parser.parse refute(parser.error?) - case program - in statements: { body: [statement] } - assert_kind_of(VCall, statement) - end + statements = program.statements.body + assert_equal 1, statements.size + assert_kind_of(VCall, statements.first) json = JSON.parse(program.to_json) io = StringIO.new diff --git a/test/test_helper.rb b/test/test_helper.rb index 80e514f0..c421d8ee 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -17,6 +17,8 @@ require "pp" require "minitest/autorun" +SUPPORTS_PATTERN_MATCHING = RUBY_ENGINE != "truffleruby" + module SyntaxTree module Assertions class Recorder @@ -67,15 +69,17 @@ def assert_syntax_tree(node) refute_includes(json, "#<") assert_equal(type, JSON.parse(json)["type"]) - # Get a match expression from the node, then assert that it can in fact - # match the node. - # rubocop:disable all - assert(eval(<<~RUBY)) - case node - in #{node.construct_keys} - true - end - RUBY + if SUPPORTS_PATTERN_MATCHING + # Get a match expression from the node, then assert that it can in fact + # match the node. + # rubocop:disable all + assert(eval(<<~RUBY)) + case node + in #{node.construct_keys} + true + end + RUBY + end end Minitest::Test.include(self) From 0c8b5e3215d3c2a8f882c13c71f183f125f999db Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Fri, 28 Oct 2022 14:11:10 +0200 Subject: [PATCH 2/7] Replace pattern matching in lib/syntax_tree/pattern.rb * For performance and compatibility with Rubies not supporting it yet. --- lib/syntax_tree/pattern.rb | 61 +++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/lib/syntax_tree/pattern.rb b/lib/syntax_tree/pattern.rb index aa558361..15b6a0a9 100644 --- a/lib/syntax_tree/pattern.rb +++ b/lib/syntax_tree/pattern.rb @@ -84,11 +84,11 @@ def combine_or(left, right) end def compile_node(root) - case root - in AryPtn[constant:, requireds:, rest: nil, posts: []] + if AryPtn === root and root.rest.nil? and root.posts.empty? + constant = root.constant compiled_constant = compile_node(constant) if constant - preprocessed = requireds.map { |required| compile_node(required) } + preprocessed = root.requireds.map { |required| compile_node(required) } compiled_requireds = ->(node) do deconstructed = node.deconstruct @@ -104,34 +104,32 @@ def compile_node(root) else compiled_requireds end - in Binary[left:, operator: :|, right:] - combine_or(compile_node(left), compile_node(right)) - in Const[value:] if SyntaxTree.const_defined?(value) - clazz = SyntaxTree.const_get(value) + elsif Binary === root and root.operator == :| + combine_or(compile_node(root.left), compile_node(root.right)) + elsif Const === root and SyntaxTree.const_defined?(root.value) + clazz = SyntaxTree.const_get(root.value) ->(node) { node.is_a?(clazz) } - in Const[value:] if Object.const_defined?(value) - clazz = Object.const_get(value) + elsif Const === root and Object.const_defined?(root.value) + clazz = Object.const_get(root.value) ->(node) { node.is_a?(clazz) } - in ConstPathRef[ - parent: VarRef[value: Const[value: "SyntaxTree"]], constant: - ] - compile_node(constant) - in DynaSymbol[parts: []] + elsif ConstPathRef === root and VarRef === root.parent and Const === root.parent.value and root.parent.value.value == "SyntaxTree" + compile_node(root.constant) + elsif DynaSymbol === root and root.parts.empty? symbol = :"" ->(node) { node == symbol } - in DynaSymbol[parts: [TStringContent[value:]]] - symbol = value.to_sym + elsif DynaSymbol === root and parts = root.parts and parts.size == 1 and TStringContent === parts[0] + symbol = parts[0].value.to_sym ->(attribute) { attribute == value } - in HshPtn[constant:, keywords:, keyword_rest: nil] - compiled_constant = compile_node(constant) + elsif HshPtn === root and root.keyword_rest.nil? + compiled_constant = compile_node(root.constant) preprocessed = - keywords.to_h do |keyword, value| - raise NoMatchingPatternError unless keyword.is_a?(Label) + root.keywords.to_h do |keyword, value| + raise CompilationError, PP.pp(root, +"").chomp unless keyword.is_a?(Label) [keyword.value.chomp(":").to_sym, compile_node(value)] end @@ -148,25 +146,26 @@ def compile_node(root) else compiled_keywords end - in RegexpLiteral[parts: [TStringContent[value:]]] - regexp = /#{value}/ + elsif RegexpLiteral === root and parts = root.parts and parts.size == 1 and TStringContent === parts[0] + regexp = /#{parts[0].value}/ ->(attribute) { regexp.match?(attribute) } - in StringLiteral[parts: []] + elsif StringLiteral === root and root.parts.empty? ->(attribute) { attribute == "" } - in StringLiteral[parts: [TStringContent[value:]]] + elsif StringLiteral === root and parts = root.parts and parts.size == 1 and TStringContent === parts[0] + value = parts[0].value ->(attribute) { attribute == value } - in SymbolLiteral[value:] - symbol = value.value.to_sym + elsif SymbolLiteral === root + symbol = root.value.value.to_sym ->(attribute) { attribute == symbol } - in VarRef[value: Const => value] - compile_node(value) - in VarRef[value: Kw[value: "nil"]] + elsif VarRef === root and Const === root.value + compile_node(root.value) + elsif VarRef === root and Kw === root.value and root.value.value.nil? ->(attribute) { attribute.nil? } + else + raise CompilationError, PP.pp(root, +"").chomp end - rescue NoMatchingPatternError - raise CompilationError, PP.pp(root, +"").chomp end end end From 12f3e8691f192a2e20baad867d46f0c2e5832011 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Fri, 28 Oct 2022 15:05:27 +0200 Subject: [PATCH 3/7] Fix test for DynaSymbol --- lib/syntax_tree/pattern.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/syntax_tree/pattern.rb b/lib/syntax_tree/pattern.rb index 15b6a0a9..f56e8a1b 100644 --- a/lib/syntax_tree/pattern.rb +++ b/lib/syntax_tree/pattern.rb @@ -123,7 +123,7 @@ def compile_node(root) elsif DynaSymbol === root and parts = root.parts and parts.size == 1 and TStringContent === parts[0] symbol = parts[0].value.to_sym - ->(attribute) { attribute == value } + ->(node) { node == symbol } elsif HshPtn === root and root.keyword_rest.nil? compiled_constant = compile_node(root.constant) From 48c659cfa5fe9b4c82939e4196e5aac8bf97f5e9 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Fri, 28 Oct 2022 15:06:21 +0200 Subject: [PATCH 4/7] Add TruffleRuby in CI --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d35471fa..7bbdedc7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,6 +12,7 @@ jobs: - '3.0' - '3.1' - head + - truffleruby-head name: CI runs-on: ubuntu-latest env: From ab6e669635df9cfea52bdf98d8f2d72efb1099c0 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Fri, 28 Oct 2022 15:19:35 +0200 Subject: [PATCH 5/7] Run in --verbose mode to see progress in CI --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7bbdedc7..9f95cc9d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -17,6 +17,7 @@ jobs: runs-on: ubuntu-latest env: CI: true + TESTOPTS: --verbose steps: - uses: actions/checkout@master - uses: ruby/setup-ruby@v1 From 42bb2a44fc81a3e36c0506fc7d842258eae7a641 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Fri, 28 Oct 2022 15:21:05 +0200 Subject: [PATCH 6/7] Run bundle exec rake stree:write --- lib/syntax_tree/cli.rb | 3 ++- lib/syntax_tree/pattern.rb | 17 ++++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/syntax_tree/cli.rb b/lib/syntax_tree/cli.rb index be0bb793..518b58a6 100644 --- a/lib/syntax_tree/cli.rb +++ b/lib/syntax_tree/cli.rb @@ -193,7 +193,8 @@ def run(item) class Expr < Action def run(item) program = item.handler.parse(item.source) - if Program === program and expressions = program.statements.body and expressions.size == 1 + if Program === program and expressions = program.statements.body and + expressions.size == 1 puts expressions.first.construct_keys else warn("The input to `stree expr` must be a single expression.") diff --git a/lib/syntax_tree/pattern.rb b/lib/syntax_tree/pattern.rb index f56e8a1b..c612c4ea 100644 --- a/lib/syntax_tree/pattern.rb +++ b/lib/syntax_tree/pattern.rb @@ -114,13 +114,16 @@ def compile_node(root) clazz = Object.const_get(root.value) ->(node) { node.is_a?(clazz) } - elsif ConstPathRef === root and VarRef === root.parent and Const === root.parent.value and root.parent.value.value == "SyntaxTree" + elsif ConstPathRef === root and VarRef === root.parent and + Const === root.parent.value and + root.parent.value.value == "SyntaxTree" compile_node(root.constant) elsif DynaSymbol === root and root.parts.empty? symbol = :"" ->(node) { node == symbol } - elsif DynaSymbol === root and parts = root.parts and parts.size == 1 and TStringContent === parts[0] + elsif DynaSymbol === root and parts = root.parts and parts.size == 1 and + TStringContent === parts[0] symbol = parts[0].value.to_sym ->(node) { node == symbol } @@ -129,7 +132,9 @@ def compile_node(root) preprocessed = root.keywords.to_h do |keyword, value| - raise CompilationError, PP.pp(root, +"").chomp unless keyword.is_a?(Label) + unless keyword.is_a?(Label) + raise CompilationError, PP.pp(root, +"").chomp + end [keyword.value.chomp(":").to_sym, compile_node(value)] end @@ -146,13 +151,15 @@ def compile_node(root) else compiled_keywords end - elsif RegexpLiteral === root and parts = root.parts and parts.size == 1 and TStringContent === parts[0] + elsif RegexpLiteral === root and parts = root.parts and + parts.size == 1 and TStringContent === parts[0] regexp = /#{parts[0].value}/ ->(attribute) { regexp.match?(attribute) } elsif StringLiteral === root and root.parts.empty? ->(attribute) { attribute == "" } - elsif StringLiteral === root and parts = root.parts and parts.size == 1 and TStringContent === parts[0] + elsif StringLiteral === root and parts = root.parts and + parts.size == 1 and TStringContent === parts[0] value = parts[0].value ->(attribute) { attribute == value } elsif SymbolLiteral === root From 570a2d134d74024ca17d557e843d5b6720c1e9dd Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Fri, 28 Oct 2022 15:33:54 +0200 Subject: [PATCH 7/7] Do not run idempotency_test.rb on TruffleRuby * It's too slow as it includes all Ruby files from stdlib, gems and more. --- test/idempotency_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/idempotency_test.rb b/test/idempotency_test.rb index 1f560db2..76116572 100644 --- a/test/idempotency_test.rb +++ b/test/idempotency_test.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -return unless ENV["CI"] +return unless ENV["CI"] and RUBY_ENGINE != "truffleruby" require_relative "test_helper" module SyntaxTree