aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md116
1 files changed, 115 insertions, 1 deletions
diff --git a/README.md b/README.md
index 520fed5..243c3de 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,118 @@
# parcom
-A simple parser combinator library for with a dumb name.
+A simple parser combinator library with a dumb name.
+
+## WARNING
+
+This library is a work in progress.
+Any version of this library <1.0.0 should not be used in production environments.
+The library is still growing and breaking changes may occur at any time.
+
+## Description
+
+Parcom is a Crystal library the provides parser combinator functionality.
+
+## Prerequisites
+
+* Git
+
+## Installation
+
+Add the following dependency to your project's `shard.yml` file:
+```
+dependencies:
+ parcom:
+ git: "https://git.matthewhall.xyz/parcom"
+ version: "0.3.0"
+```
+
+Then, run
+```
+shards install
+```
+
+## General usage
+
+Parcom parsers work by creating parser objects, and then calling their `#parse` method with the given input.
+As this library use parser combinators, complex parser objects should be made by combining simple parsers together.
+
+## Example walkthrough
+
+Before we get started, it is recommended to `include` the Parcom module in whatever namespace you are working in:
+
+```
+require "parcom"
+
+include Parcom
+
+module YourModule
+ def self.main
+ puts "Hello world!"
+ end
+end
+
+YourModule.main
+```
+
+Suppose we want to parse a `Hash(Int32, Int32)` literal from a string.
+
+First, we should define how to parse a digit:
+```
+# This defines a parser that will parse a single Char, check if
+# it is a digit, and fail if it is not a digit.
+d = Parser(Char, Char).satisfy(&.number?)
+```
+
+Numbers often have one or more digits [citation needed], so let's make another parser based on `d` that parses multiple digits:
+```
+# `Parser#some` is a method that creates a new parser that parses
+# one or more instances of what the original parser would parse.
+abs_num = d.some
+```
+
+We're not quite done with this yet, as we want a parser of `Int32`, but this parser will parse an `Array(Char)`.
+We need to change the value inside the parser with the `Parser#map` method:
+```
+# The `Parser#map` method accepts a block or proc that takes the expected
+# parser result and transforms it into something else.
+# In this case, we're converting our array of digits into an Int32.
+abs_num = d.some.map { |ds| ds.join.to_i32 }
+```
+
+Now we have a parser that can parse positive integers (in base 10). But what about negative numbers?
+
+First, we make a parser that parses a '-' sign if it can, but doesn't fail if it can't fine one:
+```
+# `Parser#optional` creates a new parser that tries to parse with the original
+# parser, but will return `nil` without consuming any input instead of failing.
+sign = Parser.token('-').optional
+```
+
+Then we can change the value to `1` or `-1` to multiply by later, based on the result:
+```
+sign = Parser.token('-').optional.map do |minus_or_nil|
+ minus_or_nil.nil? : -1_i32 : 1_i32
+end
+```
+
+Another way to do this is to use `Parser#recover`, which allows a default value to be specified:
+```
+# `#map_const` is like `#map`, but it takes a single value to replace
+# the parser value with unconditionally.
+sign = Parser.token('-').map_const(-1_i32).recover(1_i32)
+```
+
+Final code:
+
+```
+d = Parser(Char, Char).satisfy(&.number?)
+abs_num = d.some.map { |ds| ds.join.to_i32 }
+
+sign = Parser.token('-').map_const(-1_i32).recover(1_i32)
+
+int32 = parser_chain Char, Int32, "int32",
+ {s, sign},
+ {n, abs_num},
+ finally: Parser.pure(n * s)
+```