diff --git a/src/Gaze.Test/Gaze.Test.fsproj b/src/Gaze.Test/Gaze.Test.fsproj
new file mode 100644
index 0000000..7d919b8
--- /dev/null
+++ b/src/Gaze.Test/Gaze.Test.fsproj
@@ -0,0 +1,25 @@
+
+
+
+ Exe
+ net6.0
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Gaze.Test/GazeSuite.fs b/src/Gaze.Test/GazeSuite.fs
new file mode 100644
index 0000000..2d80d3b
--- /dev/null
+++ b/src/Gaze.Test/GazeSuite.fs
@@ -0,0 +1,49 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+module GazeSuite
+
+open Expecto
+
+[]
+let tests =
+ testList "Gaze Suite" [
+ testCase "empty String input" <| fun _ ->
+ let gaze = Gaze.fromString("")
+ Expect.isTrue (Gaze.isComplete gaze) ""
+ Expect.equal (Gaze.peek gaze) None ""
+ Expect.equal (Gaze.peek gaze) None ""
+ Expect.isTrue (Gaze.isComplete gaze) ""
+
+ testCase "empty array input" <| fun _ ->
+ let gaze = Gaze.fromArray([||])
+ Expect.isTrue (Gaze.isComplete gaze) ""
+ Expect.equal (Gaze.peek gaze) None ""
+ Expect.equal (Gaze.peek gaze) None ""
+ Expect.isTrue (Gaze.isComplete gaze) ""
+
+ testCase "init Gaze with one value" <| fun _ ->
+ let gaze = Gaze.fromArray([|'a'|])
+ Expect.isFalse (Gaze.isComplete gaze) ""
+ Expect.equal (Gaze.peek gaze) (Some('a')) ""
+ Expect.equal (Gaze.peek gaze) (Some('a')) ""
+ Expect.equal (Gaze.next gaze) (Some('a')) ""
+ Expect.equal (Gaze.next gaze) None ""
+ Expect.isTrue (Gaze.isComplete gaze) ""
+
+ testCase "init Gaze with single char String" <| fun _ ->
+ let gaze = Gaze.fromString("a")
+ Expect.isFalse (Gaze.isComplete gaze) ""
+ Expect.equal (Gaze.peek gaze) (Some('a')) ""
+ Expect.equal (Gaze.peek gaze) (Some('a')) ""
+ Expect.equal (Gaze.next gaze) (Some('a')) ""
+ Expect.equal (Gaze.next gaze) None ""
+ Expect.isTrue (Gaze.isComplete gaze) ""
+
+ testCase "map digit" <| fun _ ->
+ let gaze = Gaze.fromString("1")
+ let result = Some(1)
+ let nibbler = Gaze.map Gaze.next (fun char -> int (string char))
+ Expect.equal(Gaze.attempt nibbler gaze) result ""
+ ]
diff --git a/src/Gaze.Test/Main.fs b/src/Gaze.Test/Main.fs
new file mode 100644
index 0000000..ba9c2d2
--- /dev/null
+++ b/src/Gaze.Test/Main.fs
@@ -0,0 +1,9 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+open Expecto
+
+[]
+let main argv =
+ Tests.runTestsInAssembly defaultConfig argv
diff --git a/src/Gaze.Test/NibblersSuite.fs b/src/Gaze.Test/NibblersSuite.fs
new file mode 100644
index 0000000..520ff63
--- /dev/null
+++ b/src/Gaze.Test/NibblersSuite.fs
@@ -0,0 +1,156 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+module NibblersSuite
+
+open Expecto
+
+[]
+let tests = testList "Nibbler Tests" [
+ testList "Take Suite" [
+ testCase "take with single value" <| fun _ ->
+ let gaze = Gaze.fromString("a")
+ Expect.equal (Gaze.attempt (Nibblers.take 'a') gaze) (Some('a')) ""
+ Expect.isTrue (Gaze.isComplete gaze) ""
+ ]
+
+ testList "Take List Suite" [
+ testCase "takeList simple" <| fun _ ->
+ let gaze = Gaze.fromString("hello, world")
+ let list = ['h'; 'e'; 'l'; 'l'; 'o']
+ Expect.equal (Gaze.attempt (Nibblers.takeList list) gaze) (Some(list)) ""
+ Expect.equal(Gaze.peek gaze) (Some(',')) ""
+ ]
+
+ testList "Take String Suite" [
+ testCase "takeString simple" <| fun _ ->
+ let gaze = Gaze.fromString("hello, world")
+ Expect.equal (Gaze.attempt(Nibblers.takeString "hello") gaze) (Some(['h'; 'e'; 'l'; 'l'; 'o'])) ""
+ Expect.equal (Gaze.peek gaze) (Some(',')) ""
+ ]
+
+ testList "Take Cond Suite" [
+ testCase "takeCond with a single value" <| fun _ ->
+ let gaze = Gaze.fromString("a")
+ Expect.equal
+ (Gaze.attempt(Nibblers.takeCond (fun c -> c = 'a')) gaze)
+ (Some('a')) ""
+ assert(Gaze.isComplete(gaze))
+ testCase "takeCond with multiple values" <| fun _ ->
+ let gaze = Gaze.fromString("cab")
+ Expect.equal
+ (Gaze.attempt(Nibblers.takeCond(fun c -> List.contains c ['a'; 'b'; 'c'; 'd'])) gaze)
+ (Some('c')) ""
+ Expect.isFalse (Gaze.isComplete gaze) ""
+ testCase "takeCond match beginning" <| fun _ ->
+ let gaze = Gaze.fromString("abc123")
+ Expect.equal
+ (Gaze.attempt(Nibblers.takeCond(fun c -> List.contains c ['a'; 'b'; 'c'; 'd'])) gaze)
+ (Some('a')) ""
+ Expect.isFalse (Gaze.isComplete gaze) ""
+ testCase "takeCond no match beginning" <| fun _ ->
+ let gaze = Gaze.fromString("123abc")
+ Expect.equal
+ (Gaze.attempt(Nibblers.takeCond(fun c -> List.contains c ['a'; 'b'; 'c'; 'd'])) gaze)
+ None ""
+ Expect.isFalse (Gaze.isComplete gaze) ""
+ ]
+
+ testList "Take While Suite" [
+ testCase "takeWhile with a single value" <| fun _ ->
+ let gaze = Gaze.fromString("a")
+ Expect.equal (Gaze.attempt(Nibblers.takeWhile(fun c -> c = 'a')) gaze) (Some(['a'])) ""
+ Expect.isTrue (Gaze.isComplete gaze) ""
+ testCase "takeWhile with multiple values" <| fun _ ->
+ let gaze = Gaze.fromString("cab")
+ Expect.equal
+ (Gaze.attempt (Nibblers.takeWhile(fun c -> List.contains c ['a'; 'b'; 'c'; 'd'])) gaze)
+ (Some(['c'; 'a'; 'b'])) ""
+ Expect.isTrue (Gaze.isComplete gaze) ""
+ testCase "takeWhile match beginning" <| fun _ ->
+ let gaze = Gaze.fromString("abc123")
+ Expect.equal
+ (Gaze.attempt(Nibblers.takeWhile(fun c -> List.contains c ['a'; 'b'; 'c'; 'd'])) gaze)
+ (Some(['a'; 'b'; 'c'])) ""
+ Expect.isFalse (Gaze.isComplete gaze) ""
+ testCase "takeWhile no match beginning" <| fun _ ->
+ let gaze = Gaze.fromString("123abc")
+ Expect.equal
+ (Gaze.attempt(Nibblers.takeWhile(fun c -> List.contains c ['a'; 'b'; 'c'; 'd'])) gaze) None ""
+ Expect.isFalse (Gaze.isComplete gaze) ""
+ ]
+
+ testList "Take While Index Suite" [
+ testCase "takeWhileIndex simple" <| fun _ ->
+ let gaze = Gaze.fromString("abc")
+ Expect.equal
+ (Gaze.attempt(Nibblers.takeWhileIndex(fun (c, i) -> (c = 'a' && i = 0) || (c = 'b' && i = 1))) gaze)
+ (Some(['a'; 'b'])) ""
+ Expect.isFalse(Gaze.isComplete gaze) ""
+ ]
+
+ testList "Take Until Suite" [
+ testCase "takeUntil simple" <| fun _ ->
+ let gaze = Gaze.fromString("hello, world")
+ Expect.equal
+ (Gaze.attempt (Nibblers.takeUntil (Nibblers.take ',')) gaze)
+ (Some(['h'; 'e'; 'l'; 'l'; 'o'])) ""
+ Expect.equal (Gaze.peek gaze) (Some(',')) ""
+ ]
+
+ testList "Between Suite" [
+ testCase "between simple" <| fun _ ->
+ let gaze = Gaze.fromString("abbbbbc")
+ Expect.equal
+ (Gaze.attempt(Nibblers.between 'a' (Nibblers.takeWhile(fun c -> c = 'b')) 'c') gaze)
+ (Some(['b'; 'b'; 'b'; 'b'; 'b'])) ""
+ Expect.isTrue (Gaze.isComplete gaze) ""
+ ]
+
+ testList "Optional Suite" [
+ testCase "optional simple" <| fun _ ->
+ let gaze = Gaze.fromString("a")
+ Expect.equal
+ (Gaze.attempt(Nibblers.optional(Gaze.map (Nibblers.take 'b') (fun r -> [r]))) gaze)
+ (Some([])) ""
+ Expect.equal (Gaze.peek gaze) (Some('a')) ""
+ ]
+
+ testList "Repeat Suite" [
+ testCase "repeat simple" <| fun _ ->
+ let gaze = Gaze.fromString("aaaabbbbbc")
+ Expect.equal(Gaze.attempt(Nibblers.repeat(Nibblers.takeCond(fun c -> c = 'a'))) gaze) (Some(['a'; 'a'; 'a'; 'a'])) ""
+ Expect.isFalse(Gaze.isComplete gaze) ""
+ Expect.equal(Gaze.attempt(Nibblers.repeat(Nibblers.takeCond(fun c -> c = 'b'))) gaze) (Some(['b'; 'b'; 'b'; 'b'; 'b'])) ""
+ Expect.isFalse(Gaze.isComplete gaze) ""
+ Expect.equal(Gaze.attempt(Nibblers.repeat(Nibblers.takeCond(fun c -> c = 'c'))) gaze) (Some(['c'])) ""
+ Expect.isTrue(Gaze.isComplete gaze) ""
+ ]
+
+ testList "Take All Suite" [
+ testCase "take all simple" <| fun _ ->
+ let gaze = Gaze.fromString("aaaabbbbbc")
+ let takeAs = Nibblers.repeat(Nibblers.takeCond(fun c -> c = 'a'))
+ let takeBs = Nibblers.repeat(Nibblers.takeCond(fun c -> c = 'b'))
+ let takeCs = Nibblers.repeat(Nibblers.takeCond(fun c -> c = 'c'))
+ let takeAll = Nibblers.takeAll [takeAs; takeBs; takeCs]
+ Expect.equal
+ (Gaze.attempt takeAll gaze)
+ (Some([['a'; 'a'; 'a'; 'a']; ['b'; 'b'; 'b'; 'b'; 'b']; ['c']])) ""
+ Expect.isTrue(Gaze.isComplete gaze) ""
+ ]
+
+ testList "Take First Suite" [
+ testCase "take first simple" <| fun _ ->
+ let gaze = Gaze.fromString("aaaabbbbbc")
+ let takeAs = Nibblers.repeat(Nibblers.takeCond(fun c -> c = 'a'))
+ let takeBs = Nibblers.repeat(Nibblers.takeCond(fun c -> c = 'b'))
+ let takeCs = Nibblers.repeat(Nibblers.takeCond(fun c -> c = 'c'))
+ let takeFirst = Nibblers.takeFirst [takeCs; takeBs; takeAs]
+ Expect.equal
+ (Gaze.attempt takeFirst gaze)
+ (Some(['a'; 'a'; 'a'; 'a'])) ""
+ Expect.isFalse(Gaze.isComplete gaze) ""
+ ]
+]
diff --git a/src/Gaze/Gaze.fs b/src/Gaze/Gaze.fs
index a133326..8efd8e6 100644
--- a/src/Gaze/Gaze.fs
+++ b/src/Gaze/Gaze.fs
@@ -11,7 +11,7 @@ type Gaze<'input> = {
type Nibbler<'input, 'output> = Gaze<'input> -> 'output option
-let private explode (s:string) =
+let explode (s:string) =
[| for c in s -> c |]
/// Create an instance of Gaze that works with a String as input.
@@ -50,15 +50,14 @@ let check nibbler gaze =
let attempt nibbler gaze =
let startOffset = gaze.offset
match nibbler gaze with
- | Some(res) -> Some(res)
- | None ->
- gaze.offset <- startOffset
- None
+ | Some(res) -> Some(res)
+ | None ->
+ gaze.offset <- startOffset
+ None
let offset gaze = gaze.offset
let map nibbler mapper gaze =
match attempt nibbler gaze with
- | Some(result) ->
- Some(mapper(result))
- | None -> None
+ | Some(result) -> Some(mapper(result))
+ | None -> None
diff --git a/src/Gaze/Gaze.fsproj b/src/Gaze/Gaze.fsproj
new file mode 100644
index 0000000..28efe02
--- /dev/null
+++ b/src/Gaze/Gaze.fsproj
@@ -0,0 +1,13 @@
+
+
+
+ Exe
+ net6.0
+
+
+
+
+
+
+
+
diff --git a/src/Gaze/Nibblers.fs b/src/Gaze/Nibblers.fs
new file mode 100644
index 0000000..f0036ab
--- /dev/null
+++ b/src/Gaze/Nibblers.fs
@@ -0,0 +1,187 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+module Nibblers
+
+/// Create a Nibbler that take a single literal value.
+/// The literal to take.
+/// A Nibbler that takes a single literal.
+let take t gaze =
+ if Gaze.next gaze = Some(t) then
+ Some(t)
+ else
+ None
+
+/// Create a Nibbler that takes a list of tokens.
+/// The list of tokens to take.
+/// The newly created Nibbler.
+let takeList list gaze =
+ let length = List.length(list)
+ let mutable index = 0
+ let mutable cont = true
+ while not (Gaze.isComplete gaze) && cont && index < length do
+ let next = Gaze.next(gaze)
+ match next with
+ | Some value ->
+ if value = list.Item(index) then
+ index <- index + 1
+ else
+ cont <- false
+ | None ->
+ cont <- false
+ if cont && index = length then
+ Some list
+ else
+ None
+
+/// Create a Nibbler that takes a String when working with a Gaze of Chars.
+/// This is just a helper function that relies on takeList.
+/// The String to match.
+/// The newly created Nibbler.
+let takeString string =
+ takeList(Array.toList(Gaze.explode(string)))
+
+/// Create a Nibbler that accepts input based on a function that recieves the current token
+/// and returns a bool.
+/// The function used to decide if a token matches.
+/// A Nibbler that consumes one item as long as the predicate passes.
+let takeCond predicate gaze =
+ let next = Gaze.peek(gaze)
+ match next with
+ | Some(value) when predicate(value) ->
+ Gaze.next(gaze) |> ignore
+ Some(value)
+ | _ -> None
+
+/// Create a Nibbler that accepts input based on a function that recieves the current token
+/// and returns a bool.
+/// The function used to decide if a token matches.
+/// A Nibbler that consumes input as long as the predicate passes.
+let takeWhile predicate gaze =
+ let mutable cont = true
+ let mutable results = []
+ while cont do
+ let next = Gaze.peek(gaze)
+ match next with
+ | Some value when predicate value ->
+ Gaze.next(gaze) |> ignore
+ results <- results @ [value]
+ | _ ->
+ cont <- false
+ if List.length(results) = 0 then
+ None
+ else
+ Some results
+
+/// Create a Nibbler that accepts input based on a function that recieves the current token
+/// with index starting at 0 and returns a bool.
+/// The function used to decide if a token matches.
+/// A Nibbler that consumes input as long as the predicate passes.
+let takeWhileIndex predicate gaze =
+ let mutable index = 0
+ let mutable cont = true
+ let mutable results = []
+ while cont do
+ let next = Gaze.peek(gaze)
+ match next with
+ | Some(value) when predicate(value, index) ->
+ Gaze.next(gaze) |> ignore
+ results <- results @ [value]
+ index <- index + 1
+ | _ -> cont <- false
+ if List.length(results) = 0 then
+ None
+ else
+ Some(results)
+
+/// Create a Nibbler that consumes input until the given Nibbler succeeds.
+/// The Nibbler used to test.
+/// The newly created Nibbler.
+let takeUntil nibbler gaze =
+ let mutable results = []
+ let mutable cont = true
+ while cont && not (Gaze.isComplete gaze) do
+ let res = Gaze.check nibbler gaze
+ match res with
+ | Some(_) ->
+ cont <- false
+ | None ->
+ let next = Gaze.next(gaze)
+ results <- results @ [next.Value] //???
+ Some(results)
+
+/// Create a Nibbler that accepts a start.
+/// The starting token.
+/// The Nibbler used to decide the matched content.
+/// The ending token.
+/// A Nibbler that consumes a starting and ending token and returns the content that matches in between.
+let between start content last gaze =
+ if Gaze.next(gaze) = Some(start) then
+ match Gaze.attempt content gaze with
+ | Some(result) ->
+ if Gaze.next(gaze) = Some(last) then
+ Some(result)
+ else
+ None
+ | None -> None
+ else
+ None
+
+/// Creates a Nibbler that wraps another Nibbler and will never fail but will instead return an empty List.
+/// The Nibbler to wrap.
+/// The newly created Nibbler.
+let optional nibbler gaze =
+ match Gaze.attempt nibbler gaze with
+ | Some(res) -> Some(res)
+ | None -> Some([])
+
+let repeat nibbler gaze =
+ let mutable cont = true
+ let mutable results = []
+ while cont do
+ match Gaze.attempt nibbler gaze with
+ | Some(result) ->
+ results <- results @ [result]
+ | None -> cont <- false
+ if results = [] then
+ None
+ else
+ Some(results)
+
+/// Create a Nibbler that accepts a List of Nibblers and only succeeds if all of the
+/// passed in Nibblers succeed in order.
+/// A List of nibblers.
+/// A List of all of the results from each Nibbler internally grouped in Lists.
+let takeAll nibblers gaze =
+ let mutable results = []
+ let mutable nibblerIndex = 0
+ while nibblerIndex >= 0 && nibblerIndex < List.length(nibblers) do
+ let nibbler = nibblers.Item(nibblerIndex)
+ match Gaze.attempt nibbler gaze with
+ | Some(result) ->
+ results <- results @ [result]
+ nibblerIndex <- nibblerIndex + 1
+ | None ->
+ nibblerIndex <- -1
+ if results = [] || nibblerIndex = -1 then
+ None
+ else
+ Some(results)
+
+/// Create a Nibbler that accepts a List of Nibblers and matches on the first that succeeds.
+/// If all fail the created Nibbler will fail as well.
+/// A list of Nibblers to check.
+/// The newly created Nibbler.
+let takeFirst nibblers gaze =
+ let mutable result = None
+ let mutable nibblerIndex = 0
+ while nibblerIndex >= 0 && nibblerIndex < List.length(nibblers) do
+ let nibbler = nibblers.Item(nibblerIndex)
+ match Gaze.attempt nibbler gaze with
+ | Some(res) ->
+ result <- Some(res)
+ nibblerIndex <- -1
+ | None ->
+ nibblerIndex <- nibblerIndex + 1
+ result