diff options
| author | Matthew Hall <hallmatthew314@gmail.com> | 2023-04-02 12:14:51 +1200 |
|---|---|---|
| committer | Matthew Hall <hallmatthew314@gmail.com> | 2023-04-02 12:15:17 +1200 |
| commit | becabe55c4882a32e759430ae399c5e1e4057e2c (patch) | |
| tree | b2d7645d670d2f59c4240bdff5972c051fc9c69a /README.md | |
| parent | 4fd257bb084d173fa07aa057e6d294ee525721e8 (diff) | |
WIP: readme
Diffstat (limited to 'README.md')
| -rw-r--r-- | README.md | 116 |
1 files changed, 115 insertions, 1 deletions
@@ -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) +``` |
