Created
September 14, 2021 05:13
-
-
Save bluzky/704c5f9c18fec2a5f3ebfd69cc9bd11a to your computer and use it in GitHub Desktop.
Parse without using defparsec
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| defmodule Solid.Parser.Base do | |
| defmacro __using__(opts) do | |
| custom_tags = Keyword.get(opts, :custom_tags, []) | |
| custom_tag_modules = Enum.filter(custom_tags, &is_tuple(&1)) | |
| custom_tag_names = custom_tags -- custom_tag_modules | |
| quote location: :keep do | |
| import NimbleParsec | |
| alias Solid.Parser.{Literal, Variable, Argument} | |
| defp when_join(whens) do | |
| for {:when, [value: value, result: result]} <- whens, into: %{} do | |
| {value, result} | |
| end | |
| end | |
| space = Literal.whitespace(min: 0) | |
| opening_object = string("{{") | |
| opening_wc_object = string("{{-") | |
| closing_object = string("}}") | |
| closing_wc_object = string("-}}") | |
| opening_tag = | |
| string("{%") | |
| |> concat(optional(string("-"))) | |
| |> concat(space) | |
| opening_wc_tag = string("{%-") | |
| closing_wc_tag = string("-%}") | |
| closing_wc_tag_and_whitespace = | |
| closing_wc_tag | |
| |> concat(space) | |
| |> ignore() | |
| closing_tag = | |
| space | |
| |> concat(choice([closing_wc_tag_and_whitespace, string("%}")])) | |
| filter_name = | |
| ascii_string([?a..?z, ?A..?Z], 1) | |
| |> concat(ascii_string([?a..?z, ?A..?Z, ?_], min: 0)) | |
| |> reduce({Enum, :join, []}) | |
| filter = | |
| ignore(space) | |
| |> ignore(string("|")) | |
| |> ignore(space) | |
| |> concat(filter_name) | |
| |> tag( | |
| optional(ignore(string(":")) |> ignore(space) |> concat(Argument.arguments())), | |
| :arguments | |
| ) | |
| |> tag(:filter) | |
| closing_wc_object_and_whitespace = | |
| closing_wc_object | |
| |> concat(Literal.whitespace(min: 0)) | |
| |> ignore() | |
| object = | |
| ignore(opening_object) | |
| # At this stage whitespace control has been handled as part of the liquid_entry | |
| |> ignore(optional(string("-"))) | |
| |> ignore(space) | |
| |> lookahead_not(closing_object) | |
| |> tag(Argument.argument(), :argument) | |
| |> optional(tag(repeat(filter), :filters)) | |
| |> ignore(space) | |
| |> ignore(choice([closing_wc_object_and_whitespace, closing_object])) | |
| |> tag(:object) | |
| comment = string("comment") | |
| end_comment = string("endcomment") | |
| comment_tag = | |
| ignore(opening_tag) | |
| |> ignore(space) | |
| |> ignore(comment) | |
| |> ignore(space) | |
| |> ignore(closing_tag) | |
| |> ignore(parsec(:liquid_entry)) | |
| |> ignore(opening_tag) | |
| |> ignore(space) | |
| |> ignore(end_comment) | |
| |> ignore(space) | |
| |> ignore(closing_tag) | |
| increment = | |
| string("increment") | |
| |> replace({1, 0}) | |
| decrement = | |
| string("decrement") | |
| |> replace({-1, -1}) | |
| counter_tag = | |
| ignore(opening_tag) | |
| |> ignore(space) | |
| |> concat(choice([increment, decrement])) | |
| |> ignore(space) | |
| |> concat(Variable.field()) | |
| |> ignore(space) | |
| |> ignore(closing_tag) | |
| |> tag(:counter_exp) | |
| case_tag = | |
| ignore(opening_tag) | |
| |> ignore(space) | |
| |> ignore(string("case")) | |
| |> ignore(space) | |
| |> concat(Argument.argument()) | |
| |> ignore(space) | |
| |> ignore(closing_tag) | |
| when_tag = | |
| ignore(opening_tag) | |
| |> ignore(space) | |
| |> ignore(string("when")) | |
| |> ignore(space) | |
| |> concat(Literal.value()) | |
| |> ignore(space) | |
| |> ignore(closing_tag) | |
| |> tag(parsec(:liquid_entry), :result) | |
| |> tag(:when) | |
| else_tag = | |
| ignore(opening_tag) | |
| |> ignore(space) | |
| |> ignore(string("else")) | |
| |> ignore(space) | |
| |> ignore(closing_tag) | |
| |> tag(parsec(:liquid_entry), :result) | |
| cond_case_tag = | |
| tag(case_tag, :case_exp) | |
| # FIXME | |
| |> ignore(parsec(:liquid_entry)) | |
| |> unwrap_and_tag(reduce(times(when_tag, min: 1), :when_join), :whens) | |
| |> optional(tag(else_tag, :else_exp)) | |
| |> ignore(opening_tag) | |
| |> ignore(space) | |
| |> ignore(string("endcase")) | |
| |> ignore(space) | |
| |> ignore(closing_tag) | |
| operator = | |
| choice([ | |
| string("=="), | |
| string("!="), | |
| string(">="), | |
| string("<="), | |
| string(">"), | |
| string("<"), | |
| string("contains") | |
| ]) | |
| |> map({:erlang, :binary_to_atom, [:utf8]}) | |
| argument_filter = | |
| tag(Argument.argument(), :argument) | |
| |> tag( | |
| repeat( | |
| lookahead_not(choice([operator, string("and"), string("or")])) | |
| |> concat(filter) | |
| ), | |
| :filters | |
| ) | |
| boolean_operation = | |
| tag(argument_filter, :arg1) | |
| |> ignore(space) | |
| |> tag(operator, :op) | |
| |> ignore(space) | |
| |> tag(argument_filter, :arg2) | |
| |> wrap() | |
| expression = | |
| ignore(space) | |
| |> choice([boolean_operation, wrap(argument_filter)]) | |
| |> ignore(space) | |
| bool_and = | |
| string("and") | |
| |> replace(:bool_and) | |
| bool_or = | |
| string("or") | |
| |> replace(:bool_or) | |
| boolean_expression = | |
| expression | |
| |> repeat(choice([bool_and, bool_or]) |> concat(expression)) | |
| if_tag = | |
| ignore(opening_tag) | |
| |> ignore(space) | |
| |> ignore(string("if")) | |
| |> tag(boolean_expression, :expression) | |
| |> ignore(closing_tag) | |
| |> tag(parsec(:liquid_entry), :result) | |
| elsif_tag = | |
| ignore(opening_tag) | |
| |> ignore(space) | |
| |> ignore(string("elsif")) | |
| |> tag(boolean_expression, :expression) | |
| |> ignore(closing_tag) | |
| |> tag(parsec(:liquid_entry), :result) | |
| |> tag(:elsif_exp) | |
| unless_tag = | |
| ignore(opening_tag) | |
| |> ignore(space) | |
| |> ignore(string("unless")) | |
| |> tag(boolean_expression, :expression) | |
| |> ignore(space) | |
| |> ignore(closing_tag) | |
| |> tag(parsec(:liquid_entry), :result) | |
| cond_if_tag = | |
| tag(if_tag, :if_exp) | |
| |> tag(times(elsif_tag, min: 0), :elsif_exps) | |
| |> optional(tag(else_tag, :else_exp)) | |
| |> ignore(opening_tag) | |
| |> ignore(space) | |
| |> ignore(string("endif")) | |
| |> ignore(space) | |
| |> ignore(closing_tag) | |
| cond_unless_tag = | |
| tag(unless_tag, :unless_exp) | |
| |> tag(times(elsif_tag, min: 0), :elsif_exps) | |
| |> optional(tag(else_tag, :else_exp)) | |
| |> ignore(opening_tag) | |
| |> ignore(space) | |
| |> ignore(string("endunless")) | |
| |> ignore(space) | |
| |> ignore(closing_tag) | |
| assign_tag = | |
| ignore(opening_tag) | |
| |> ignore(string("assign")) | |
| |> ignore(space) | |
| |> concat(Variable.field()) | |
| |> ignore(space) | |
| |> ignore(string("=")) | |
| |> ignore(space) | |
| |> tag(Argument.argument(), :argument) | |
| |> optional(tag(repeat(filter), :filters)) | |
| |> ignore(closing_tag) | |
| |> tag(:assign_exp) | |
| range = | |
| ignore(string("(")) | |
| |> unwrap_and_tag(choice([integer(min: 1), Variable.field()]), :first) | |
| |> ignore(string("..")) | |
| |> unwrap_and_tag(choice([integer(min: 1), Variable.field()]), :last) | |
| |> ignore(string(")")) | |
| |> tag(:range) | |
| limit = | |
| ignore(string("limit")) | |
| |> ignore(space) | |
| |> ignore(string(":")) | |
| |> ignore(space) | |
| |> unwrap_and_tag(integer(min: 1), :limit) | |
| |> ignore(space) | |
| offset = | |
| ignore(string("offset")) | |
| |> ignore(space) | |
| |> ignore(string(":")) | |
| |> ignore(space) | |
| |> unwrap_and_tag(integer(min: 1), :offset) | |
| |> ignore(space) | |
| reversed = | |
| string("reversed") | |
| |> replace({:reversed, 0}) | |
| |> ignore(space) | |
| for_parameters = | |
| repeat(choice([limit, offset, reversed])) | |
| |> reduce({Enum, :into, [%{}]}) | |
| for_tag = | |
| ignore(opening_tag) | |
| |> ignore(space) | |
| |> ignore(string("for")) | |
| |> ignore(space) | |
| |> concat(Argument.argument()) | |
| |> ignore(space) | |
| |> ignore(string("in")) | |
| |> ignore(space) | |
| |> tag(choice([Variable.field(), range]), :enumerable) | |
| |> ignore(space) | |
| |> unwrap_and_tag(for_parameters, :parameters) | |
| |> ignore(space) | |
| |> ignore(closing_tag) | |
| |> tag(parsec(:liquid_entry), :result) | |
| |> optional(tag(else_tag, :else_exp)) | |
| |> ignore(opening_tag) | |
| |> ignore(string("endfor")) | |
| |> ignore(space) | |
| |> ignore(closing_tag) | |
| |> tag(:for_exp) | |
| capture_tag = | |
| ignore(opening_tag) | |
| |> ignore(string("capture")) | |
| |> ignore(space) | |
| |> concat(Variable.field()) | |
| |> ignore(closing_tag) | |
| |> tag(parsec(:liquid_entry), :result) | |
| |> ignore(opening_tag) | |
| |> ignore(string("endcapture")) | |
| |> ignore(closing_tag) | |
| |> tag(:capture_exp) | |
| break_tag = | |
| ignore(opening_tag) | |
| |> ignore(string("break")) | |
| |> ignore(closing_tag) | |
| |> tag(:break_exp) | |
| continue_tag = | |
| ignore(opening_tag) | |
| |> ignore(string("continue")) | |
| |> ignore(closing_tag) | |
| |> tag(:continue_exp) | |
| end_raw_tag = | |
| opening_tag | |
| |> ignore(string("endraw")) | |
| |> ignore(closing_tag) | |
| raw_tag = | |
| ignore(opening_tag) | |
| |> ignore(string("raw")) | |
| |> ignore(closing_tag) | |
| |> repeat(lookahead_not(ignore(end_raw_tag)) |> utf8_char([])) | |
| |> ignore(end_raw_tag) | |
| |> tag(:raw_exp) | |
| cycle_tag = | |
| ignore(opening_tag) | |
| |> ignore(string("cycle")) | |
| |> ignore(space) | |
| |> optional( | |
| Literal.double_quoted_string() | |
| |> ignore(string(":")) | |
| |> ignore(space) | |
| |> unwrap_and_tag(:name) | |
| ) | |
| |> concat( | |
| Literal.double_quoted_string() | |
| |> repeat( | |
| ignore(space) | |
| |> ignore(string(",")) | |
| |> ignore(space) | |
| |> concat(Literal.double_quoted_string()) | |
| ) | |
| |> tag(:values) | |
| ) | |
| |> ignore(closing_tag) | |
| |> tag(:cycle_exp) | |
| render_tag = | |
| ignore(opening_tag) | |
| |> ignore(space) | |
| |> ignore(string("render")) | |
| |> ignore(space) | |
| |> tag(Argument.argument(), :template) | |
| |> tag( | |
| optional( | |
| ignore(string(",")) | |
| |> ignore(space) | |
| |> concat(Argument.named_arguments()) | |
| ), | |
| :arguments | |
| ) | |
| |> ignore(space) | |
| |> ignore(closing_tag) | |
| |> tag(:render_exp) | |
| base_tags = [ | |
| counter_tag, | |
| comment_tag, | |
| assign_tag, | |
| cond_if_tag, | |
| cond_unless_tag, | |
| cond_case_tag, | |
| for_tag, | |
| capture_tag, | |
| break_tag, | |
| continue_tag, | |
| raw_tag, | |
| cycle_tag, | |
| render_tag | |
| ] | |
| # We must try to parse longer strings first so if | |
| # foo and foobar are custom tags foobar must be tried to be parsed first | |
| custom_tag_names = | |
| unquote(custom_tag_names) | |
| |> Enum.uniq() | |
| |> Enum.sort_by(&String.length/1, &Kernel.>=/2) | |
| |> Enum.map(fn custom_tag -> string(custom_tag) end) | |
| custom_tags = | |
| if custom_tag_names != [] do | |
| custom_tag = | |
| ignore(opening_tag) | |
| |> concat(choice(custom_tag_names)) | |
| |> ignore(space) | |
| |> tag(optional(Argument.arguments()), :arguments) | |
| |> ignore(space) | |
| |> ignore(closing_tag) | |
| |> tag(:custom_tag) | |
| [custom_tag] | |
| end | |
| all_tags = base_tags ++ (custom_tags || []) | |
| custom_tags = | |
| if unquote(custom_tag_modules) != [] do | |
| unquote(custom_tag_modules) | |
| |> Enum.uniq() | |
| |> Enum.reduce([], fn {tag_name, module}, acc -> | |
| [tag(module.spec(), tag_name) | acc] | |
| end) | |
| |> Enum.reverse() | |
| end | |
| all_tags = all_tags ++ (custom_tags || []) | |
| tags = | |
| choice(all_tags) | |
| |> tag(:tag) | |
| text = | |
| lookahead_not( | |
| choice([ | |
| Literal.whitespace(min: 1) | |
| |> concat(opening_wc_object), | |
| Literal.whitespace(min: 1) | |
| |> concat(opening_wc_tag), | |
| opening_object, | |
| opening_tag | |
| ]) | |
| ) | |
| |> utf8_string([], 1) | |
| |> times(min: 1) | |
| |> reduce({Enum, :join, []}) | |
| |> tag(:text) | |
| leading_whitespace = | |
| Literal.whitespace(min: 1) | |
| |> lookahead(choice([opening_wc_object, opening_wc_tag])) | |
| |> ignore() | |
| defcombinatorp(:liquid_entry, repeat(choice([object, tags, text, leading_whitespace]))) | |
| defparsec(:parse, parsec(:liquid_entry) |> eos()) | |
| end | |
| end | |
| end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment