aboutsummaryrefslogtreecommitdiff
path: root/src/parcom.cr
diff options
context:
space:
mode:
authorMatthew Hall <hallmatthew314@gmail.com>2023-03-16 23:11:23 +1300
committerMatthew Hall <hallmatthew314@gmail.com>2023-03-16 23:11:23 +1300
commitf023ca56dbf9372464afe0060270fcef85271db0 (patch)
treeb045bebfbb8ac7e6208e5703cb0f53808b14fe89 /src/parcom.cr
parentb274828831fec26cd8b3089ffef14cb96ce2de2f (diff)
I'm too tired for this
Diffstat (limited to 'src/parcom.cr')
-rw-r--r--src/parcom.cr56
1 files changed, 53 insertions, 3 deletions
diff --git a/src/parcom.cr b/src/parcom.cr
index d12b011..ffa5654 100644
--- a/src/parcom.cr
+++ b/src/parcom.cr
@@ -68,9 +68,43 @@ module Parcom
def initialize(@tokens : Tokens(T), @value : U)
end
+
+ def map(f : U -> V) : Result(T, V) forall V
+ Result.new(@tokens, f.call(@value))
+ end
+
+ def map(&block : U -> V) : Result(T, V) forall V
+ map(block)
+ end
end
- class Parser(T, U)
+ # 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))
@@ -80,6 +114,11 @@ module Parcom
@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
@@ -89,7 +128,7 @@ module Parcom
@f.call(tokens)
end
- def parse(tokens : Tokens(T)) : Result(T, U)?
+ def parse?(tokens : Tokens(T)) : Result(T, U)?
parse(tokens)
rescue
nil
@@ -97,7 +136,7 @@ module Parcom
def assert(f : U -> Bool) : Parser(T, U)
p = self
- Parser.new("#{p.name} assertion") do |tokens|
+ 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}")
@@ -109,6 +148,17 @@ module Parcom
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