Commit 3f64a3722a78b3adead8a524775629809b5a9c1e
0 parents
First commit
Showing
7 changed files
with
1793 additions
and
0 deletions
combinators.nim
0 → 100644
1 | +++ a/combinators.nim | |
1 | +import petitparser, context | |
2 | + | |
3 | +# DelegateParser | |
4 | +type | |
5 | + DelegateParser* = ref object of Parser | |
6 | + delegate*: Parser | |
7 | + | |
8 | +proc newDelegateParser*(delegate: Parser not nil): DelegateParser = | |
9 | + DelegateParser(delegate: delegate) | |
10 | + | |
11 | +method parseOn*(self: DelegateParser, context: Context): Result = | |
12 | + self.delegate.parseOn(context) | |
13 | + | |
14 | +method replace*(self: DelegateParser, source, target: Parser) = | |
15 | + procCall(Parser(self).replace(source, target)) | |
16 | + if delegate == source: | |
17 | + delegate = target | |
18 | + | |
19 | +method getChildren(self: DelegateParser): seq[Parser] = | |
20 | + @[delegate] | |
21 | + | |
22 | +method copy(self: DelegateParser): Parser = | |
23 | + newDelegateParser(self.delegate) | |
24 | + | |
25 | + | |
26 | +## A parser that optionally parsers its delegate, or answers nil. | |
27 | +type | |
28 | + OptionalParser* = ref object of DelegateParser | |
29 | + otherwise*: RootRef | |
30 | + | |
31 | +proc newOptionalParser*(delegate: Parser, otherwise: RootRef): OptionalParser = | |
32 | + OptionalParser(delegate: delegate, otherwise: otherwise) | |
33 | + | |
34 | +# public OptionalParser(Parser delegate, Object otherwise) { | |
35 | +# super(delegate); | |
36 | +# this.otherwise = otherwise; | |
37 | +# } | |
38 | + | |
39 | +method parseOn*(self: OptionalParser, context: Context): Result = | |
40 | + result = self.delegate.parseOn(context) | |
41 | + if result.isSuccess: | |
42 | + return | |
43 | + current.success(elements) | |
44 | + | |
45 | + | |
46 | +# public Result parseOn(Context context) { | |
47 | +# Result result = delegate.parseOn(context); | |
48 | +# if (result.isSuccess()) { | |
49 | +# return result; | |
50 | +# } else { | |
51 | +# return context.success(otherwise); | |
52 | +# } | |
53 | +# } | |
54 | + | |
55 | +method hasEqualProperties*(self: OptionalParser, other: Parser): bool = | |
56 | + return true | |
57 | + | |
58 | +# protected boolean hasEqualProperties(Parser other) { | |
59 | +# return super.hasEqualProperties(other) && | |
60 | +# Objects.equals(otherwise, ((OptionalParser) other).otherwise); | |
61 | +# } | |
62 | + | |
63 | +method copy*(self: OptionalParser): Parser = | |
64 | + newOptionalParser(self.delegate, self.otherwise) | |
65 | + | |
66 | +# @Override | |
67 | +# public Parser copy() { | |
68 | +# return new OptionalParser(delegate, otherwise); | |
69 | +# } | |
70 | + | |
71 | + | |
72 | + | |
73 | +# Abstract parser that parses a list of things in some way (to be specified by the subclasses). | |
74 | +type | |
75 | + ListParser* = ref object of Parser | |
76 | + parsers*: seq[Parser] | |
77 | + | |
78 | +#proc newListParser*(parsers: seq[Parser] not nil): ListParser = | |
79 | +# ListParser(parsers: parsers) | |
80 | + | |
81 | +method replace*(self: ListParser, source, target: Parser) = | |
82 | + procCall(Parser(self).replace(source, target)) | |
83 | + for i in 0..high(parsers): | |
84 | + if parsers[i] == source: | |
85 | + parsers[i] = target | |
86 | + | |
87 | +# public void replace(Parser source, Parser target) { | |
88 | +# super.replace(source, target); | |
89 | +# for (int i = 0; i < parsers.length; i++) { | |
90 | +# if (parsers[i] == source) { | |
91 | +# parsers[i] = target; | |
92 | +# } | |
93 | +# } | |
94 | +# } | |
95 | + | |
96 | +method getChildren(self: ListParser): seq[Parser] = | |
97 | + self.parsers | |
98 | + | |
99 | +# public List<Parser> getChildren() { | |
100 | +# return Arrays.asList(parsers); | |
101 | +# } | |
102 | + | |
103 | + | |
104 | +# A parser that parses a sequence of parsers. | |
105 | +type | |
106 | + SequenceParser* = ref object of ListParser | |
107 | + | |
108 | +method parseOn*(self: SequenceParser, context: Context): Result = | |
109 | + var | |
110 | + current = context | |
111 | + elements = newSeq[auto](self.parsers.len) | |
112 | + for parser in self.parsers: | |
113 | + result = parser.parseOn(current) | |
114 | + if result.isFailure: | |
115 | + return | |
116 | + elements.add(result.get()) | |
117 | + current = result | |
118 | + current.success(elements) | |
119 | + | |
120 | +# public Result parseOn(Context context) { | |
121 | +# Context current = context; | |
122 | +# List<Object> elements = new ArrayList<>(parsers.length); | |
123 | +# for (Parser parser : parsers) { | |
124 | +# Result result = parser.parseOn(current); | |
125 | +# if (result.isFailure()) { | |
126 | +# return result; | |
127 | +# } | |
128 | +# elements.add(result.get()); | |
129 | +# current = result; | |
130 | +# } | |
131 | +# return current.success(elements); | |
132 | +# } | |
133 | + | |
134 | +method seq*(self: SequenceParser, others: varargs[Parser]): Parser = | |
135 | + newSequenceParser(self.parsers & others) | |
136 | +# let all = newSeq[Parser](self.parsers.len + others.len) | |
137 | + | |
138 | +# public Parser seq(Parser... others) { | |
139 | +# Parser[] array = Arrays.copyOf(parsers, parsers.length + others.length); | |
140 | +# System.arraycopy(others, 0, array, parsers.length, others.length); | |
141 | +# return new SequenceParser(array); | |
142 | +# } | |
143 | + | |
144 | +method copy*(self: SequenceParser): Parser = | |
145 | + newSequenceParser(@[] & self.parsers) | |
146 | + | |
147 | + | |
148 | +# @Override | |
149 | +# public Parser copy() { | |
150 | +# return new SequenceParser(Arrays.copyOf(parsers, parsers.length)); | |
151 | +# } | |
152 | +#} | ... | ... |
context.nim
0 → 100644
1 | +++ a/context.nim | |
1 | +import hashes | |
2 | + | |
3 | +# Converts the position index in a buffer to a line and column tuple. | |
4 | +# This code is an adaptation of splitLines. | |
5 | +proc findLineAndColumn(s: string, position: int): tuple[line, col: int] = | |
6 | + var first = 0 | |
7 | + var last = 0 | |
8 | + var line = 0 | |
9 | + while true: | |
10 | + while s[last] notin {'\0', '\c', '\l'}: inc(last) | |
11 | + # First line found | |
12 | + inc(line) | |
13 | + if last > position: | |
14 | + let col = position - first | |
15 | + return (line, col) | |
16 | + # skip newlines: | |
17 | + if s[last] == '\l': inc(last) | |
18 | + elif s[last] == '\c': | |
19 | + inc(last) | |
20 | + if s[last] == '\l': inc(last) | |
21 | + else: break # was '\0' | |
22 | + first = last | |
23 | + | |
24 | + | |
25 | +# Context | |
26 | +type | |
27 | + Context* = ref object of RootObj | |
28 | + buffer*: string | |
29 | + position*: int | |
30 | + | |
31 | +proc newContext*(buffer: string, position: int): Context = | |
32 | + Context(buffer: buffer, position: position) | |
33 | + | |
34 | + | |
35 | +# Result | |
36 | +type | |
37 | + Result* = ref object of Context | |
38 | + | |
39 | +# Success | |
40 | +type | |
41 | + Success*[T] = ref object of Result | |
42 | + result*: T | |
43 | + | |
44 | +proc newSuccess*[T](buffer: string, position: int, value: T): Success = | |
45 | + Success(buffer: buffer, position: position, result: value) | |
46 | + | |
47 | +# Failure | |
48 | +type | |
49 | + Failure* = ref object of Result | |
50 | + message*: string | |
51 | + | |
52 | +proc newFailure*(buffer: string, position: int, message: string): Failure = | |
53 | + Failure(buffer: buffer, position: position, message: message) | |
54 | + | |
55 | +# ParseError Exception | |
56 | +type | |
57 | + ParseError* = object of Exception | |
58 | + failure*: Failure | |
59 | + | |
60 | +proc newParseError*(failure: Failure): ref ParseError = | |
61 | + new(result) | |
62 | + result.failure = failure | |
63 | + result.msg = failure.message | |
64 | + | |
65 | +# Token | |
66 | +type | |
67 | + Token* = ref object of RootObj | |
68 | + buffer*: string | |
69 | + start*: int | |
70 | + stop*: int | |
71 | + value*: RootRef | |
72 | + | |
73 | +proc newToken*(buffer: string, start, stop: int, value: RootRef): Token = | |
74 | + Token(buffer: buffer, start: start, stop: stop, value: value) | |
75 | + | |
76 | +method getInput*(self: Token): string = | |
77 | + self.buffer.substr(self.start, self.stop) | |
78 | + | |
79 | +method getLength*(self: Token): int = | |
80 | + self.stop - self.stop | |
81 | + | |
82 | +method getLine*(self: Token): int = | |
83 | + self.buffer.findLineAndColumn(self.start)[0] | |
84 | + | |
85 | +method getColumn*(self: Token): int = | |
86 | + self.buffer.findLineAndColumn(self.start)[1] | |
87 | + | |
88 | +method `$`*(self: Token): string = | |
89 | + let (line, col) = findLineAndColumn(self.buffer, self.start); | |
90 | + "Token[" & $line & ":" & $col & "]: " & repr(self.value) # $self.value | |
91 | + | |
92 | +method hash*(self: Token): THash = | |
93 | + result = self.buffer.hash !& self.start.hash !& self.stop.hash #!& self.value.hash | |
94 | + result = !$result | |
95 | + | |
96 | +# We do not need to redefine ==, the generic one works fine. | |
97 | +# But let's see if we can | |
98 | +#method `==`(self, other: Token): bool = | |
99 | +# if (addr(self) == addr(other)): | |
100 | +# return true | |
101 | +# if other.isNil or self.isNil: | |
102 | +# return false | |
103 | +# self[] == other[] | |
104 | + | |
105 | + | |
106 | +method success*[T](self: Context, value: T, position: int): Success = | |
107 | + newSuccess(self.buffer, position, value) | |
108 | + | |
109 | +method success*[T](self: Context, value: T): Success = | |
110 | + self.success(value, self.position) | |
111 | + | |
112 | +method failure*(self: Context, message: string, position: int): Failure = | |
113 | + newFailure(self.buffer, position, message) | |
114 | + | |
115 | +method failure*(self: Context, message: string): Failure = | |
116 | + self.failure(message, self.position) | |
117 | + | |
118 | + | |
119 | +method isSuccess*(self: Result): bool = | |
120 | + false | |
121 | + | |
122 | +method isFailure*(self: Result): bool = | |
123 | + false | |
124 | + | |
125 | +# An abstract method, hmmm, which you can't really do | |
126 | +# in nim. Normally I would like to throw exception here, | |
127 | +# but we need to return a T for compiler to be happy... | |
128 | +method get*[T](self: Result): T = | |
129 | + discard result | |
130 | + # Dirty trick to make compiler realize I return a T | |
131 | + # T(self) | |
132 | + | |
133 | +method get*[T](self: Failure): T = | |
134 | + raise newParseError(self) | |
135 | + | |
136 | +method get*[T](self: Success): T = | |
137 | + self.result | |
138 | + | |
139 | + | |
140 | +method getMessage*(self: Failure): string = | |
141 | + self.message | |
142 | + | |
143 | +method isSuccess*(self: Success): bool = | |
144 | + true | |
145 | + | |
146 | +method name*(self: Result): string = | |
147 | + "Result" | |
148 | + | |
149 | +method name*(self: Success): string = | |
150 | + "Success" | |
151 | + | |
152 | +method name*(self: Failure): string = | |
153 | + "Failure" | |
154 | + | |
155 | + | |
156 | +method `$`*(self: Result): string = | |
157 | + let (line, col) = findLineAndColumn(self.buffer, self.position) | |
158 | + self.name & "[" & $line & ":" & $col & "]" | |
159 | + | |
160 | + | |
161 | +method `$`*(self: Success): string = | |
162 | + procCall($(Result(self))) & ": " & repr(self.result) | |
163 | + | |
164 | + | |
165 | +method isFailure*(self: Failure): bool = | |
166 | + true | |
167 | + | |
168 | +method `$`*(self: Failure): string = | |
169 | + procCall($(Result(self))) & ": " & self.message | |
170 | + | |
171 | +# The simplest form of unit tests | |
172 | +#when isMainModule: | |
173 | + | ... | ... |
context2.nim
0 → 100644
1 | +++ a/context2.nim | |
1 | +import hashes | |
2 | + | |
3 | +# Converts the position index in a buffer to a line and column tuple. | |
4 | +# This code is an adaptation of splitLines. | |
5 | +proc findLineAndColumn(s: string, position: int): tuple[line, col: int] = | |
6 | + var first = 0 | |
7 | + var last = 0 | |
8 | + var line = 0 | |
9 | + while true: | |
10 | + while s[last] notin {'\0', '\c', '\l'}: inc(last) | |
11 | + # First line found | |
12 | + inc(line) | |
13 | + if last > position: | |
14 | + let col = position - first | |
15 | + return (line, col) | |
16 | + # skip newlines: | |
17 | + if s[last] == '\l': inc(last) | |
18 | + elif s[last] == '\c': | |
19 | + inc(last) | |
20 | + if s[last] == '\l': inc(last) | |
21 | + else: break # was '\0' | |
22 | + first = last | |
23 | + | |
24 | +type | |
25 | + ContextKind = enum ckContext, ckSuccess, ckFailure | |
26 | + | |
27 | +# Context | |
28 | +type | |
29 | + Context* = object | |
30 | + buffer*: string | |
31 | + position*: int | |
32 | + case kind: ContextKind | |
33 | + of ckContext: | |
34 | + nil | |
35 | + of ckSuccess: | |
36 | + value*: RootRef | |
37 | + of ckFailure: | |
38 | + message*: string | |
39 | + | |
40 | + | |
41 | +type Success = distinct Context | |
42 | +type Failure = distinct Context | |
43 | + | |
44 | +proc newContext*(buffer: string, position: int): Context = | |
45 | + Context(kind: ckContext, buffer: buffer, position: position) | |
46 | + | |
47 | +proc newSuccess*(buffer: string, position: int, value: RootRef): Success = | |
48 | + Success(Context(kind: ckSuccess, buffer: buffer, position: position, value: value)) | |
49 | + | |
50 | +proc newFailure*(buffer: string, position: int, message: string): Failure = | |
51 | + Failure(Context(kind: ckFailure, buffer: buffer, position: position, message: message)) | |
52 | + | |
53 | +# ParseError Exception | |
54 | +type | |
55 | + ParseError* = object of Exception | |
56 | + failure*: Failure | |
57 | + | |
58 | +proc newParseError*(failure: Failure): ref ParseError = | |
59 | + new(result) | |
60 | + result.failure = failure | |
61 | + result.msg = failure.message | |
62 | + | |
63 | +# Token | |
64 | +type | |
65 | + Token* = ref object of RootObj | |
66 | + buffer*: string | |
67 | + start*: int | |
68 | + stop*: int | |
69 | + value*: RootRef | |
70 | + | |
71 | +proc newToken*(buffer: string, start, stop: int, value: RootRef): Token = | |
72 | + Token(buffer: buffer, start: start, stop: stop, value: value) | |
73 | + | |
74 | +method getInput*(self: Token): string = | |
75 | + self.buffer.substr(self.start, self.stop) | |
76 | + | |
77 | +method getLength*(self: Token): int = | |
78 | + self.stop - self.stop | |
79 | + | |
80 | +method getLine*(self: Token): int = | |
81 | + self.buffer.findLineAndColumn(self.start)[0] | |
82 | + | |
83 | +method getColumn*(self: Token): int = | |
84 | + self.buffer.findLineAndColumn(self.start)[1] | |
85 | + | |
86 | +method `$`*(self: Token): string = | |
87 | + let (line, col) = findLineAndColumn(self.buffer, self.start); | |
88 | + "Token[" & $line & ":" & $col & "]: " & repr(self.value) # $self.value | |
89 | + | |
90 | +method hash*(self: Token): THash = | |
91 | + result = self.buffer.hash !& self.start.hash !& self.stop.hash #!& self.value.hash | |
92 | + result = !$result | |
93 | + | |
94 | +# We do not need to redefine ==, the generic one works fine. | |
95 | +# But let's see if we can | |
96 | +#method `==`(self, other: Token): bool = | |
97 | +# if (addr(self) == addr(other)): | |
98 | +# return true | |
99 | +# if other.isNil or self.isNil: | |
100 | +# return false | |
101 | +# self[] == other[] | |
102 | + | |
103 | + | |
104 | +method success[T]*(self: Context, value: T, position: int): Success = | |
105 | + newSuccess[T](self.buffer, position, value) | |
106 | + | |
107 | +method success[T]*(self: Context, value: T): Success = | |
108 | + self.success[T](value, self.position) | |
109 | + | |
110 | +method failure*(self: Context, message: string, position: int): Failure = | |
111 | + newFailure(self.buffer, position, message) | |
112 | + | |
113 | +method failure*(self: Context, message: string): Failure = | |
114 | + self.failure(message, self.position) | |
115 | + | |
116 | + | |
117 | +method isSuccess*(self: Context): bool = | |
118 | + false | |
119 | + | |
120 | +method isFailure*(self: Context): bool = | |
121 | + false | |
122 | + | |
123 | +# An abstract method, hmmm, which you can't really do | |
124 | +# in nim. Normally I would like to throw exception here, | |
125 | +# but we need to return a T for compiler to be happy... | |
126 | +method get*(self: Result): = | |
127 | + discard result | |
128 | + # Dirty trick to make compiler realize I return a T | |
129 | + # T(self) | |
130 | + | |
131 | +method get*(self: Failure): RootRef = | |
132 | + raise newParseError(self) | |
133 | + | |
134 | +method get*(self: Success): RootRef = | |
135 | + self.result | |
136 | + | |
137 | + | |
138 | +method getMessage*(self: Failure): string = | |
139 | + self.message | |
140 | + | |
141 | +method isSuccess*(self: Success): bool = | |
142 | + true | |
143 | + | |
144 | +method `$`*(self: Result): string = | |
145 | + let (line, col) = findLineAndColumn(self.buffer, self.position) | |
146 | + self.name & "[" & $line & ":" & $col & "]" | |
147 | + | |
148 | + | |
149 | +method `$`*(self: Success): string = | |
150 | + procCall($(Result(self))) & ": " & repr(self.result) | |
151 | + | |
152 | + | |
153 | +method isFailure*(self: Failure): bool = | |
154 | + true | |
155 | + | |
156 | +method `$`*(self: Failure): string = | |
157 | + procCall($(Result(self))) & ": " & self.message | |
158 | + | |
159 | +# The simplest form of unit tests | |
160 | +#when isMainModule: | |
161 | + | ... | ... |
functions.nim
0 → 100644
1 | +++ a/functions.nim | |
1 | +import sequtils | |
2 | + | |
3 | +proc firstOfList*[T](): proc(list: seq[T]): T = | |
4 | + # Returns a function that returns the first value of a seq. | |
5 | + result = proc (list: seq[T]): T = list[0] | |
6 | + | |
7 | +assert(firstOfList[int]()(@[1, 2]) == 1) | |
8 | + | |
9 | +# public static <T> Function<List<T>, T> firstOfList() { | |
10 | +# return (list) -> list.get(0); | |
11 | +# } | |
12 | + | |
13 | +proc lastOfList*[T](): proc(list: seq[T]): T = | |
14 | + # Returns a function that returns the last value of a seq. | |
15 | + result = proc (list: seq[T]): T = list[high(list)] | |
16 | + | |
17 | +assert(lastOfList[int]()(@[1, 2]) == 2) | |
18 | + | |
19 | +# public static <T> Function<List<T>, T> lastOfList() { | |
20 | +# return (list) -> list.get(list.size() - 1); | |
21 | +# } | |
22 | + | |
23 | +proc nthOfList*[T](index: int): proc(list: seq[T]): T = | |
24 | + ## Returns a function that returns the value at the given index. Negative indexes are counted from | |
25 | + ## the end of the list. | |
26 | + result = proc (list: seq[T]): T = | |
27 | + if index < 0: | |
28 | + result = list[list.len + index] | |
29 | + else: | |
30 | + result = list[index] | |
31 | + | |
32 | +assert(nthOfList[int](1)(@[1, 2]) == 2) | |
33 | +assert(nthOfList[int](-1)(@[1, 2]) == 2) | |
34 | +assert(nthOfList[int](-2)(@[1, 2]) == 1) | |
35 | + | |
36 | +# public static <T> Function<List<T>, T> nthOfList(int index) { | |
37 | +# return (list) -> list.get(index < 0 ? list.size() + index : index); | |
38 | +# } | |
39 | + | |
40 | +proc permutationOfList*[T](indexes: varargs[int]): proc(list: seq[T]): seq[T] = | |
41 | + ## Returns a function that returns the permutation of a given list. Negative indexes are counted | |
42 | + ## from the end of the list. | |
43 | + let inds = @indexes # Copying varargs (an array) to a seq so that the proc below can capture it | |
44 | + result = proc (list: seq[T]): seq[T] = | |
45 | + newSeq(result, 0) | |
46 | + for index in inds: | |
47 | + if index < 0: | |
48 | + result.add(list[list.len + index]) | |
49 | + else: | |
50 | + result.add(list[index]) | |
51 | + | |
52 | +assert(permutationOfList[int](1,0)(@[1, 2]) == @[2, 1]) | |
53 | +assert(permutationOfList[int](0,1)(@[1, 2]) == @[1, 2]) | |
54 | + | |
55 | +# public static <T> Function<List<T>, List<T>> permutationOfList(int... indexes) { | |
56 | +# return (list) -> { | |
57 | +# List<T> result = new ArrayList<>(indexes.length); | |
58 | +# for (int index : indexes) { | |
59 | +# result.add(list.get(index < 0 ? list.size() + index : index)); | |
60 | +# } | |
61 | +# return result; | |
62 | +# }; | |
63 | +# } | |
64 | + | |
65 | +proc withoutSeparators*[T](): proc(list: seq[T]): seq[T] = | |
66 | + ## Returns a function that skips the separators of a given list. | |
67 | + result = proc (list: seq[T]): seq[T] = | |
68 | + newSeq(result, 0) | |
69 | + for i in countup(0, high(list), 2): | |
70 | + result.add(list[i]) | |
71 | + | |
72 | +# public static <T> Function<List<T>, List<T>> withoutSeparators() { | |
73 | +# return (list) -> { | |
74 | +# List<T> result = new ArrayList<>(); | |
75 | +# for (int i = 0; i < list.size(); i += 2) { | |
76 | +# result.add(list.get(i)); | |
77 | +# } | |
78 | +# return result; | |
79 | +# }; | |
80 | +# } | |
81 | + | |
82 | +proc constant*[T,A](output: T): proc(input: A): T = | |
83 | + ## Returns a function that returns a constant value. | |
84 | + result = proc (input: A): T = output | |
85 | + | |
86 | +# public static <T> Function<Object, T> constant(T output) { | |
87 | +# return (input) -> output; | |
88 | +# } | |
89 | + | |
90 | + | |
91 | +assert(constant[string, string]("hey")("hy") == "hey") | ... | ... |
parser.nim
0 → 100644
petitparser.nim
0 → 100644
1 | +++ a/petitparser.nim | |
1 | +import context, functions | |
2 | + | |
3 | +# Parser | |
4 | +type | |
5 | + Parser* = ref object of RootObj | |
6 | + | |
7 | + | |
8 | +method parseOn*(self: Parser, context: Context): Result = | |
9 | + raise newException(Exception, "should be implemented by subclass") | |
10 | + | |
11 | +method parse*(self: Parser, input: string): Result = | |
12 | + self.parseOn(newContext(input, 0)) | |
13 | + | |
14 | +method accept*(self: Parser, input: string): bool = | |
15 | + self.parse(input).isSuccess | |
16 | + | |
17 | +method matches*[T](self: Parser, input: string): seq[T] = | |
18 | + result = @[] | |
19 | + self.andd.map(result.add).seq(any).orr(any).star.parse(input) | |
20 | + | |
21 | +method getChildren*(self: Parser): seq[Parser] = | |
22 | + @[] | |
23 | + | |
24 | +method hasEqualProperties*(self, other: Parser): bool = | |
25 | + true | |
26 | + | |
27 | +method replace*(self, source, target: Parser) = | |
28 | + # no referring parsers | |
29 | + discard | |
30 | + | |
31 | +method name*(self: Parser): string = | |
32 | + "Parser" | |
33 | + | |
34 | +method `$`*(self: Parser): string = | |
35 | + self.name | |
36 | + | |
37 | +# public String toString() { | |
38 | +# return getClass().getSimpleName(); | |
39 | +# } | |
40 | + | |
41 | + | |
42 | +# DelegateParser | |
43 | +type | |
44 | + DelegateParser* = ref object of Parser | |
45 | + delegate*: Parser | |
46 | + | |
47 | +proc newDelegateParser*(delegate: Parser): DelegateParser = | |
48 | + DelegateParser(delegate: delegate) | |
49 | + | |
50 | +method parseOn*(self: DelegateParser, context: Context): Result = | |
51 | + self.delegate.parseOn(context) | |
52 | + | |
53 | +method replace*(self: DelegateParser, source, target: Parser) = | |
54 | + procCall(Parser(self).replace(source, target)) | |
55 | + if self.delegate == source: | |
56 | + self.delegate = target | |
57 | + | |
58 | +method getChildren(self: DelegateParser): seq[Parser] = | |
59 | + @[self.delegate] | |
60 | + | |
61 | +method copy(self: DelegateParser): Parser = | |
62 | + newDelegateParser(self.delegate) | |
63 | + | |
64 | +method name*(self: DelegateParser): string = | |
65 | + "DelegateParser" | |
66 | + | |
67 | +## A parser that optionally parsers its delegate, or answers nil. | |
68 | +type | |
69 | + OptionalParser*[T] = ref object of DelegateParser | |
70 | + otherwise*: T | |
71 | + | |
72 | +proc newOptionalParser*[T](delegate: Parser, otherwise: T): OptionalParser = | |
73 | + OptionalParser(delegate: delegate, otherwise: otherwise) | |
74 | + | |
75 | +# public OptionalParser(Parser delegate, Object otherwise) { | |
76 | +# super(delegate); | |
77 | +# this.otherwise = otherwise; | |
78 | +# } | |
79 | + | |
80 | +method parseOn*(self: OptionalParser, context: Context): Result = | |
81 | + result = self.delegate.parseOn(context) | |
82 | + if result.isSuccess: | |
83 | + return | |
84 | + return context.success(self.otherwise) | |
85 | + | |
86 | + | |
87 | +# public Result parseOn(Context context) { | |
88 | +# Result result = delegate.parseOn(context); | |
89 | +# if (result.isSuccess()) { | |
90 | +# return result; | |
91 | +# } else { | |
92 | +# return context.success(otherwise); | |
93 | +# } | |
94 | +# } | |
95 | + | |
96 | +method hasEqualProperties*(self: OptionalParser, other: OptionalParser): bool = | |
97 | + procCall(Parser(self).hasEqualProperties(Parser(other))) and self.otherwise == other.otherwise | |
98 | + | |
99 | +# protected boolean hasEqualProperties(Parser other) { | |
100 | +# return super.hasEqualProperties(other) && | |
101 | +# Objects.equals(otherwise, ((OptionalParser) other).otherwise); | |
102 | +# } | |
103 | + | |
104 | +method copy*(self: OptionalParser): Parser = | |
105 | + newOptionalParser(self.delegate, self.otherwise) | |
106 | + | |
107 | +# @Override | |
108 | +# public Parser copy() { | |
109 | +# return new OptionalParser(delegate, otherwise); | |
110 | +# } | |
111 | + | |
112 | +method name*(self: OptionalParser): string = | |
113 | + "OptionalParser" | |
114 | + | |
115 | +# An abstract parser that repeatedly parses between 'min' and 'max' instances of its delegate. | |
116 | +type | |
117 | + RepeatingParser* = ref object of DelegateParser | |
118 | + min*: int | |
119 | + max*: int | |
120 | + | |
121 | +const UNBOUNDED = 1 | |
122 | + | |
123 | +proc newRepeatingParser*(delegate: Parser, min: int, max: int): RepeatingParser = | |
124 | + if min < 0: | |
125 | + raise newException(Exception, "Invalid min repetitions") | |
126 | + if max != UNBOUNDED and min > max: | |
127 | + raise newException(Exception, "Invalid max repetitions") | |
128 | + RepeatingParser(delegate: delegate, min: min, max: max) | |
129 | + | |
130 | +method hasEqualProperties*(self: RepeatingParser, other: RepeatingParser): bool = | |
131 | + # No super implementation in DelegateParser | |
132 | + procCall(Parser(self).hasEqualProperties(Parser(other))) and self.min == other.min and self.max == other.max | |
133 | + | |
134 | +method name*(self: RepeatingParser): string = | |
135 | + "RepeatingParser" | |
136 | + | |
137 | +method `$`*(self: RepeatingParser): string = | |
138 | + result = procCall($Parser(self)) & "[" & $self.min & ".." | |
139 | + if self.max == UNBOUNDED: | |
140 | + result = result & "*]" | |
141 | + else: | |
142 | + result = result & $self.max & "]" | |
143 | + | |
144 | +# public String toString() { | |
145 | +# return super.toString() + "[" + min + ".." + (max == UNBOUNDED ? "*" : max) + "]"; | |
146 | +# } | |
147 | + | |
148 | +# A greedy parser that repeatedly parses between 'min' and 'max' instances of its delegate. | |
149 | +type | |
150 | + PossessiveRepeatingParser = ref object of RepeatingParser | |
151 | + | |
152 | +proc newPossessiveRepeatingParser*(delegate: Parser, min, max: int): PossessiveRepeatingParser = | |
153 | + PossessiveRepeatingParser(newRepeatingParser(delegate, min, max)) | |
154 | + | |
155 | +method name*(self: PossessiveRepeatingParser): string = | |
156 | + "PossessiveRepeatingParser" | |
157 | + | |
158 | + | |
159 | +method copy*(self: PossessiveRepeatingParser): Parser = | |
160 | + newPossessiveRepeatingParser(self.delegate, self.min, self.max) | |
161 | + | |
162 | +method parseOn*[T](self: PossessiveRepeatingParser, context: Context): Result = | |
163 | + var | |
164 | + current = context | |
165 | + elements = newSeq[T]() | |
166 | + while elements.len < self.min: | |
167 | + result = self.delegate.parseOn(current) | |
168 | + if result.isFailure: | |
169 | + return | |
170 | + elements.add(get[T](result)) | |
171 | + current = result | |
172 | + while self.max == UNBOUNDED or elements.len < max: | |
173 | + result = self.delegate.parseOn(current) | |
174 | + if result.isFailure: | |
175 | + return current.success[T](elements) | |
176 | + elements.add(get[T](result)) | |
177 | + current = result | |
178 | + current.success[T](elements) | |
179 | + | |
180 | + | |
181 | +# Abstract parser that parses a list of things in some way (to be specified by the subclasses). | |
182 | +type | |
183 | + ListParser* = ref object of Parser | |
184 | + parsers*: seq[Parser] | |
185 | + | |
186 | +#proc newListParser*(parsers: seq[Parser] not nil): ListParser = | |
187 | +# ListParser(parsers: parsers) | |
188 | + | |
189 | +method replace*(self: ListParser, source, target: Parser) = | |
190 | + procCall(Parser(self).replace(source, target)) | |
191 | + for i in 0..high(self.parsers): | |
192 | + if self.parsers[i] == source: | |
193 | + self.parsers[i] = target | |
194 | + | |
195 | +# public void replace(Parser source, Parser target) { | |
196 | +# super.replace(source, target); | |
197 | +# for (int i = 0; i < parsers.length; i++) { | |
198 | +# if (parsers[i] == source) { | |
199 | +# parsers[i] = target; | |
200 | +# } | |
201 | +# } | |
202 | +# } | |
203 | + | |
204 | +method getChildren(self: ListParser): seq[Parser] = | |
205 | + self.parsers | |
206 | + | |
207 | +# public List<Parser> getChildren() { | |
208 | +# return Arrays.asList(parsers); | |
209 | +# } | |
210 | + | |
211 | + | |
212 | +method name*(self: ListParser): string = | |
213 | + "ListParser" | |
214 | + | |
215 | + | |
216 | +# A parser that parses a sequence of parsers. | |
217 | +type | |
218 | + SequenceParser* = ref object of ListParser | |
219 | + | |
220 | +proc newSequenceParser*(parsers: seq[Parser]): SequenceParser = | |
221 | + SequenceParser(parsers: parsers) | |
222 | + | |
223 | +method parseOn*[T](self: SequenceParser, context: Context): Result = | |
224 | + var | |
225 | + current = context | |
226 | + elements = newSeq[T](self.parsers.len) | |
227 | + for parser in self.parsers: | |
228 | + result = parser.parseOn(current) | |
229 | + if result.isFailure: | |
230 | + return | |
231 | + elements.add(get[T](result)) | |
232 | + current = result | |
233 | + current.success[T](elements) | |
234 | + | |
235 | +# public Result parseOn(Context context) { | |
236 | +# Context current = context; | |
237 | +# List<Object> elements = new ArrayList<>(parsers.length); | |
238 | +# for (Parser parser : parsers) { | |
239 | +# Result result = parser.parseOn(current); | |
240 | +# if (result.isFailure()) { | |
241 | +# return result; | |
242 | +# } | |
243 | +# elements.add(result.get()); | |
244 | +# current = result; | |
245 | +# } | |
246 | +# return current.success(elements); | |
247 | +# } | |
248 | + | |
249 | +method seq*(self: SequenceParser, others: varargs[Parser]): Parser = | |
250 | + newSequenceParser(self.parsers & @others) | |
251 | +# let all = newSeq[Parser](self.parsers.len + others.len) | |
252 | + | |
253 | +# public Parser seq(Parser... others) { | |
254 | +# Parser[] array = Arrays.copyOf(parsers, parsers.length + others.length); | |
255 | +# System.arraycopy(others, 0, array, parsers.length, others.length); | |
256 | +# return new SequenceParser(array); | |
257 | +# } | |
258 | + | |
259 | +method copy*(self: SequenceParser): Parser = | |
260 | + var parsersCopy = self.parsers | |
261 | + newSequenceParser(parsersCopy) | |
262 | + | |
263 | + | |
264 | +# public Parser copy() { | |
265 | +# return new SequenceParser(Arrays.copyOf(parsers, parsers.length)); | |
266 | +# } | |
267 | + | |
268 | +method name*(self: SequenceParser): string = | |
269 | + "SequenceParser" | |
270 | + | |
271 | +# An abstract parser that repeatedly parses between 'min' and 'max' instances of its delegate and | |
272 | +# that requires the input to be completed with a specified parser 'limit'. Subclasses provide | |
273 | +# repeating behavior as typically seen in regular expression implementations (non-blind). | |
274 | +type | |
275 | + LimitedRepeatingParser* = ref object of RepeatingParser | |
276 | + limit*: Parser | |
277 | + | |
278 | +proc newLimitedRepeatingParser*(delegate: Parser, limit: Parser not nil, min, max: int): LimitedRepeatingParser = | |
279 | + LimitedRepeatingParser(delegate: delegate, limit: limit, min: min, max: max) | |
280 | + | |
281 | +method getChildren(self: LimitedRepeatingParser): seq[Parser] = | |
282 | + @[self.delegate, self.limit] | |
283 | + | |
284 | +method replace*(self: LimitedRepeatingParser, source, target: Parser) = | |
285 | + procCall(DelegateParser(self).replace(source, target)) | |
286 | + if self.limit == source: | |
287 | + self.limit = target | |
288 | + | |
289 | +method name*(self: LimitedRepeatingParser): string = | |
290 | + "LimitedRepeatingParser" | |
291 | + | |
292 | + | |
293 | +# A greedy repeating parser, commonly seen in regular expression implementations. It aggressively | |
294 | +# consumes as much input as possible and then backtracks to meet the 'limit' condition. | |
295 | +type | |
296 | + GreedyRepeatingParser* = ref object of LimitedRepeatingParser | |
297 | + | |
298 | +proc newGreedyRepeatingParser*(delegate, limit: Parser, min, max: int): GreedyRepeatingParser = | |
299 | + GreedyRepeatingParser(delegate: delegate, limit: limit, min: min, max: max) | |
300 | + | |
301 | +method copy*(self: GreedyRepeatingParser): Parser = | |
302 | + newGreedyRepeatingParser(self.delegate, self.limit, self.min, self.max) | |
303 | + | |
304 | +#method parseOn*[T](self: GreedyRepeatingParser, context: Context): Result = | |
305 | +# result = self.delegate.parseOn(context) | |
306 | +# if result.isSuccess: | |
307 | +# return context.success(get[T](result)) | |
308 | + | |
309 | +method parseOn*[T](self: GreedyRepeatingParser, context: Context): Result = | |
310 | + var | |
311 | + current = context | |
312 | + elements = newSeq[T]() | |
313 | + while elements.len < self.min: | |
314 | + result = self.delegate.parseOn(current) | |
315 | + if result.isFailure: | |
316 | + return | |
317 | + elements.add(get[T](result)) | |
318 | + current = result | |
319 | + var contexts = newSeq[Context]() | |
320 | + contexts.add(current) | |
321 | + while self.max == UNBOUNDED or elements.len < max: | |
322 | + result = self.delegate.parseOn(current) | |
323 | + if result.isFailure: | |
324 | + break | |
325 | + elements.add(get[T](result)) | |
326 | + contexts.add(current = result) | |
327 | + while true: | |
328 | + var stop = self.limit.parseOn(contexts[contexts.high]) | |
329 | + if stop.isSuccess: | |
330 | + return contexts[contexts.high].success(elements) | |
331 | + if elements.len == 0: | |
332 | + return stop | |
333 | + contexts.pop() | |
334 | + elements.pop() | |
335 | + if contexts.len == 0: | |
336 | + return stop | |
337 | + | |
338 | + | |
339 | +discard """ | |
340 | + | |
341 | + public Result parseOn(Context context) { | |
342 | + Context current = context; | |
343 | + List<Object> elements = new ArrayList<>(); | |
344 | + while (elements.size() < min) { | |
345 | + Result result = delegate.parseOn(current); | |
346 | + if (result.isFailure()) { | |
347 | + return result; | |
348 | + } | |
349 | + elements.add(result.get()); | |
350 | + current = result; | |
351 | + } | |
352 | + List<Context> contexts = new ArrayList<>(); | |
353 | + contexts.add(current); | |
354 | + while (max == UNBOUNDED || elements.size() < max) { | |
355 | + Result result = delegate.parseOn(current); | |
356 | + if (result.isFailure()) { | |
357 | + break; | |
358 | + } | |
359 | + elements.add(result.get()); | |
360 | + contexts.add(current = result); | |
361 | + } | |
362 | + while (true) { | |
363 | + Result stop = limit.parseOn(contexts.get(contexts.size() - 1)); | |
364 | + if (stop.isSuccess()) { | |
365 | + return contexts.get(contexts.size() - 1).success(elements); | |
366 | + } | |
367 | + if (elements.isEmpty()) { | |
368 | + return stop; | |
369 | + } | |
370 | + contexts.remove(contexts.size() - 1); | |
371 | + elements.remove(elements.size() - 1); | |
372 | + if (contexts.isEmpty()) { | |
373 | + return stop; | |
374 | + } | |
375 | + } | |
376 | + } | |
377 | + | |
378 | +} | |
379 | +""" | |
380 | + | |
381 | +# A parser that uses the first parser that succeeds. | |
382 | +type | |
383 | + ChoiceParser* = ref object of ListParser | |
384 | + | |
385 | +proc newChoiceParser*(parsers: varargs[Parser]): ChoiceParser = | |
386 | + ChoiceParser(parsers: @parsers) | |
387 | + | |
388 | +method orr*(self: ChoiceParser, others: varargs[Parser]): Parser = | |
389 | + ## Returns a parser that accepts the receiver or `other`. The resulting parser returns the | |
390 | + ## parse result of the receiver, if the receiver fails it returns the parse result of `other` | |
391 | + ## (exclusive ordered choice). | |
392 | + newChoiceParser(self.parsers & @others) | |
393 | + | |
394 | +method copy*(self: ChoiceParser): Parser = | |
395 | + let parsersCopy = self.parsers | |
396 | + newChoiceParser(parsersCopy) | |
397 | + | |
398 | +method parseOn*(self: ChoiceParser, context: Context): Result = | |
399 | + for parser in self.parsers: | |
400 | + result = parser.parseOn(context) | |
401 | + if result.isSuccess: | |
402 | + return | |
403 | + | |
404 | +# public Result parseOn(Context context) { | |
405 | +# Result result = null; | |
406 | +# for (Parser parser : parsers) { | |
407 | +# result = parser.parseOn(context); | |
408 | +# if (result.isSuccess()) { | |
409 | +# return result; | |
410 | +# } | |
411 | +# } | |
412 | +# return result; | |
413 | +# } | |
414 | + | |
415 | +# The and-predicate, a parser that succeeds whenever its delegate does, but does not consume the | |
416 | +# input stream [Parr 1994, 1995]. | |
417 | +type | |
418 | + AndParser* = ref object of DelegateParser | |
419 | + | |
420 | +proc newAndParser*(delegate: Parser): AndParser = | |
421 | + AndParser(delegate) | |
422 | + | |
423 | +method parseOn*[T](self: AndParser, context: Context): Result = | |
424 | + result = self.delegate.parseOn(context) | |
425 | + if result.isSuccess: | |
426 | + return context.success(get[T](result)) | |
427 | + | |
428 | +method copy*(self: AndParser): Parser = | |
429 | + newAndParser(self.delegate) | |
430 | + | |
431 | +# Testing a type class to match the Java interface | |
432 | +# This means, a ContinuationHandler is any type which | |
433 | +# you can call `apply` on, with the given arguments. | |
434 | +type | |
435 | + ContinuationHandler = generic handler | |
436 | + handler.callParseOn(Parser, Context) is Result | |
437 | + #handler.apply(proc(c: Context): Result, Context) is Result | |
438 | + | |
439 | +# Just a sample ContinuationHandler type | |
440 | +type | |
441 | + Sammy = ref object | |
442 | +proc callParseOn*(self: Sammy, p: Parser, c: Context): Result = | |
443 | + p.parseOn(c) | |
444 | + | |
445 | +# Continuation parser that when activated captures a continuation function and passes it together | |
446 | +# with the current context into the handler. | |
447 | +type | |
448 | + ContinuationParser*[T] = ref object of DelegateParser | |
449 | + handler*: T | |
450 | + | |
451 | +proc newContinuationParser*(delegate: Parser, handler: ContinuationHandler): ContinuationParser = | |
452 | + ContinuationParser(delegate: delegate, handler: handler) | |
453 | + | |
454 | +# TODO | |
455 | +method parseOn*(self: ContinuationParser, context: Context): Result = | |
456 | + self.handler.callParseOn(self, context) | |
457 | + | |
458 | +method copy*(self: ContinuationParser): Parser = | |
459 | + newContinuationParser(self.delegate, self.handler) | |
460 | + | |
461 | +method hasEqualProperties*(self: ContinuationParser, other: ContinuationParser): bool = | |
462 | + procCall(Parser(self).hasEqualProperties(Parser(other))) and self.handler == other.handler | |
463 | + | |
464 | + | |
465 | + | |
466 | +# The not-predicate, a parser that succeeds whenever its delegate does not, but consumes no input [Parr 1994, 1995]. | |
467 | +type | |
468 | + NotParser* = ref object of DelegateParser | |
469 | + message*: string | |
470 | + | |
471 | +proc newNotParser*(delegate: Parser, message: string): NotParser = | |
472 | + NotParser(delegate: delegate, message: message) | |
473 | + | |
474 | +method parseOn*[T](self: NotParser, context: Context): Result = | |
475 | + if self.delegate.parseOn(context).isFailure: | |
476 | + return context.success(nil) | |
477 | + else: | |
478 | + return context.failure(self.message) | |
479 | + | |
480 | +method hasEqualProperties*(self: NotParser, other: NotParser): bool = | |
481 | + procCall(Parser(self).hasEqualProperties(Parser(other))) and self.message == other.message | |
482 | + | |
483 | +method copy*(self: NotParser): NotParser = | |
484 | + newNotParser(self.delegate, self.message) | |
485 | + | |
486 | +method `$`*(self: NotParser): string = | |
487 | + procCall($Parser(self)) & "[" & self.message & "]" | |
488 | + | |
489 | + | |
490 | + | |
491 | + | |
492 | + | |
493 | +# Parses a single character. | |
494 | +type | |
495 | + CharacterParser* = ref object of Parser | |
496 | + | |
497 | +proc newCharacterParser*(predicate: CharacterPredicate, message: string): Parser = | |
498 | + CharacterParser(predicate, message) | |
499 | + | |
500 | +proc off*(predicate: CharacterPredicate, message: string): Parser = | |
501 | + newCharacterParser(predicate, message) | |
502 | + | |
503 | +discard """ | |
504 | + /** | |
505 | + * Returns a parser that accepts a specific {@link CharacterPredicate}. | |
506 | + */ | |
507 | + public static Parser of(CharacterPredicate predicate, String message) { | |
508 | + return new CharacterParser(predicate, message); | |
509 | + } | |
510 | + | |
511 | + /** | |
512 | + * Returns a parser that accepts a specific {@code character}. | |
513 | + */ | |
514 | + public static Parser of(char character) { | |
515 | + return of(character, "'" + character + "' expected"); | |
516 | + } | |
517 | + | |
518 | + public static Parser of(char character, String message) { | |
519 | + return of(CharacterPredicate.of(character), message); | |
520 | + } | |
521 | + | |
522 | + /** | |
523 | + * Returns a parser that accepts any character. | |
524 | + */ | |
525 | + public static Parser any() { | |
526 | + return any("any character expected"); | |
527 | + } | |
528 | + | |
529 | + public static Parser any(String message) { | |
530 | + return of(CharacterPredicate.any(), message); | |
531 | + } | |
532 | + | |
533 | + /** | |
534 | + * Returns a parser that accepts any of the provided characters. | |
535 | + */ | |
536 | + public static Parser anyOf(String chars) { | |
537 | + return anyOf(chars, "any of '" + chars + "' expected"); | |
538 | + } | |
539 | + | |
540 | + public static Parser anyOf(String chars, String message) { | |
541 | + return of(CharacterPredicate.anyOf(chars), message); | |
542 | + } | |
543 | + | |
544 | + /** | |
545 | + * Returns a parser that accepts no character. | |
546 | + */ | |
547 | + public static Parser none() { | |
548 | + return none("no character expected"); | |
549 | + } | |
550 | + | |
551 | + public static Parser none(String message) { | |
552 | + return of(CharacterPredicate.none(), message); | |
553 | + } | |
554 | + | |
555 | + /** | |
556 | + * Returns a parser that accepts none of the provided characters. | |
557 | + */ | |
558 | + public static Parser noneOf(String chars) { | |
559 | + return noneOf(chars, "none of '" + chars + "' expected"); | |
560 | + } | |
561 | + | |
562 | + public static Parser noneOf(String chars, String message) { | |
563 | + return of(CharacterPredicate.noneOf(chars), message); | |
564 | + } | |
565 | + | |
566 | + /** | |
567 | + * Returns a parser that accepts a single digit. | |
568 | + */ | |
569 | + public static Parser digit() { | |
570 | + return digit("digit expected"); | |
571 | + } | |
572 | + | |
573 | + public static Parser digit(String message) { | |
574 | + return new CharacterParser(Character::isDigit, message); | |
575 | + } | |
576 | + | |
577 | + /** | |
578 | + * Returns a parser that accepts a single letter. | |
579 | + */ | |
580 | + public static Parser letter() { | |
581 | + return letter("letter expected"); | |
582 | + } | |
583 | + | |
584 | + public static Parser letter(String message) { | |
585 | + return of(Character::isLetter, message); | |
586 | + } | |
587 | + | |
588 | + /** | |
589 | + * Returns a parser that accepts an lower-case letter. | |
590 | + */ | |
591 | + public static Parser lowerCase() { | |
592 | + return lowerCase("lowercase letter expected"); | |
593 | + } | |
594 | + | |
595 | + public static Parser lowerCase(String message) { | |
596 | + return of(Character::isLowerCase, message); | |
597 | + } | |
598 | + | |
599 | + /** | |
600 | + * Returns a parser that accepts a specific character pattern. | |
601 | + * <p> | |
602 | + * Characters match themselves. A dash {@code -} between two characters matches the range of those | |
603 | + * characters. A caret {@code ^} at the beginning negates the pattern. | |
604 | + */ | |
605 | + public static Parser pattern(String pattern) { | |
606 | + return pattern(pattern, "[" + pattern + "] expected"); | |
607 | + } | |
608 | + | |
609 | + public static Parser pattern(String pattern, String message) { | |
610 | + return of(CharacterPredicate.pattern(pattern), message); | |
611 | + } | |
612 | + | |
613 | + /** | |
614 | + * Returns a parser that accepts a specific character range. | |
615 | + */ | |
616 | + public static Parser range(char start, char stop) { | |
617 | + return range(start, stop, start + ".." + stop + " expected"); | |
618 | + } | |
619 | + | |
620 | + public static Parser range(char start, char stop, String message) { | |
621 | + return of(CharacterPredicate.range(start, stop), message); | |
622 | + } | |
623 | + | |
624 | + /** | |
625 | + * Returns a parser that accepts an upper-case letter. | |
626 | + */ | |
627 | + public static Parser upperCase() { | |
628 | + return upperCase("uppercase letter expected"); | |
629 | + } | |
630 | + | |
631 | + public static Parser upperCase(String message) { | |
632 | + return of(Character::isUpperCase, message); | |
633 | + } | |
634 | + | |
635 | + /** | |
636 | + * Returns a parser that accepts a single whitespace. | |
637 | + */ | |
638 | + public static Parser whitespace() { | |
639 | + return whitespace("whitespace expected"); | |
640 | + } | |
641 | + | |
642 | + public static Parser whitespace(String message) { | |
643 | + return of(Character::isWhitespace, message); | |
644 | + } | |
645 | + | |
646 | + /** | |
647 | + * Returns a parser that accepts a single letter or digit. | |
648 | + */ | |
649 | + public static Parser word() { | |
650 | + return word("letter or digit expected"); | |
651 | + } | |
652 | + | |
653 | + public static Parser word(String message) { | |
654 | + return of(Character::isLetterOrDigit, message); | |
655 | + } | |
656 | + | |
657 | + private final CharacterPredicate matcher; | |
658 | + private final String message; | |
659 | + | |
660 | + private CharacterParser(CharacterPredicate matcher, String message) { | |
661 | + this.matcher = Objects.requireNonNull(matcher, "Undefined matcher"); | |
662 | + this.message = Objects.requireNonNull(message, "Undefined message"); | |
663 | + } | |
664 | + | |
665 | + @Override | |
666 | + public Result parseOn(Context context) { | |
667 | + String buffer = context.getBuffer(); | |
668 | + int position = context.getPosition(); | |
669 | + if (position < buffer.length()) { | |
670 | + char result = buffer.charAt(position); | |
671 | + if (matcher.test(result)) { | |
672 | + return context.success(result, position + 1); | |
673 | + } | |
674 | + } | |
675 | + return context.failure(message); | |
676 | + } | |
677 | + | |
678 | + @Override | |
679 | + public Parser neg(String message) { | |
680 | + return of(matcher.not(), message); | |
681 | + } | |
682 | + | |
683 | + @Override | |
684 | + protected boolean hasEqualProperties(Parser other) { | |
685 | + return super.hasEqualProperties(other) && | |
686 | + Objects.equals(matcher, ((CharacterParser) other).matcher) && | |
687 | + Objects.equals(message, ((CharacterParser) other).message); | |
688 | + } | |
689 | + | |
690 | + @Override | |
691 | + public Parser copy() { | |
692 | + return of(matcher, message); | |
693 | + } | |
694 | + | |
695 | + @Override | |
696 | + public String toString() { | |
697 | + return super.toString() + "[" + message + "]"; | |
698 | + } | |
699 | +""" | |
700 | + | |
701 | + | |
702 | + | |
703 | +# /** | |
704 | +# * Returns a list of all successful overlapping parses of the {@code input}. | |
705 | +# */ | |
706 | +# @SuppressWarnings("unchecked") | |
707 | +# public <T> List<T> matches(String input) { | |
708 | +# List<Object> list = new ArrayList<>(); | |
709 | +# this.and().map(list::add).seq(any()).or(any()).star().parse(input); | |
710 | +# return (List<T>) list; | |
711 | +# } | |
712 | + | |
713 | +method matchesSkipping*[T](self: Parser, input: string): seq[T] = | |
714 | + result = @[] | |
715 | + self.map(result.add).`or`(any).star.parse(input) | |
716 | + | |
717 | +# /** | |
718 | +# * Returns a list of all successful non-overlapping parses of the {@code input}. | |
719 | +# */ | |
720 | +# @SuppressWarnings("unchecked") | |
721 | +# public <T> List<T> matchesSkipping(String input) { | |
722 | +# List<Object> list = new ArrayList<>(); | |
723 | +# this.map(list::add).or(any()).star().parse(input); | |
724 | +# return (List<T>) list; | |
725 | +# } | |
726 | + | |
727 | + | |
728 | +method repeat*(self: Parser, min, max: int): Parser = | |
729 | + ## Returns a parser that accepts the receiver between `min` and `max` times. The | |
730 | + ## resulting parser returns a list of the parse results of the receiver. | |
731 | + ## | |
732 | + ## This is a greedy and blind implementation that tries to consume as much input as possible and | |
733 | + ## that does not consider what comes afterwards. | |
734 | + newPossessiveRepeatingParser(self, min, max) | |
735 | + | |
736 | +# public Parser repeat(int min, int max) { | |
737 | +# return new PossessiveRepeatingParser(this, min, max); | |
738 | +# } | |
739 | + | |
740 | + | |
741 | +method optional*[T](self: Parser, otherwise: T): Parser = | |
742 | + ## Returns new parser that accepts the receiver, if possible. | |
743 | + ## The returned value can be provided as `otherwise`. | |
744 | + newOptionalParser(self, otherwise) | |
745 | + | |
746 | +# public Parser optional(Object otherwise) { | |
747 | +# return new OptionalParser(this, otherwise); | |
748 | +# } | |
749 | + | |
750 | +method optional*[T](self: Parser): Parser = | |
751 | + ## Returns new parser that accepts the receiver, if possible. The resulting parser returns the | |
752 | + ## result of the receiver, or `nil` if not applicable. | |
753 | + optional[T](self, nil) | |
754 | + | |
755 | +# public Parser optional() { | |
756 | +# return optional(null); | |
757 | +# } | |
758 | + | |
759 | +method start*(self: Parser): Parser = | |
760 | + ## Returns a parser that accepts the receiver zero or more times. The resulting parser returns a | |
761 | + ## list of the parse results of the receiver. | |
762 | + ## | |
763 | + ## This is a greedy and blind implementation that tries to consume as much input as possible and | |
764 | + ## that does not consider what comes afterwards. | |
765 | + self.repeat(0, UNBOUNDED) | |
766 | + | |
767 | +# public Parser star() { | |
768 | +# return repeat(0, RepeatingParser.UNBOUNDED); | |
769 | +# } | |
770 | + | |
771 | +# Forward dec | |
772 | +method repeatGreedy*(self, limit: Parser, min, max: int): Parser | |
773 | + | |
774 | +method starGreedy*(self, limit: Parser): Parser = | |
775 | + ## Returns a parser that parses the receiver zero or more times until it reaches a `limit`. | |
776 | + ## This is a greedy non-blind implementation of the `star <#star>`_ operator. | |
777 | + ## The `limit` is not consumed. | |
778 | + self.repeatGreedy(limit, 0, UNBOUNDED) | |
779 | + | |
780 | +# public Parser starGreedy(Parser limit) { | |
781 | +# return repeatGreedy(limit, 0, RepeatingParser.UNBOUNDED); | |
782 | +# } | |
783 | +method repeatLazy*(self, limit: Parser, min, max: int): Parser | |
784 | +method starLazy*(self, limit: Parser): Parser = | |
785 | + ## Returns a parser that parses the receiver zero or more times until it reaches a `limit`. | |
786 | + ## This is a lazy non-blind implementation of the `star <#star>`_ operator. | |
787 | + ## The `limit` is not consumed. | |
788 | + self.repeatLazy(limit, 0, UNBOUNDED) | |
789 | + | |
790 | + | |
791 | +# public Parser starLazy(Parser limit) { | |
792 | +# return repeatLazy(limit, 0, RepeatingParser.UNBOUNDED); | |
793 | +# } | |
794 | + | |
795 | +method plus*(self: Parser): Parser = | |
796 | + ## Returns a parser that accepts the receiver one or more times. The resulting parser returns a | |
797 | + ## list of the parse results of the receiver. | |
798 | + ## | |
799 | + ## This is a greedy and blind implementation that tries to consume as much input as possible and | |
800 | + ## that does not consider what comes afterwards. | |
801 | + self.repeat(1, UNBOUNDED) | |
802 | + | |
803 | +# public Parser plus() { | |
804 | +# return repeat(1, RepeatingParser.UNBOUNDED); | |
805 | +# } | |
806 | + | |
807 | + | |
808 | +method plusGreedy*(self, limit: Parser): Parser = | |
809 | + ## Returns a parser that parses the receiver one or more times until it reaches `limit`. | |
810 | + ## This is a reedy non-blind implementation of the `plus <#plus>`_ operator. | |
811 | + ## The `limit` is not consumed. | |
812 | + self.repeatGreedy(limit, 1, UNBOUNDED) | |
813 | + | |
814 | +# public Parser plusGreedy(Parser limit) { | |
815 | +# return repeatGreedy(limit, 1, RepeatingParser.UNBOUNDED); | |
816 | +# } | |
817 | + | |
818 | + | |
819 | +method plusLazy*(self, limit: Parser): Parser = | |
820 | + ## Returns a parser that parses the receiver one or more times until it reaches a `limit`. | |
821 | + ## This is a lazy non-blind implementation of the `plus <#plus>`_ operator. | |
822 | + ## The `limit` is not consumed. | |
823 | + self.repeatLazy(limit, 1, UNBOUNDED) | |
824 | + | |
825 | + | |
826 | +# public Parser plusLazy(Parser limit) { | |
827 | +# return repeatLazy(limit, 1, RepeatingParser.UNBOUNDED); | |
828 | +# } | |
829 | + | |
830 | + | |
831 | +method repeatGreedy*(self, limit: Parser, min, max: int): Parser = | |
832 | + ## Returns a parser that parses the receiver at least `min` and at most `max` times | |
833 | + ## until it reaches a {@code limit}. This is a greedy non-blind implementation of the | |
834 | + ## `repeat <#repeat>`_ operator. The `limit` is not consumed. | |
835 | + newGreedyRepeatingParser(self, limit, min, max) | |
836 | + | |
837 | +# public Parser repeatGreedy(Parser limit, int min, int max) { | |
838 | +# return new GreedyRepeatingParser(this, limit, min, max); | |
839 | +# } | |
840 | + | |
841 | + | |
842 | +method repeatLazy*(self, limit: Parser, min, max: int): Parser = | |
843 | + ## Returns a parser that parses the receiver at least `min` and at most `max` times | |
844 | + ## until it reaches a `limit`. This is a lazy non-blind implementation of the | |
845 | + ## `repeat <#repeat>`_ operator. The `limit` is not consumed. | |
846 | + newGreedyRepeatingParser(self, limit, min, max) | |
847 | + | |
848 | +# public Parser repeatLazy(Parser limit, int min, int max) { | |
849 | +# return new LazyRepeatingParser(this, limit, min, max); | |
850 | +# } | |
851 | + | |
852 | +method times*(self: Parser, count: int): Parser = | |
853 | + ## Returns a parser that accepts the receiver exactly `count` times. | |
854 | + ## The resulting parser returns a list of the parse results of the receiver. | |
855 | + self.repeat(count, count) | |
856 | + | |
857 | +# public Parser times(int count) { | |
858 | +# return repeat(count, count); | |
859 | +# } | |
860 | + | |
861 | + | |
862 | +method seq*(self: Parser, others: varargs[Parser]): Parser = | |
863 | + ## Returns a parser that accepts the receiver followed by `others`. The resulting parser | |
864 | + ## returns a list of the parse result of the receiver followed by the parse result of `others`. | |
865 | + ## Calling this method on an existing sequence code not nest this sequence into a new one, | |
866 | + ## but instead augments the existing sequence with `others`. | |
867 | + | |
868 | + # Alternative low level version of addFirst | |
869 | + #var s = newSeq[Parser](others.len + 1) | |
870 | + #s[0] = self | |
871 | + #var j = 1 | |
872 | + #for p in others: | |
873 | + # s[j] = p | |
874 | + # inc(j) | |
875 | + #newSequenceParser(s) | |
876 | + | |
877 | + # Quick version of addFirst | |
878 | + newSequenceParser(@[self] & @others) | |
879 | + | |
880 | +# public Parser seq(Parser... others) { | |
881 | +# Parser[] parsers = new Parser[1 + others.length]; | |
882 | +# parsers[0] = this; | |
883 | +# System.arraycopy(others, 0, parsers, 1, others.length); | |
884 | +# return new SequenceParser(parsers); | |
885 | +# } | |
886 | + | |
887 | +method orr*(self: Parser, others: varargs[Parser]): Parser = | |
888 | + ## Returns a parser that accepts the receiver or `other`. The resulting parser returns the | |
889 | + ## parse result of the receiver, if the receiver fails it returns the parse result of `other` | |
890 | + ## (exclusive ordered choice). | |
891 | + newChoiceParser(@[self] & @others) | |
892 | + | |
893 | +# public Parser or(Parser... others) { | |
894 | +# Parser[] parsers = new Parser[1 + others.length]; | |
895 | +# parsers[0] = this; | |
896 | +# System.arraycopy(others, 0, parsers, 1, others.length); | |
897 | +# return new ChoiceParser(parsers); | |
898 | +# } | |
899 | + | |
900 | +method andd*(self: Parser): Parser = | |
901 | + ## Returns a parser (logical and-predicate) that succeeds whenever the receiver does, but never | |
902 | + ## consumes input. | |
903 | + newAndParser(self) | |
904 | + | |
905 | +# public Parser and() { | |
906 | +# return new AndParser(this); | |
907 | +# } | |
908 | + | |
909 | +method callCC*(self: Parser, handler: ContinuationHandler): Parser = | |
910 | + ## Returns a parser that is called with its current continuation. | |
911 | + newContinuationParser(self, handler) | |
912 | + | |
913 | +# public Parser callCC(ContinuationParser.ContinuationHandler handler) { | |
914 | +# return new ContinuationParser(this, handler); | |
915 | +# } | |
916 | + | |
917 | +method nott*(self: Parser): Parser = | |
918 | + ## Returns a parser (logical not-predicate) that succeeds whenever the receiver fails, but never | |
919 | + ## consumes input. | |
920 | + raise newException(Exception, "unexpected call to nott") | |
921 | + | |
922 | +# public Parser not() { | |
923 | +# return not("unexpected"); | |
924 | +# } | |
925 | + | |
926 | +method nott*(self: Parser, message: string): Parser = | |
927 | + ## Returns a parser (logical not-predicate) that succeeds whenever the receiver fails, but never | |
928 | + ## consumes input. | |
929 | + newNotParser(self, message) | |
930 | + | |
931 | +# public Parser not(String message) { | |
932 | +# return new NotParser(this, message); | |
933 | +# } | |
934 | + | |
935 | +method neg*(self: Parser, message: string): Parser = | |
936 | + ## Returns a parser that consumes any input token (character), but the receiver. | |
937 | + self.nott(message).seq(CharacterParser.any()).pick(1) | |
938 | + | |
939 | +# public Parser neg(String message) { | |
940 | +# return not(message).seq(CharacterParser.any()).pick(1); | |
941 | +# } | |
942 | + | |
943 | +method neg*(self: Parser): Parser = | |
944 | + ## Returns a parser that consumes any input token (character), but the receiver. | |
945 | + self.neg($self & " not expected") | |
946 | + | |
947 | +# public Parser neg() { | |
948 | +# return neg(this + " not expected"); | |
949 | +# } | |
950 | + | |
951 | +method flatten*(self: Parser): Parser = | |
952 | + ## Returns a parser that discards the result of the receiver, and returns a sub-string of the | |
953 | + ## consumed range in the string/list being parsed. | |
954 | + newFlattenParser(self) | |
955 | + | |
956 | +# public Parser flatten() { | |
957 | +# return new FlattenParser(this); | |
958 | +# } | |
959 | + | |
960 | +method token*(self: Parser): Parser = | |
961 | + ## Returns a parser that returns a {@link Token}. The token carries the parsed value of the | |
962 | + ## receiver {@link Token#getValue()}, as well as the consumed input {@link Token#getInput()} from | |
963 | + ## {@link Token#getStart()} to {@link Token#getStop()} of the input being parsed. | |
964 | + newTokenParser(self) | |
965 | + | |
966 | +# public Parser token() { | |
967 | +# return new TokenParser(this); | |
968 | +# } | |
969 | + | |
970 | +method trim*(self: Parser): Parser = | |
971 | + ## Returns a parser that consumes whitespace before and after the receiver. | |
972 | + self.trim(CharacterParser.whitespace()) | |
973 | + | |
974 | +# public Parser trim() { | |
975 | +# return trim(CharacterParser.whitespace()); | |
976 | +# } | |
977 | + | |
978 | +method trim*(self: Parser, both: Parser): Parser = | |
979 | + ## Returns a parser that consumes input on `both` sides of the receiver. | |
980 | + self.trim(both, both) | |
981 | + | |
982 | +# public Parser trim(Parser both) { | |
983 | +# return trim(both, both); | |
984 | +# } | |
985 | + | |
986 | +method trim*(self, before, after: Parser): Parser = | |
987 | + ## Returns a parser that consumes input {@code before} and {@code after} the receiver. | |
988 | + newTrimmingParser(self, before, after) | |
989 | + | |
990 | +# public Parser trim(Parser before, Parser after) { | |
991 | +# return new TrimmingParser(this, before, after); | |
992 | +# } | |
993 | + | |
994 | +method endd*(self: Parser): Parser = | |
995 | + ## Returns a parser that succeeds only if the receiver consumes the complete input. | |
996 | + self.endd("end of input expected") | |
997 | + | |
998 | +# public Parser end() { | |
999 | +# return end("end of input expected"); | |
1000 | +# } | |
1001 | + | |
1002 | +method endd*(self: Parser, message: string): Parser = | |
1003 | + ## Returns a parser that succeeds only if the receiver consumes the complete input, otherwise | |
1004 | + ## return a failure with the {@code message}. | |
1005 | + newEndOfInputParser(self, message) | |
1006 | + | |
1007 | +# public Parser end(String message) { | |
1008 | +# return new EndOfInputParser(this, message); | |
1009 | +# } | |
1010 | + | |
1011 | +method settable*(self: Parser): SettableParser = | |
1012 | + ## Returns a parser that points to the receiver, but can be changed to point to something else at | |
1013 | + ## a later point in time. | |
1014 | + newSettableParserWith(self) | |
1015 | + | |
1016 | +# public SettableParser settable() { | |
1017 | +# return SettableParser.with(this); | |
1018 | +# } | |
1019 | + | |
1020 | +method map*[A, B](self: Parser, function: proc (x: A): B {.closure.}): Parser = | |
1021 | + ## Returns a parser that evaluates a {@code function} as the production action on success of the | |
1022 | + ## receiver. | |
1023 | + newActionParser(self, function) | |
1024 | + | |
1025 | +# public <A, B> Parser map(Function<A, B> function) { | |
1026 | +# return new ActionParser<>(this, function); | |
1027 | +# } | |
1028 | + | |
1029 | +method pick*(self: Parser, index: int): Parser = | |
1030 | + ## Returns a parser that transform a successful parse result by returning the element at {@code | |
1031 | + ## index} of a list. A negative index can be used to access the elements from the back of the | |
1032 | + ## list. | |
1033 | + self.map(nthOfList(index)) | |
1034 | + | |
1035 | +# public Parser pick(int index) { | |
1036 | +# return map(Functions.nthOfList(index)); | |
1037 | +# } | |
1038 | + | |
1039 | +method permute*(self: Parser, indexes: varargs[int]): Parser = | |
1040 | + ## Returns a parser that transforms a successful parse result by returning the permuted elements | |
1041 | + ## at {@code indexes} of a list. Negative indexes can be used to access the elements from the back | |
1042 | + ## of the list. | |
1043 | + self.map(permutationOfList(indexes)) | |
1044 | + | |
1045 | + | |
1046 | +# public Parser permute(int... indexes) { | |
1047 | +# return this.map(Functions.permutationOfList(indexes)); | |
1048 | +# } | |
1049 | + | |
1050 | + ## Returns a new parser that parses the receiver one or more times, separated | |
1051 | + ## by a {@code separator}. | |
1052 | + newSequenceParser(self, newSequenceParser(separator, self).star()) | |
1053 | + .map( | |
1054 | + | |
1055 | +# public Parser separatedBy(Parser separator) { | |
1056 | +# return new SequenceParser(this, new SequenceParser(separator, this).star()) | |
1057 | +# .map(new Function<List<List<List<Object>>>, List<Object>>() { | |
1058 | +# @Override | |
1059 | +# public List<Object> apply(List<List<List<Object>>> input) { | |
1060 | +# List<Object> result = new ArrayList<>(); | |
1061 | +# result.add(input.get(0)); | |
1062 | +# input.get(1).forEach(result::addAll); | |
1063 | +# return result; | |
1064 | +# } | |
1065 | +# }); | |
1066 | +# } | |
1067 | + | |
1068 | +discard """ | |
1069 | + /** | |
1070 | + * Returns a new parser that parses the receiver one or more times, separated | |
1071 | + * and possibly ended by a {@code separator}." | |
1072 | + */ | |
1073 | + public Parser delimitedBy(Parser separator) { | |
1074 | + return separatedBy(separator) | |
1075 | + .seq(separator.optional()) | |
1076 | + .map(new Function<List<List<Object>>, List<Object>>() { | |
1077 | + @Override | |
1078 | + public List<Object> apply(List<List<Object>> input) { | |
1079 | + List<Object> result = new ArrayList<>(input.get(0)); | |
1080 | + if (input.get(1) != null) { | |
1081 | + result.add(input.get(1)); | |
1082 | + } | |
1083 | + return result; | |
1084 | + } | |
1085 | + }); | |
1086 | + } | |
1087 | + | |
1088 | + /** | |
1089 | + * Returns a shallow copy of the receiver. | |
1090 | + */ | |
1091 | + public abstract Parser copy(); | |
1092 | + | |
1093 | + /** | |
1094 | + * Recursively tests for structural similarity of two parsers. | |
1095 | + * | |
1096 | + * The code can automatically deals with recursive parsers and parsers that refer to other | |
1097 | + * parsers. This code is supposed to be overridden by parsers that add other state. | |
1098 | + */ | |
1099 | + public boolean isEqualTo(Parser other) { | |
1100 | + return isEqualTo(other, new HashSet<>()); | |
1101 | + } | |
1102 | + | |
1103 | + /** | |
1104 | + * Recursively tests for structural similarity of two parsers. | |
1105 | + */ | |
1106 | + protected boolean isEqualTo(Parser other, Set<Parser> seen) { | |
1107 | + if (this.equals(other) || seen.contains(this)) { | |
1108 | + return true; | |
1109 | + } | |
1110 | + seen.add(this); | |
1111 | + return getClass().equals(other.getClass()) | |
1112 | + && hasEqualProperties(other) | |
1113 | + && hasEqualChildren(other, seen); | |
1114 | + } | |
1115 | + | |
1116 | + /** | |
1117 | + * Compares the properties of two parsers. | |
1118 | + * | |
1119 | + * Override this method in all subclasses that add new state. | |
1120 | + */ | |
1121 | + protected boolean hasEqualProperties(Parser other) { | |
1122 | + return true; | |
1123 | + } | |
1124 | + | |
1125 | + /** | |
1126 | + * Compares the children of two parsers. | |
1127 | + * | |
1128 | + * Normally subclasses should not override this method, but instead {@link #getChildren()}. | |
1129 | + */ | |
1130 | + protected boolean hasEqualChildren(Parser other, Set<Parser> seen) { | |
1131 | + List<Parser> thisChildren = this.getChildren(); | |
1132 | + List<Parser> otherChildren = other.getChildren(); | |
1133 | + if (thisChildren.size() != otherChildren.size()) { | |
1134 | + return false; | |
1135 | + } | |
1136 | + for (int i = 0; i < thisChildren.size(); i++) { | |
1137 | + if (!thisChildren.get(i).isEqualTo(otherChildren.get(i), seen)) { | |
1138 | + return false; | |
1139 | + } | |
1140 | + } | |
1141 | + return true; | |
1142 | + } | |
1143 | +""" | ... | ... |
repeating.nim
0 → 100644
1 | +++ a/repeating.nim | |
1 | +import petitparser | |
2 | + | |
3 | +# An abstract parser that repeatedly parses between 'min' and 'max' instances of its delegate. | |
4 | +type | |
5 | + RepeatingParser = ref object of DelegateParser | |
6 | + min*: int | |
7 | + max*: int | |
8 | + | |
9 | +const UNBOUNDED = 1 | |
10 | + | |
11 | +proc newRepeatingParser*(delegate: Parser, min, max: int): RepeatingParser = | |
12 | + if min < 0: | |
13 | + raise newException(Exception, "Invalid min repetitions") | |
14 | + if max != UNBOUNDED and min > max: | |
15 | + raise newException(Exception, "Invalid max repetitions") | |
16 | + RepeatingParser(delegate, min, max) | |
17 | + | |
18 | + | |
19 | +discard """ | |
20 | + @Override | |
21 | + public boolean hasEqualProperties(Parser other) { | |
22 | + return super.hasEqualProperties(other) && | |
23 | + Objects.equals(min, ((RepeatingParser) other).min) && | |
24 | + Objects.equals(max, ((RepeatingParser) other).max); | |
25 | + } | |
26 | + | |
27 | + @Override | |