Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions lib/syntax_tree/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
63 changes: 31 additions & 32 deletions lib/syntax_tree/pattern.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
->(node) { node == symbol }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to add a test to validate this change but couldn't find a way to trigger this case.
I tried things like:

    def test_search_string_simple
      results = search(%{"#{'abc'}"}, "StringLiteral[parts: [TStringContent[value: 'abc']]]")

      assert_equal 1, results.length
    end

    def test_search_symbol_simple
      puts %s{:"#{'abc'}"}
      puts ':"#{\'abc\'}"'
      results = search(':"#{\'abc\'}"', ':abc')
      results = search(':"#{\'abc\'}"', ':"#{\'abc\'}"')
      results = search(%{"#{'abc'}"}, "DynaSymbol[parts: [TStringContent[value: 'abc']]]")

      assert_equal 1, results.length
    end

But none seem to match this specific condition.

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

Expand All @@ -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
2 changes: 2 additions & 0 deletions test/cli_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 =
Expand Down
12 changes: 4 additions & 8 deletions test/location_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
7 changes: 3 additions & 4 deletions test/node_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 13 additions & 9 deletions test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
require "pp"
require "minitest/autorun"

SUPPORTS_PATTERN_MATCHING = RUBY_ENGINE != "truffleruby"

module SyntaxTree
module Assertions
class Recorder
Expand Down Expand Up @@ -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)
Expand Down