Skip to content

Commit

Permalink
✨ labeling and mapping over results
Browse files Browse the repository at this point in the history
  • Loading branch information
nabeelvalley committed Jul 19, 2024
1 parent 2ef3e6f commit 94cf9f1
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 32 deletions.
81 changes: 57 additions & 24 deletions src/parz/combinators.gleam
Original file line number Diff line number Diff line change
@@ -1,46 +1,48 @@
import gleam/list
import gleam/string
import parz/types.{type Parser, type ParserState, ParserState}
import parz/util.{tap}

fn sequence_rec(parsers, state, acc) {
fn sequence_rec(parsers: List(Parser), input, acc) {
case parsers {
[] -> Ok([])
[] -> Ok(#([], input))
[first, ..rest] ->
case first(state) {
case first(input) {
Error(err) -> Error(err)
Ok(ok) ->
case sequence_rec(rest, ok, acc) {
case sequence_rec(rest, ok.remaining, acc) {
Error(err) -> Error(err)
Ok(rec) -> Ok([ok, ..rec])
Ok(rec) -> {
let #(matches, remaining) = rec
Ok(#([ok.matched, ..matches], remaining))
}
}
}
}
}

pub fn sequence(parsers) {
fn(state) { sequence_rec(parsers, state, []) }
pub fn sequence(parsers: List(Parser)) {
fn(input) { sequence_rec(parsers, input, []) }
}

pub fn choice(parsers: List(Parser)) {
fn(state) {
fn(input) {
case parsers {
[] -> Error("No more choices provided")
[first, ..rest] ->
case first(state) {
case first(input) {
Ok(ok) -> Ok(ok)
Error(err) ->
case rest {
[] -> Error(err)
_ -> choice(rest)(state)
_ -> choice(rest)(input)
}
}
}
}
}

pub fn right(l: Parser, r: Parser) -> Parser {
fn(state) {
case l(state) {
fn(input) {
case l(input) {
Error(err) -> Error(err)
Ok(okl) ->
case r(okl.remaining) {
Expand All @@ -52,8 +54,8 @@ pub fn right(l: Parser, r: Parser) -> Parser {
}

pub fn left(l: Parser, r: Parser) -> Parser {
fn(state) {
case l(state) {
fn(input) {
case l(input) {
Error(err) -> Error(err)
Ok(okl) ->
case r(okl.remaining) {
Expand All @@ -65,8 +67,8 @@ pub fn left(l: Parser, r: Parser) -> Parser {
}

pub fn between(l: Parser, keep: Parser, r: Parser) -> Parser {
fn(state) {
case l(state) {
fn(input) {
case l(input) {
Error(err) -> Error(err)
Ok(okl) ->
case left(keep, r)(okl.remaining) {
Expand All @@ -77,8 +79,8 @@ pub fn between(l: Parser, keep: Parser, r: Parser) -> Parser {
}
}

fn many_rec(parser: Parser, state, acc) {
case parser(state) {
fn many_rec(parser: Parser, input, acc) {
case parser(input) {
Error(err) -> Error(err)
Ok(ok) -> {
case many_rec(parser, ok.remaining, acc) {
Expand All @@ -93,14 +95,45 @@ fn many_rec(parser: Parser, state, acc) {
}

pub fn many1(parser: Parser) {
fn(state) { many_rec(parser, state, []) }
fn(input) { many_rec(parser, input, []) }
}

pub fn many(parser: Parser) {
fn(state) {
case many1(parser)(state) {
Error(_) -> Ok(#([], state))
fn(input) {
case many1(parser)(input) {
Error(_) -> Ok(#([], input))
Ok(ok) -> Ok(ok)
}
}
}

pub fn concat(parser) {
fn(input) {
case parser(input) {
Error(err) -> Error(err)
Ok(ok) -> {
let #(parts, remainder) = ok

Ok(ParserState(string.concat(parts), remainder))
}
}
}
}

pub fn label_error(parser, message) {
fn(input) {
case parser(input) {
Ok(ok) -> Ok(ok)
Error(_) -> Error(message)
}
}
}

pub fn map(parser, transform) {
fn(input) {
case parser(input) {
Error(err) -> Error(err)
Ok(ok) -> Ok(transform(ok))
}
}
}
14 changes: 7 additions & 7 deletions src/parz/parsers.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,27 @@ import gleam/string
import parz/types.{type Parser, ParserState}

pub fn str(start) -> Parser {
fn(state) {
case string.starts_with(state, start) {
False -> Error("Expected " <> start <> "But found " <> state)
fn(input) {
case string.starts_with(input, start) {
False -> Error("Expected " <> start <> " but found " <> input)
True -> {
let remaining = string.drop_left(state, string.length(start))
let remaining = string.drop_left(input, string.length(start))
Ok(ParserState(start, remaining))
}
}
}
}

pub fn regex(regex) {
fn(state) {
fn(input) {
case regex.from_string(regex) {
Error(_) -> Error("Invalid Regex Provided " <> regex)
Ok(re) -> {
case regex.scan(re, state) {
case regex.scan(re, input) {
[] -> Error("String does not match regex " <> regex)
[match, ..] -> {
let remaining =
string.drop_left(state, string.length(match.content))
string.drop_left(input, string.length(match.content))
Ok(ParserState(match.content, remaining))
}
}
Expand Down
76 changes: 75 additions & 1 deletion test/combinators_test.gleam
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import gleeunit/should
import parz.{run}
import parz/combinators.{between, choice, left, many, many1, right, sequence}
import parz/combinators.{
between, choice, concat, label_error, left, many, many1, map, right, sequence,
}
import parz/parsers.{letters, str}
import parz/types.{ParserState}

Expand Down Expand Up @@ -113,3 +115,75 @@ pub fn many1_test() {
|> should.be_ok
|> should.equal(#(["x", "x"], "!"))
}

pub fn sequence_test() {
let parser = sequence([str("["), letters(), str("]")])

run(parser, "[hello]")
|> should.be_ok
|> should.equal(#(["[", "hello", "]"], ""))

run(parser, "[hello]!")
|> should.be_ok
|> should.equal(#(["[", "hello", "]"], "!"))

run(parser, "[hello")
|> should.be_error
}

pub fn concat_test() {
let parser = concat(sequence([str("["), letters(), str("]")]))

run(parser, "[hello]")
|> should.be_ok
|> should.equal(ParserState("[hello]", ""))

run(parser, "[hello]!")
|> should.be_ok
|> should.equal(ParserState("[hello]", "!"))

run(parser, "[hello")
|> should.be_error
}

pub fn label_error_test() {
let message = "Expected [letters]"
let parser =
concat(sequence([str("["), letters(), str("]")])) |> label_error(message)

run(parser, "[hello]")
|> should.be_ok
|> should.equal(ParserState("[hello]", ""))

run(parser, "[hellox")
|> should.be_error
|> should.equal(message)
}

type Transformed {
NoContent
Content(String)
}

pub fn map_test() {
let parser =
concat(sequence([str("["), letters(), str("]")]))
|> map(fn(ok) {
let seq = ok.matched
case seq {
"" -> NoContent
content -> Content(content)
}
})

run(parser, "[hello]")
|> should.be_ok
|> should.equal(Content("[hello]"))

run(parser, "[hello]x")
|> should.be_ok
|> should.equal(Content("[hello]"))

run(parser, "[hellox")
|> should.be_error
}

0 comments on commit 94cf9f1

Please sign in to comment.