From e18e66860d05174b7d0e72ce158fd2b71b68f173 Mon Sep 17 00:00:00 2001 From: Alejandro Mery Date: Sat, 26 Jun 2021 22:41:16 +0100 Subject: [PATCH] runes: imported github.com/JamesOwenHall/json2.Scanner as Probe Signed-off-by: Alejandro Mery --- runes/probe.go | 124 ++++++++++++++++++++++++++++++++++++++++++++ runes/probe_test.go | 58 +++++++++++++++++++++ 2 files changed, 182 insertions(+) create mode 100644 runes/probe.go create mode 100644 runes/probe_test.go diff --git a/runes/probe.go b/runes/probe.go new file mode 100644 index 0000000..c634625 --- /dev/null +++ b/runes/probe.go @@ -0,0 +1,124 @@ +package runes + +import ( + "unicode" +) + +// Probe was borrowed from https://github.com/JamesOwenHall/json2.Scanner +// + +// Probe is a func that returns a subset of the input and a success bool. +type Probe func([]rune) ([]rune, bool) + +// If returns a probe that accepts the a rune if it satisfies the condition. +func If(condition func(rune) bool) Probe { + return func(input []rune) ([]rune, bool) { + if len(input) > 0 && condition(input[0]) { + return input[0:1], true + } + + return nil, false + } +} + +// Rune returns a probe that accepts r. +func Rune(r rune) Probe { + return If(func(b rune) bool { + return r == b + }) +} + +// Space returns a probe that accepts whitespace as defined in the unicode +// package. +func Space() Probe { + return func(input []rune) ([]rune, bool) { + if len(input) > 0 && unicode.IsSpace(input[0]) { + return input[0:1], true + } + + return nil, false + } +} + +// And returns a probe that accepts all probes in sequence. +func And(probes ...Probe) Probe { + return func(input []rune) ([]rune, bool) { + remaining := input + accumulated := []rune{} + + for _, s := range probes { + if read, ok := s(remaining); !ok { + return nil, false + } else { + accumulated = append(accumulated, read...) + remaining = remaining[len(read):] + } + } + + return accumulated, true + } +} + +// Or returns a probe that accepts the first successful probe in probes. +func Or(probes ...Probe) Probe { + return func(input []rune) ([]rune, bool) { + for _, s := range probes { + if read, ok := s(input); ok { + return read, true + } + } + + return nil, false + } +} + +// Maybe runs probe and returns true regardless of the output. +func Maybe(probe Probe) Probe { + return func(input []rune) ([]rune, bool) { + read, _ := probe(input) + return read, true + } +} + +// Any returns a probe that accepts any number of occurrences of probe, +// including zero. +func Any(probe Probe) Probe { + return func(input []rune) ([]rune, bool) { + remaining := input + accumulated := []rune{} + + for { + if read, ok := probe(remaining); !ok { + return accumulated, true + } else { + accumulated = append(accumulated, read...) + remaining = remaining[len(read):] + } + } + } +} + +// N returns a probe that accepts probe exactly n times. +func N(n int, probe Probe) Probe { + return func(input []rune) ([]rune, bool) { + probes := make([]Probe, n) + for i := 0; i < n; i++ { + probes[i] = probe + } + + return And(probes...)(input) + } +} + +// AtLeast returns a probe that accepts probe at least n times. +func AtLeast(n int, probe Probe) Probe { + return func(input []rune) ([]rune, bool) { + probes := make([]Probe, n, n+1) + for i := range probes { + probes[i] = probe + } + + probes = append(probes, Any(probe)) + return And(probes...)(input) + } +} diff --git a/runes/probe_test.go b/runes/probe_test.go new file mode 100644 index 0000000..cf2f306 --- /dev/null +++ b/runes/probe_test.go @@ -0,0 +1,58 @@ +package runes + +import ( + "testing" +) + +func TestProbe(t *testing.T) { + type TestCase struct { + probe Probe + input string + } + + tests := []TestCase{ + {Rune('a'), "a"}, + {Space(), " "}, + {Space(), "\t"}, + {Space(), "\n"}, + {And(Rune('1'), Rune('2'), Space()), "12 "}, + {Or(Rune('r'), Space(), Rune('x')), "r"}, + {Or(Rune('r'), Space(), Rune('x')), " "}, + {Or(Rune('r'), Space(), Rune('x')), "x"}, + {Any(Rune('w')), ""}, + {Any(Rune('w')), "w"}, + {Any(Rune('w')), "ww"}, + {Any(Rune('w')), "www"}, + {N(6, Rune('w')), "wwwwww"}, + {Maybe(Rune('w')), ""}, + {Maybe(Rune('w')), "w"}, + } + + for _, test := range tests { + if read, ok := test.probe([]rune(test.input)); !ok { + t.Errorf("Expected to read %s", string(test.input)) + } else if string(read) != test.input { + t.Errorf("Mismatch of input %s and read %s", test.input, string(read)) + } + } +} + +func TestProbeFail(t *testing.T) { + type TestCase struct { + probe Probe + input string + } + + tests := []TestCase{ + {Rune('a'), "b"}, + {Space(), "a"}, + {And(Rune('1'), Rune('2'), Space()), "12"}, + {Or(Rune('r'), Space(), Rune('x')), "4"}, + } + + for _, test := range tests { + if read, ok := test.probe([]rune(test.input)); ok { + t.Errorf("Unexpectedly read %s with input %s", string(read), test.input) + } + } +}