diff options
| author | Matthew Hall <hallmatthew314@gmail.com> | 2023-03-17 22:50:14 +1300 |
|---|---|---|
| committer | Matthew Hall <hallmatthew314@gmail.com> | 2023-03-17 22:50:14 +1300 |
| commit | 982d239252b54297ff3558894038871f8d3a4175 (patch) | |
| tree | 41ab6073fd601caf2da0bf87c4cd8226b87b7126 /src | |
| parent | f023ca56dbf9372464afe0060270fcef85271db0 (diff) | |
Please work this time
Diffstat (limited to 'src')
| -rw-r--r-- | src/parcom.cr | 83 | ||||
| -rw-r--r-- | src/parcom/basic.cr | 65 | ||||
| -rw-r--r-- | src/parcom/parser.cr | 137 |
3 files changed, 137 insertions, 148 deletions
diff --git a/src/parcom.cr b/src/parcom.cr index ffa5654..8387752 100644 --- a/src/parcom.cr +++ b/src/parcom.cr @@ -77,88 +77,5 @@ module Parcom map(block) end end - - # A parser defines a process of extracting some sort of data - # from a stream of tokens. - # `Parser` objects wrap a function/block that accepts a `Tokens` object, - # and either returns a `Result`, or raises a `ParserFail`. - # This struct also defines a handful of methods for modifying a parser's - # behavior. - # - # The function that is wrapped by a parser should: - # 1. Have the type `Tokens(T) -> Result(T, U)` - # 1. Indicate failure by raising a `ParserFail` - # 1. Not introduce side-effects - # - # Instead of inheriting from `Parser`, custom parsers should be - # defined using a method that generates the parser directly: - # ``` - # struct Foo(T, U) < Parser(T, Array(U)) - # def parse(tokens) - # # This will cause headaches - # end - # end - # - # # Do this instead: - # def foo(t : T.class, u : U.class, p : Parser(T, U)) : Parser(T, U) forall T, U - # Parser(T, U).new("Foo") { |tokens| "your code here" } - # end - # ``` - struct Parser(T, U) - getter name - - def initialize(@name : String, @f : Tokens(T) -> Result(T, U)) - end - - def initialize(@name : String, &block : Tokens(T) -> Result(T, U)) - @f = block - end - - # Changes the `name` property and returns `self`. - # This should be used to specify a custom name: - # ``` - # a = Basic.token(Char, 'a').named("letter a") - # ``` - def named(name : String) : self - @name = name - self - end - - def parse(tokens : Tokens(T)) : Result(T, U) - @f.call(tokens) - end - - def parse?(tokens : Tokens(T)) : Result(T, U)? - parse(tokens) - rescue - nil - end - - def assert(f : U -> Bool) : Parser(T, U) - p = self - Parser.new("#{p.name} (assertion)") do |tokens| - result = p.parse(tokens) - unless f.call(r.value) - raise ParserFail.new("Assertion failed for value #{r.value}") - end - result - end - end - - def assert(&block : U -> Bool) : Parser(T, U) - assert(block) - end - - def map(f : U -> T) : Parser(T, V) forall V - p = self - Parser(T, V).new("#{p.name} (mapped)") do |tokens| - p.parse(tokens).map(f) - end - end - - def map(&block : U -> T) : Parser(T, V) forall V - map(block) - end - end end diff --git a/src/parcom/basic.cr b/src/parcom/basic.cr deleted file mode 100644 index 4f01021..0000000 --- a/src/parcom/basic.cr +++ /dev/null @@ -1,65 +0,0 @@ -require "../parcom.cr" - -module Parcom - module Basic(T, U) - # A collection of building-block parsers that can be combined into more - # powerful parsers. If you can't find a parser here that does what you're - # looking for, it may be defined as a method of the `Parser` struct itself. - extend self - - # Creates a parser that always succeeds with the given value. - # This parser consumes no input. - def pure(value : U) : Parser(T, U) forall T, U - Parser(T, U).new("Pure #{value}") { |tokens| Result.new(tokens, value) } - end - - # Creates a parser that always fails. - def flunk : Parser(T, U) forall T, U - Parser(T, U).new("Flunk") { |_| raise ParserFail.new("Flunked parser") } - end - - # Creates a parser that returns the first token in the input stream. - # Fails if the input stream is empty. - # Analagous to a `.` in a regular expression. - def any_token : Parser(T, T) forall T - Parser(T, T).new("Any Token") do |tokens| - if tokens.empty? - raise ParserFail.new("Expected a token, got EOF") - else - Result.new(tokens[1..], tokens[0]) - end - end - end - - # Creates a parser that succeeds with `nil` if the input stream is empty. - # Fails if the input stream is not empty. - # Analagous to a `$` in a regular expression. - def eof : Parser(T, Nil) forall T - Parser(T, Nil).new("EOF") do |tokens| - if tokens.empty? - Result.new(tokens, nil) - else - raise ParserFail.new("Expected EOF, got a token") - end - end - end - - # Creates a parser that parses the first token in the input stream - # if that token satisfies a given predicate. - # Fails if the input stream is empty or if the predicate fails. - # To test a predicate against any parser result, see `Parser#assert`. - # TODO: the type-checker hates me - def satisfy(f : T -> Bool) : Parser(T, T) forall T - #p = any_token(T).assert(f).named("Satisfy") - Parser(T, T).new("Satisfy") do |tokens| - Basic.any_token.assert(f).parse(tokens) - end - end - - # :ditto: - def satisfy(&block : T -> Bool) : Parser(T, T) forall T - satisfy(T, block) - end - end -end - diff --git a/src/parcom/parser.cr b/src/parcom/parser.cr new file mode 100644 index 0000000..adf0bba --- /dev/null +++ b/src/parcom/parser.cr @@ -0,0 +1,137 @@ +require "../parcom.cr" + +module Parcom + # A parser defines a process of extracting some sort of data + # from a stream of tokens. + # `Parser` objects wrap a function/block that accepts a `Tokens` object, + # and either returns a `Result`, or raises a `ParserFail`. + # This struct also defines a handful of methods for modifying a parser's + # behavior. + # + # The function that is wrapped by a parser should: + # 1. Have the type `Tokens(T) -> Result(T, U)` + # 1. Indicate failure by raising a `ParserFail` + # 1. Not introduce side-effects + # + # Instead of inheriting from `Parser`, custom parsers should be + # defined using a method that generates the parser directly: + # ``` + # struct Foo(T, U) < Parser(T, Array(U)) + # def parse(tokens) + # # This will cause headaches + # end + # end + # + # # Do this instead: + # def foo(t : T.class, u : U.class, p : Parser(T, U)) : Parser(T, U) forall T, U + # Parser(T, U).new("Foo") { |tokens| "your code here" } + # end + # ``` + struct Parser(T, U) + getter name + + # Creates a parser that always succeeds with the given value. + # This parser consumes no input. + def self.pure(value : U) : Parser(T, U) + Parser(T, U).new("Pure #{value}") { |tokens| Result.new(tokens, value) } + end + + # Creates a parser that always fails. + def self.flunk : Parser(T, U) + Parser(T, U).new("Flunk") { |_| raise ParserFail.new("Flunked parser") } + end + + # Creates a parser that returns the first token in the input stream. + # Fails if the input stream is empty. + # Analagous to a `.` in a regular expression. + def self.any_token : Parser(T, T) forall T + Parser(T, T).new("Any Token") do |tokens| + if tokens.empty? + raise ParserFail.new("Expected a token, got EOF") + else + Result.new(tokens[1..], tokens[0]) + end + end + end + + # Creates a parser that succeeds with `nil` if the input stream is empty. + # Fails if the input stream is not empty. + # Analagous to a `$` in a regular expression. + def self.eof : Parser(T, Nil) + Parser(T, Nil).new("EOF") do |tokens| + if tokens.empty? + Result.new(tokens, nil) + else + raise ParserFail.new("Expected EOF, got a token") + end + end + end + + # Creates a parser that parses the first token in the input stream + # if that token satisfies a given predicate. + # Fails if the input stream is empty or if the predicate fails. + # To test a predicate against any parser result, see `Parser#assert`. + def self.satisfy(f : T -> Bool) : Parser(T, T) + Parser(T, T).any_token.assert(f).named("Satisfy") + end + + # :ditto: + def self.satisfy(&block : T -> Bool) : Parser(T, T) + satisfy(block) + end + + def initialize(@name : String, @f : Tokens(T) -> Result(T, U)) + end + + def initialize(@name : String, &block : Tokens(T) -> Result(T, U)) + @f = block + end + + # Changes the `name` property and returns `self`. + # This should be used to specify a custom name: + # ``` + # a = Basic.token(Char, 'a').named("letter a") + # ``` + def named(name : String) : self + @name = name + self + end + + def parse(tokens : Tokens(T)) : Result(T, U) + @f.call(tokens) + end + + def parse?(tokens : Tokens(T)) : Result(T, U)? + parse(tokens) + rescue + nil + end + + def assert(f : U -> Bool) : Parser(T, U) + p = self + Parser(T, U).new("#{p.name} (assertion)") do |tokens| + result = p.parse(tokens) + unless f.call(result.value) + raise ParserFail.new("Assertion failed for value #{result.value}") + end + result + end + end + + def assert(&block : U -> Bool) : Parser(T, U) + assert(block) + end + + def map(f : U -> T) : Parser(T, V) forall V + p = self + Parser(T, V).new("#{p.name} (mapped)") do |tokens| + p.parse(tokens).map(f) + end + end + + def map(&block : U -> T) : Parser(T, V) forall V + map(block) + end + end +end + |
