Created
June 2, 2025 04:55
-
-
Save thacuber2a03/449ddb0fb7556b5aeaa538cb5cf5d030 to your computer and use it in GitHub Desktop.
a parser combinator library written in Wren
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class Error { | |
| construct new(message) { | |
| _message = message | |
| } | |
| toString { "[error] %(_message)" } | |
| } | |
| /* abstract */ class Input { | |
| peek(n) { Fiber.abort("%(type) does not implement peek(_)") } | |
| get(n) { Fiber.abort("%(type) does not implement get(_)") } | |
| pos { _pos } | |
| } | |
| class StringInput is Input { | |
| construct new(s) { | |
| _s = s.codePoints.toList | |
| _pos = 0 | |
| } | |
| peek(n) { | |
| var p = _pos + n | |
| if (p > _s.count) return "" | |
| return _s[_pos...p] | |
| .map {|x| String.fromCodePoint(x) } | |
| .toList | |
| .join("") | |
| } | |
| get(n) { | |
| var s = peek(n) | |
| _pos = _pos + n | |
| return s | |
| } | |
| default { "" } | |
| } | |
| class ListInput is Input { | |
| construct new(l) { | |
| _l = l | |
| _pos = 0 | |
| } | |
| peek(n) { _l[_pos...(_pos+n)] } | |
| get(n) { | |
| var l = peek(n) | |
| _pos = _pos + n | |
| return l | |
| } | |
| default { [] } | |
| } | |
| class Parser is Sequence { | |
| construct new(f) { _f = f } | |
| go(i) { _f.call(Parse.makeInput(i)) } | |
| then(other) {Parser.new {|i| | |
| var r1 = go(i) | |
| if (r1 is Error) return r1 | |
| var r2 = other.go(i) | |
| if (r2 is Error) return r2 | |
| return [r1, r2] | |
| }} | |
| thenIgnore(other) {Parser.new {|i| then(other).map {|x| x[0]} .go(i) }} | |
| ignoreThen(other) {Parser.new {|i| then(other).map {|x| x[1]} .go(i) }} | |
| andIgnore {Parser.new {|i| this.map {|x| i.default }.go(i) }} | |
| or(other) {Parser.new {|i| | |
| var r1 = go(i) | |
| if (!(r1 is Error)) return r1 | |
| var r2 = other.go(i) | |
| if (!(r2 is Error)) return r2 | |
| return Error.new("no parser matched") | |
| }} | |
| map(f) {Parser.new {|i| | |
| var r = go(i) | |
| if (r is Error) return r | |
| return f.call(r) | |
| }} | |
| anyTimes {Parser.new {|i| this.atLeastOnce.or(Parse.none).go(i) }} | |
| atLeastOnce {Parser.new {|i| | |
| var strin = i is StringInput | |
| var res = strin ? "" : [] | |
| while (true) { | |
| var r = go(i) | |
| if (r is Error) break | |
| if (strin) res = res + r else res.add(r) | |
| } | |
| if (res.count == 0) return Error.new("expected at least one") | |
| return res | |
| }} | |
| maybe {Parser.new {|i| this.or(Parse.none).go(i) }} | |
| delimitedBy(start, end) {Parser.new {|i| start.ignoreThen(this).thenIgnore(end).go(i) }} | |
| paddedBy(pad) {Parser.new {|i| delimitedBy(pad, pad).go(i) }} | |
| } | |
| class Parse { | |
| static makeInput(i) { | |
| if (i is Input) return i | |
| if (i is String) return StringInput.new(i) | |
| if (i is List) return ListInput.new(i) | |
| Fiber.abort("invalid input %(i)") | |
| } | |
| static just(e) {Parser.new {|i| | |
| var s = i.peek(e.count) | |
| if (s is Error) return s | |
| if (s == e) return i.get(e.count) | |
| if (s.count == 0) s = "end of input" | |
| return Error.new("expected %(e), got %(s)") | |
| }} | |
| static anyOf(cs) {Parser.new {|i| | |
| var s = i.peek(1) | |
| if (s is Error) return s | |
| for (c in cs) if (s == c) return i.get(1) | |
| return Error.new("couldn't match any of %(cs)") | |
| }} | |
| static digit {Parser.new {|i| anyOf("0123456789").go(i) }} | |
| static alpha {Parser.new {|i| anyOf("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ").go(i) }} | |
| static fail {Parser.new {|i| Error.new("fail") }} | |
| static none {Parser.new {|i| i.default }} | |
| static any {Parser.new {|i| i.get(1) }} | |
| static eof {Parser.new {|i| | |
| var e = i.peek(1) | |
| if (e.count != 0) return Error.new("expected end of input, got %(e)") | |
| }} | |
| static ifMatches(f) {Parser.new {|i| | |
| var r = i.peek(1) | |
| if (r is Error) return r | |
| if (!f.call(r)) return Error.new("%(r) does not match the predicate") | |
| return i.get(1) | |
| }} | |
| static whitespace { Parse.ifMatches {|x| x.trim().isEmpty } } | |
| } | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment