aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMatthew Hall <hallmatthew314@gmail.com>2023-03-17 22:50:14 +1300
committerMatthew Hall <hallmatthew314@gmail.com>2023-03-17 22:50:14 +1300
commit982d239252b54297ff3558894038871f8d3a4175 (patch)
tree41ab6073fd601caf2da0bf87c4cd8226b87b7126 /src
parentf023ca56dbf9372464afe0060270fcef85271db0 (diff)
Please work this time
Diffstat (limited to 'src')
-rw-r--r--src/parcom.cr83
-rw-r--r--src/parcom/basic.cr65
-rw-r--r--src/parcom/parser.cr137
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
+