首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >长效聚乙二醇分析器发生器

长效聚乙二醇分析器发生器
EN

Code Review用户
提问于 2018-03-18 07:17:39
回答 1查看 471关注 0票数 4

我正在努力学习Elixir,所以我决定编写Ruby的Parslet图书馆端口。我想要定义一个DSL,它允许您定义语法,并给出一个字符串可以将其解析为天冬氨酸

我写了些东西,但不太习惯。我想要一些指点。此外,我希望能够将规则定义如下:

代码语言:javascript
运行
复制
rule :test do {
  str("a") >> (str("b") >> repeat(2))
}

其中'2‘是'min_count’,并让它匹配'abbbbb‘。

根据我目前的设计,我不得不这样做:

代码语言:javascript
运行
复制
rule :test do {
  str("a") >> repeat(2, str("b"))
}

下面是我的代码和测试:

代码语言:javascript
运行
复制
defmodule Parslet do
  @moduledoc """
  Documentation for Parslet.
  """

  # Callback invoked by `use`.
  #
  # For now it returns a quoted expression that
  # imports the module itself into the user code.
  @doc false
  defmacro __using__(_opts) do
    quote do
      import Parslet

      # Initialize @tests to an empty list
      @rules []
      @root :undefined

      # Invoke Parslet.__before_compile__/1 before the module is compiled
      @before_compile Parslet
    end
  end

  @doc """
  Defines a test case with the given description.

  ## Examples

      rule :testString do
        str("test")
      end

  """
  defmacro rule(description, do: block) do
    function_name = description
    quote do
      # Prepend the newly defined test to the list of rules
      @rules [unquote(function_name) | @rules]
      def unquote(function_name)(), do: unquote(block)
    end
  end

  defmacro root(rule_name) do
    quote do
      # Prepend the newly defined test to the list of rules
      @root unquote(rule_name)
    end
  end


  # This will be invoked right before the target module is compiled
  # giving us the perfect opportunity to inject the `parse/1` function
  @doc false
  defmacro __before_compile__(_env) do
    quote do
      def parse(document) do
        # IO.puts "Root is defined as #{@root}"
        # Enum.each @rules, fn name ->
        #   IO.puts "Defined rule #{name}"
        # end
        case apply(__MODULE__, @root, []).(document) do
          {:ok, any, ""} -> {:ok , any}
          {:ok, any, rest} -> {:error, "Consumed #{inspect(any)}, but had the following remaining '#{rest}'"}
          error -> error
        end
      end
    end
  end

  def call_aux(fun, aux) do
    fn doc ->
      case fun.(doc) do
        {:ok, match, rest} -> aux.(rest, match)
        other -> other
      end
    end
  end


  # TODO ... checkout ("a" <> rest ) syntax...
  # https://stackoverflow.com/questions/25896762/how-can-pattern-matching-be-done-on-text
  def str(text), do: str(&Parslet.identity/1, text)
  def match(regex_s), do: match(&Parslet.identity/1, regex_s)
  def repeat(fun, min_count), do: repeat(&Parslet.identity/1, fun, min_count)


  def str(fun, text), do: call_aux( fun,
      fn (doc, matched) -> str_aux(text, doc, matched) end )
  def match(fun, regex_s), do: call_aux( fun,
      fn (doc, matched) -> match_aux(regex_s, doc, matched) end )
  def repeat(prev, fun, min_count), do: call_aux( prev,
      fn (doc, matched) -> repeat_aux(fun, min_count, doc, matched) end )


  defp str_aux(text, doc, matched) do
      tlen = String.length(text)
      if String.starts_with?(doc, text) do
        {:ok, matched <> text,  String.slice(doc, tlen..-1) }
      else
        {:error, "'#{doc}' does not match string '#{text}'"}
      end
  end

  defp match_aux(regex_s, doc, matched) do
    regex = ~r{^#{regex_s}}
    case Regex.run(regex, doc) do
      nil -> {:error, "'#{doc}' does not match regex '#{regex_s}'"}
      [match | _] -> {:ok, matched <> match, String.slice(doc, String.length(match)..-1)}
    end
  end

  defp repeat_aux(fun, 0, doc, matched) do
    case fun.(doc) do
      {:ok, match, rest} -> repeat_aux(fun, 0, rest, matched <> match)
      _ -> {:ok, matched, doc}
    end
  end

  defp repeat_aux(fun, count, doc, matched) do
    case fun.(doc) do
      {:ok, match, rest} -> repeat_aux(fun, count - 1, rest, matched <> match)
      other -> other
    end
  end

  def identity(doc) do
    {:ok, "", doc}
  end

end

测试

代码语言:javascript
运行
复制
defmodule ParsletTest do
  use ExUnit.Case
  doctest Parslet

  defmodule ParsletExample do
    use Parslet

    rule :test_string do
      str("test")
    end
    root :test_string
  end

  test "str matches whole string" do
    assert ParsletExample.parse("test") == {:ok, "test"}
  end
  test "str doesnt match different strings" do
    assert ParsletExample.parse("tost") == {:error, "'tost' does not match string 'test'"}
  end
  test "parse reports error if not all the input document is consumed" do
    assert ParsletExample.parse("test_the_best") ==
      {:error, "Consumed \"test\", but had the following remaining '_the_best'"}
  end

  defmodule ParsletExample2 do
    use Parslet

    rule :test_regex do
      match("123")
    end

    # calling another rule should just work. :)
    rule :document do
      test_regex()
    end

    root :document
  end

  test "[123]" do
    assert ParsletExample2.parse("123") == {:ok, "123"}
    assert ParsletExample2.parse("w123") == {:error, "'w123' does not match regex '123'"}
    assert ParsletExample2.parse("234") == {:error, "'234' does not match regex '123'"}
    assert ParsletExample2.parse("123the_rest") == {:error, "Consumed \"123\", but had the following remaining 'the_rest'"}
  end


  defmodule ParsletExample3 do
    use Parslet

    rule :a do
      repeat(str("a"), 1)
    end

    root :a
  end

  test "a+" do
    assert ParsletExample3.parse("a") == {:ok, "a"}
    assert ParsletExample3.parse("aaaaaa") == {:ok, "aaaaaa"}
  end

  defmodule ParsletExample4 do
    use Parslet

    rule :a do
      str("a") |> str("b")
    end

    root :a
  end

  test "a > b = ab" do
    assert ParsletExample4.parse("ab") == {:ok, "ab"}
  end

  defmodule ParsletExample5 do
    use Parslet

    rule :a do
      repeat(str("a") |>  str("b") , 1)
    end

    root :a
  end

  test "(a > b)+" do
    assert ParsletExample5.parse("ababab") == {:ok, "ababab"}
  end

   defmodule ParsletExample6 do
    use Parslet

    rule :a do
      str("a") |> repeat(str("b"), 1)
    end

    root :a
  end

  test "a > b+" do
    assert ParsletExample6.parse("abbbbb") == {:ok, "abbbbb"}
  end

end
EN

回答 1

Code Review用户

回答已采纳

发布于 2018-05-03 13:04:22

最新的版本是在https://github.com/NigelThorne/ElixirParslet/blob/master/test/json[医]解析器_test.exs

我更改了dsl以创建一个数据结构,然后在解析器运行时对其进行解释。这让我以传递函数的方式将DSL与行为脱钩。

下面是用我的语言编写的JSON解析器。

代码语言:javascript
运行
复制
defmodule JSONParser do
  use Parslet
    rule :value do
      one_of ([
        string(),
        number(),
        object(),
        array(),
        boolean(),
        null(),
        ])
    end

    rule :null do
      as(:null, str("null"))
    end

    rule :boolean do
      as(:boolean, one_of ([
        str("true"),
        str("false"),
      ]))
    end

    rule :sp_ do
      repeat(match("[\s\r\n]"), 0)
    end

    rule :string do
        (str("\"")
          |>  as(:string,
                repeat(
                as(:char, one_of( [
                    (absent?(str("\"")) |> absent?(str("\\")) |> match(".")),
                    (str("\\")
                        |>  as(:escaped, one_of(
                            [
                                match("[\"\\/bfnrt]"),
                                (str("u")
                                    |> match("[a-fA-F0-9]")
                                    |> match("[a-fA-F0-9]")
                                    |> match("[a-fA-F0-9]")
                                    |> match("[a-fA-F0-9]"))
                            ]))
                    )
                ])),0)
            )
          |> str("\""))

    end

    rule :digit, do: match("[0-9]")

    rule :number do
        as(:number,
            as(:integer, maybe(str("-")) |>
              one_of([
                  str("0"),
                  (match("[1-9]") |> repeat( digit(), 0 ))
              ])) |>
            as(:decimal,
                maybe(str(".") |> repeat( digit(), 1 ))
              ) |>
            as(:exponent,
              maybe(
                one_of( [str("e"), str("E")] ) |>
                    maybe( one_of( [ str("+"), str("-") ] )) |>
                        repeat( digit(), 1)
              )
            )
        )
    end

    rule :key_value_pair do
        as(:pair, as(:key, string()) |> sp_() |> str(":") |> sp_() |> as(:value, value()))
    end

    rule :object do
        as(:object, str("{") |> sp_() |>
         maybe(
             key_value_pair() |>  repeat(  sp_() |> str(",") |> sp_() |> key_value_pair(), 0)
             ) |> sp_() |>
        str("}"))
    end

    rule :array do
      as(:array, str("[") |> sp_() |>
         maybe(
             value() |>  repeat( sp_() |> str(",") |> sp_() |> value(), 0)
             ) |> sp_() |>
      str("]"))
    end

    rule :document do
      sp_() |> value |> sp_()
    end

    root :document

end

defmodule JSONTransformer do
  def transform(%{escaped: val}) do
    {result, _} = Code.eval_string("\"\\#{val}\"")
    result
  end

  def transform(%{string: val}) when is_list(val) do
    List.to_string(val)
  end
  def transform(%{string: val}), do: val
  def transform(%{char: val}), do: val
  def transform(%{array: val}), do: val
  def transform(%{null: "null"}), do: :null  #replace null with :null
  def transform(%{boolean: val}), do: val == "true"

  def transform(%{number: %{integer: val, decimal: "", exponent: ""}}) do
     {intVal, ""} = Integer.parse("#{val}")
     intVal
  end

  def transform(%{number: %{integer: val, decimal: dec, exponent: ex}}) do
    {intVal, ""} = Float.parse("#{val}#{dec}#{ex}")
    intVal
  end

  def transform(%{object: pairs}) when is_list(pairs) do
    for %{pair: %{key: k, value: v}} <- pairs, into: %{}, do: {k,v}
  end

  def transform(%{object: %{pair: %{key: k, value: v}}}) do
    %{k => v}
  end

  #default to leaving it untouched
  def transform(any), do: any

end

def parseJSON(document) do
  {:ok, parsed} = JSONParser.parse(document)
  Transformer.transform_with(&JSONTransformer.transform/1, parsed)
end

所以打电话

代码语言:javascript
运行
复制
parseJSON(~S({"bob":{"jane":234},"fre\r\n\t\u26C4ddy":"a"})) ==
              %{"bob" => %{"jane" => 234},"fre\r\n\t⛄ddy" => "a"}
票数 0
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/189854

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档