Skip to content

Commit

Permalink
Add the options argument to the macro parse.
Browse files Browse the repository at this point in the history
  • Loading branch information
I3oris committed Jan 20, 2025
1 parent c8c4f34 commit 4d23f0e
Show file tree
Hide file tree
Showing 4 changed files with 22 additions and 12 deletions.
4 changes: 3 additions & 1 deletion spec/parser/parser_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ describe TopDown::Parser do

it "parses syntax" do
parser = TopDown::Spec.syntax_parser
parser.source = "abbc;abbcc;abbccc;abbcccc;abbccc;abbcc;abbc;abbcc;"
parser.source = "abbc;abbcc;abbccc;abbcccc;abbccc;abbcc;abbc;abbcc;aaa;aaaaa;"
parser.spec_parse_syn.should eq({a: 'a', b: "bb", c: "c"})
parser.spec_parse_syn!.should eq({a: 'a', b: "bb", c: "cc"})
parser.spec_parse_syn_with_error!.should eq({a: 'a', b: "bb", c: "ccc"})
Expand All @@ -318,6 +318,8 @@ describe TopDown::Parser do
parser.spec_parse_syn_empty.should eq(Tuple.new)
parser.spec_parse_syn_with_prefix.should eq({a: 'a', b: "bb", c: "c"})
parser.spec_parse_syn_blockless.should eq({'a', "bb", "cc", ';'})
parser.spec_parse_syn_with_options_3.should eq(['a', 'a', 'a'])
parser.spec_parse_syn_with_options_5.should eq(['a', 'a', 'a', 'a', 'a'])
end

it "fails parsing syntax" do
Expand Down
8 changes: 8 additions & 0 deletions spec/parser/parser_spec_helper.cr
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ module TopDown::Spec
def_parse_wrapper :syn_empty { parse(:empty_syntax) }
def_parse_wrapper :syn_with_prefix { parse(:syntax_with_prefix) }
def_parse_wrapper :syn_blockless { parse(:blockless_syntax) }
def_parse_wrapper :syn_with_options_3 { parse(:syntax_with_options, options: {count: 3}) }
def_parse_wrapper :syn_with_options_5 { parse(:syntax_with_options, options: {count: 5}) }

syntax(:syntax) do
a = parse('a')
Expand All @@ -100,6 +102,12 @@ module TopDown::Spec
end

syntax(:blockless_syntax, 'a', "bb", /c+/, ';')

syntax(:syntax_with_options) do
results = (0...options[:count]).map { parse!('a') }
parse!(';')
results
end
end

class SkipParser < ParserBase
Expand Down
8 changes: 4 additions & 4 deletions src/parser/parselets.cr
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ abstract class TopDown::Parser < TopDown::CharReader
class ParseletLiteral
end

private macro parselet(parselet, raises? = false, error = nil, at = nil, with_precedence = nil, left = nil, &block)
private macro parselet(parselet, raises? = false, error = nil, at = nil, with_precedence = nil, left = nil, options = nil, &block)
{% parselet = parselet.expressions[0] if parselet.is_a?(Expressions) && parselet.expressions.size == 1 %}

skip_chars
Expand All @@ -57,7 +57,7 @@ abstract class TopDown::Parser < TopDown::CharReader
parselet_regex({{parselet}}, {{raises?}}, {{error}}, {{at}})

{% elsif parselet.is_a? SymbolLiteral %}
parselet_syntax({{parselet}}, {{raises?}}, {{error}}, {{at}}, {{with_precedence}}, {{left}})
parselet_syntax({{parselet}}, {{raises?}}, {{error}}, {{at}}, {{with_precedence}}, {{left}}, options: {{options}})

{% elsif parselet.is_a? Call && parselet.name == "|" %}
parselet_union([parselet({{parselet.receiver}}), parselet({{parselet.args[0]}})], {{raises?}}, {{error}}, {{at}})
Expand Down Expand Up @@ -176,9 +176,9 @@ abstract class TopDown::Parser < TopDown::CharReader
end
end

private macro parselet_syntax(syntax_name, raises? = false, error = nil, at = nil, with_precedence = nil, left = nil)
private macro parselet_syntax(syntax_name, raises? = false, error = nil, at = nil, with_precedence = nil, left = nil, options = nil)
%begin_location = self.location
%result = parse_{{syntax_name.id}}({{left}}, {{with_precedence || "_precedence_".id}})
%result = parse_{{syntax_name.id}}({{left}}, {{with_precedence || "_precedence_".id}}, {{options.double_splat if options}})
if %result.is_a? Fail
fail {{raises?}}, error_message({{error}} || ->hook_expected_syntax(Char, Symbol), got: peek_char, expected: {{syntax_name}}), at: ({{at || "self.location".id}}) \
ensure self.location = %begin_location
Expand Down
14 changes: 7 additions & 7 deletions src/parser/parser.cr
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ abstract class TopDown::Parser < TopDown::CharReader
# ```
macro syntax(syntax_name, *prefixs, end! = nil, &block)
@[AlwaysInline]
private def parse_{{syntax_name.id}}(_left_, _precedence_)
private def parse_{{syntax_name.id}}(_left_, _precedence_, **options)
_begin_location_ = self.location

handle_fail do
Expand Down Expand Up @@ -283,8 +283,8 @@ abstract class TopDown::Parser < TopDown::CharReader
# *with_precedence* (`NumberLiteral`) changes the `current_precedence`, the given *parselet* will be parsed as if the contained syntax have this precedence.
# Allow to handle multi-precedence for ternary-or-more operator.
#
macro parse(parselet, with_precedence = nil, &block)
parselet({{parselet}}, with_precedence: {{with_precedence}}) {{ block }}
macro parse(parselet, with_precedence = nil, options = nil, &block)
parselet({{parselet}}, with_precedence: {{with_precedence}}, options: {{options}}) {{ block }}
end

# Similar to [`Parser.parse`](#parse(parselet,with_precedence=nil,&block)-macro), but raises `SyntaxError` if the parsing fail.
Expand All @@ -303,8 +303,8 @@ abstract class TopDown::Parser < TopDown::CharReader
# This would raises before the surrounding context could try an other solution.
#
# However, this should generally be used everywhere else, to allow more localized errors.
macro parse!(parselet, error = nil, at = nil, with_precedence = nil, &block)
parselet({{parselet}}, true, {{error}}, {{at}}, {{with_precedence}}) {{ block }}
macro parse!(parselet, error = nil, at = nil, with_precedence = nil, options = nil, &block)
parselet({{parselet}}, true, {{error}}, {{at}}, {{with_precedence}}, options: {{options}}) {{ block }}
end

# Equivalent to `parse(parselet, with_precedence: precedence)`
Expand All @@ -327,10 +327,10 @@ abstract class TopDown::Parser < TopDown::CharReader
# parse parselet
# if succeed, don't move the cursor and return parselet result
# else, don't move the cursor and break a failure
macro peek(parselet, with_precedence = nil, &block)
macro peek(parselet, with_precedence = nil, options = nil, &block)
%location = self.location
handle_fail do
parselet({{parselet}}, with_precedence: {{with_precedence}}) {{ block }}
parselet({{parselet}}, with_precedence: {{with_precedence}}, options: {{options}}) {{ block }}
ensure
self.location = %location
end.tap do |result|
Expand Down

0 comments on commit 4d23f0e

Please sign in to comment.