diff --git b/combinators.nim a/combinators.nim new file mode 100644 index 0000000..708f284 --- /dev/null +++ a/combinators.nim @@ -0,0 +1,152 @@ +import petitparser, context + +# DelegateParser +type + DelegateParser* = ref object of Parser + delegate*: Parser + +proc newDelegateParser*(delegate: Parser not nil): DelegateParser = + DelegateParser(delegate: delegate) + +method parseOn*(self: DelegateParser, context: Context): Result = + self.delegate.parseOn(context) + +method replace*(self: DelegateParser, source, target: Parser) = + procCall(Parser(self).replace(source, target)) + if delegate == source: + delegate = target + +method getChildren(self: DelegateParser): seq[Parser] = + @[delegate] + +method copy(self: DelegateParser): Parser = + newDelegateParser(self.delegate) + + +## A parser that optionally parsers its delegate, or answers nil. +type + OptionalParser* = ref object of DelegateParser + otherwise*: RootRef + +proc newOptionalParser*(delegate: Parser, otherwise: RootRef): OptionalParser = + OptionalParser(delegate: delegate, otherwise: otherwise) + +# public OptionalParser(Parser delegate, Object otherwise) { +# super(delegate); +# this.otherwise = otherwise; +# } + +method parseOn*(self: OptionalParser, context: Context): Result = + result = self.delegate.parseOn(context) + if result.isSuccess: + return + current.success(elements) + + +# public Result parseOn(Context context) { +# Result result = delegate.parseOn(context); +# if (result.isSuccess()) { +# return result; +# } else { +# return context.success(otherwise); +# } +# } + +method hasEqualProperties*(self: OptionalParser, other: Parser): bool = + return true + +# protected boolean hasEqualProperties(Parser other) { +# return super.hasEqualProperties(other) && +# Objects.equals(otherwise, ((OptionalParser) other).otherwise); +# } + +method copy*(self: OptionalParser): Parser = + newOptionalParser(self.delegate, self.otherwise) + +# @Override +# public Parser copy() { +# return new OptionalParser(delegate, otherwise); +# } + + + +# Abstract parser that parses a list of things in some way (to be specified by the subclasses). +type + ListParser* = ref object of Parser + parsers*: seq[Parser] + +#proc newListParser*(parsers: seq[Parser] not nil): ListParser = +# ListParser(parsers: parsers) + +method replace*(self: ListParser, source, target: Parser) = + procCall(Parser(self).replace(source, target)) + for i in 0..high(parsers): + if parsers[i] == source: + parsers[i] = target + +# public void replace(Parser source, Parser target) { +# super.replace(source, target); +# for (int i = 0; i < parsers.length; i++) { +# if (parsers[i] == source) { +# parsers[i] = target; +# } +# } +# } + +method getChildren(self: ListParser): seq[Parser] = + self.parsers + +# public List getChildren() { +# return Arrays.asList(parsers); +# } + + +# A parser that parses a sequence of parsers. +type + SequenceParser* = ref object of ListParser + +method parseOn*(self: SequenceParser, context: Context): Result = + var + current = context + elements = newSeq[auto](self.parsers.len) + for parser in self.parsers: + result = parser.parseOn(current) + if result.isFailure: + return + elements.add(result.get()) + current = result + current.success(elements) + +# public Result parseOn(Context context) { +# Context current = context; +# List elements = new ArrayList<>(parsers.length); +# for (Parser parser : parsers) { +# Result result = parser.parseOn(current); +# if (result.isFailure()) { +# return result; +# } +# elements.add(result.get()); +# current = result; +# } +# return current.success(elements); +# } + +method seq*(self: SequenceParser, others: varargs[Parser]): Parser = + newSequenceParser(self.parsers & others) +# let all = newSeq[Parser](self.parsers.len + others.len) + +# public Parser seq(Parser... others) { +# Parser[] array = Arrays.copyOf(parsers, parsers.length + others.length); +# System.arraycopy(others, 0, array, parsers.length, others.length); +# return new SequenceParser(array); +# } + +method copy*(self: SequenceParser): Parser = + newSequenceParser(@[] & self.parsers) + + +# @Override +# public Parser copy() { +# return new SequenceParser(Arrays.copyOf(parsers, parsers.length)); +# } +#} diff --git b/context.nim a/context.nim new file mode 100644 index 0000000..797c7d8 --- /dev/null +++ a/context.nim @@ -0,0 +1,173 @@ +import hashes + +# Converts the position index in a buffer to a line and column tuple. +# This code is an adaptation of splitLines. +proc findLineAndColumn(s: string, position: int): tuple[line, col: int] = + var first = 0 + var last = 0 + var line = 0 + while true: + while s[last] notin {'\0', '\c', '\l'}: inc(last) + # First line found + inc(line) + if last > position: + let col = position - first + return (line, col) + # skip newlines: + if s[last] == '\l': inc(last) + elif s[last] == '\c': + inc(last) + if s[last] == '\l': inc(last) + else: break # was '\0' + first = last + + +# Context +type + Context* = ref object of RootObj + buffer*: string + position*: int + +proc newContext*(buffer: string, position: int): Context = + Context(buffer: buffer, position: position) + + +# Result +type + Result* = ref object of Context + +# Success +type + Success*[T] = ref object of Result + result*: T + +proc newSuccess*[T](buffer: string, position: int, value: T): Success = + Success(buffer: buffer, position: position, result: value) + +# Failure +type + Failure* = ref object of Result + message*: string + +proc newFailure*(buffer: string, position: int, message: string): Failure = + Failure(buffer: buffer, position: position, message: message) + +# ParseError Exception +type + ParseError* = object of Exception + failure*: Failure + +proc newParseError*(failure: Failure): ref ParseError = + new(result) + result.failure = failure + result.msg = failure.message + +# Token +type + Token* = ref object of RootObj + buffer*: string + start*: int + stop*: int + value*: RootRef + +proc newToken*(buffer: string, start, stop: int, value: RootRef): Token = + Token(buffer: buffer, start: start, stop: stop, value: value) + +method getInput*(self: Token): string = + self.buffer.substr(self.start, self.stop) + +method getLength*(self: Token): int = + self.stop - self.stop + +method getLine*(self: Token): int = + self.buffer.findLineAndColumn(self.start)[0] + +method getColumn*(self: Token): int = + self.buffer.findLineAndColumn(self.start)[1] + +method `$`*(self: Token): string = + let (line, col) = findLineAndColumn(self.buffer, self.start); + "Token[" & $line & ":" & $col & "]: " & repr(self.value) # $self.value + +method hash*(self: Token): THash = + result = self.buffer.hash !& self.start.hash !& self.stop.hash #!& self.value.hash + result = !$result + +# We do not need to redefine ==, the generic one works fine. +# But let's see if we can +#method `==`(self, other: Token): bool = +# if (addr(self) == addr(other)): +# return true +# if other.isNil or self.isNil: +# return false +# self[] == other[] + + +method success*[T](self: Context, value: T, position: int): Success = + newSuccess(self.buffer, position, value) + +method success*[T](self: Context, value: T): Success = + self.success(value, self.position) + +method failure*(self: Context, message: string, position: int): Failure = + newFailure(self.buffer, position, message) + +method failure*(self: Context, message: string): Failure = + self.failure(message, self.position) + + +method isSuccess*(self: Result): bool = + false + +method isFailure*(self: Result): bool = + false + +# An abstract method, hmmm, which you can't really do +# in nim. Normally I would like to throw exception here, +# but we need to return a T for compiler to be happy... +method get*[T](self: Result): T = + discard result + # Dirty trick to make compiler realize I return a T + # T(self) + +method get*[T](self: Failure): T = + raise newParseError(self) + +method get*[T](self: Success): T = + self.result + + +method getMessage*(self: Failure): string = + self.message + +method isSuccess*(self: Success): bool = + true + +method name*(self: Result): string = + "Result" + +method name*(self: Success): string = + "Success" + +method name*(self: Failure): string = + "Failure" + + +method `$`*(self: Result): string = + let (line, col) = findLineAndColumn(self.buffer, self.position) + self.name & "[" & $line & ":" & $col & "]" + + +method `$`*(self: Success): string = + procCall($(Result(self))) & ": " & repr(self.result) + + +method isFailure*(self: Failure): bool = + true + +method `$`*(self: Failure): string = + procCall($(Result(self))) & ": " & self.message + +# The simplest form of unit tests +#when isMainModule: + diff --git b/context2.nim a/context2.nim new file mode 100644 index 0000000..e7e4467 --- /dev/null +++ a/context2.nim @@ -0,0 +1,161 @@ +import hashes + +# Converts the position index in a buffer to a line and column tuple. +# This code is an adaptation of splitLines. +proc findLineAndColumn(s: string, position: int): tuple[line, col: int] = + var first = 0 + var last = 0 + var line = 0 + while true: + while s[last] notin {'\0', '\c', '\l'}: inc(last) + # First line found + inc(line) + if last > position: + let col = position - first + return (line, col) + # skip newlines: + if s[last] == '\l': inc(last) + elif s[last] == '\c': + inc(last) + if s[last] == '\l': inc(last) + else: break # was '\0' + first = last + +type + ContextKind = enum ckContext, ckSuccess, ckFailure + +# Context +type + Context* = object + buffer*: string + position*: int + case kind: ContextKind + of ckContext: + nil + of ckSuccess: + value*: RootRef + of ckFailure: + message*: string + + +type Success = distinct Context +type Failure = distinct Context + +proc newContext*(buffer: string, position: int): Context = + Context(kind: ckContext, buffer: buffer, position: position) + +proc newSuccess*(buffer: string, position: int, value: RootRef): Success = + Success(Context(kind: ckSuccess, buffer: buffer, position: position, value: value)) + +proc newFailure*(buffer: string, position: int, message: string): Failure = + Failure(Context(kind: ckFailure, buffer: buffer, position: position, message: message)) + +# ParseError Exception +type + ParseError* = object of Exception + failure*: Failure + +proc newParseError*(failure: Failure): ref ParseError = + new(result) + result.failure = failure + result.msg = failure.message + +# Token +type + Token* = ref object of RootObj + buffer*: string + start*: int + stop*: int + value*: RootRef + +proc newToken*(buffer: string, start, stop: int, value: RootRef): Token = + Token(buffer: buffer, start: start, stop: stop, value: value) + +method getInput*(self: Token): string = + self.buffer.substr(self.start, self.stop) + +method getLength*(self: Token): int = + self.stop - self.stop + +method getLine*(self: Token): int = + self.buffer.findLineAndColumn(self.start)[0] + +method getColumn*(self: Token): int = + self.buffer.findLineAndColumn(self.start)[1] + +method `$`*(self: Token): string = + let (line, col) = findLineAndColumn(self.buffer, self.start); + "Token[" & $line & ":" & $col & "]: " & repr(self.value) # $self.value + +method hash*(self: Token): THash = + result = self.buffer.hash !& self.start.hash !& self.stop.hash #!& self.value.hash + result = !$result + +# We do not need to redefine ==, the generic one works fine. +# But let's see if we can +#method `==`(self, other: Token): bool = +# if (addr(self) == addr(other)): +# return true +# if other.isNil or self.isNil: +# return false +# self[] == other[] + + +method success[T]*(self: Context, value: T, position: int): Success = + newSuccess[T](self.buffer, position, value) + +method success[T]*(self: Context, value: T): Success = + self.success[T](value, self.position) + +method failure*(self: Context, message: string, position: int): Failure = + newFailure(self.buffer, position, message) + +method failure*(self: Context, message: string): Failure = + self.failure(message, self.position) + + +method isSuccess*(self: Context): bool = + false + +method isFailure*(self: Context): bool = + false + +# An abstract method, hmmm, which you can't really do +# in nim. Normally I would like to throw exception here, +# but we need to return a T for compiler to be happy... +method get*(self: Result): = + discard result + # Dirty trick to make compiler realize I return a T + # T(self) + +method get*(self: Failure): RootRef = + raise newParseError(self) + +method get*(self: Success): RootRef = + self.result + + +method getMessage*(self: Failure): string = + self.message + +method isSuccess*(self: Success): bool = + true + +method `$`*(self: Result): string = + let (line, col) = findLineAndColumn(self.buffer, self.position) + self.name & "[" & $line & ":" & $col & "]" + + +method `$`*(self: Success): string = + procCall($(Result(self))) & ": " & repr(self.result) + + +method isFailure*(self: Failure): bool = + true + +method `$`*(self: Failure): string = + procCall($(Result(self))) & ": " & self.message + +# The simplest form of unit tests +#when isMainModule: + diff --git b/functions.nim a/functions.nim new file mode 100644 index 0000000..0064a6c --- /dev/null +++ a/functions.nim @@ -0,0 +1,91 @@ +import sequtils + +proc firstOfList*[T](): proc(list: seq[T]): T = + # Returns a function that returns the first value of a seq. + result = proc (list: seq[T]): T = list[0] + +assert(firstOfList[int]()(@[1, 2]) == 1) + +# public static Function, T> firstOfList() { +# return (list) -> list.get(0); +# } + +proc lastOfList*[T](): proc(list: seq[T]): T = + # Returns a function that returns the last value of a seq. + result = proc (list: seq[T]): T = list[high(list)] + +assert(lastOfList[int]()(@[1, 2]) == 2) + +# public static Function, T> lastOfList() { +# return (list) -> list.get(list.size() - 1); +# } + +proc nthOfList*[T](index: int): proc(list: seq[T]): T = + ## Returns a function that returns the value at the given index. Negative indexes are counted from + ## the end of the list. + result = proc (list: seq[T]): T = + if index < 0: + result = list[list.len + index] + else: + result = list[index] + +assert(nthOfList[int](1)(@[1, 2]) == 2) +assert(nthOfList[int](-1)(@[1, 2]) == 2) +assert(nthOfList[int](-2)(@[1, 2]) == 1) + +# public static Function, T> nthOfList(int index) { +# return (list) -> list.get(index < 0 ? list.size() + index : index); +# } + +proc permutationOfList*[T](indexes: varargs[int]): proc(list: seq[T]): seq[T] = + ## Returns a function that returns the permutation of a given list. Negative indexes are counted + ## from the end of the list. + let inds = @indexes # Copying varargs (an array) to a seq so that the proc below can capture it + result = proc (list: seq[T]): seq[T] = + newSeq(result, 0) + for index in inds: + if index < 0: + result.add(list[list.len + index]) + else: + result.add(list[index]) + +assert(permutationOfList[int](1,0)(@[1, 2]) == @[2, 1]) +assert(permutationOfList[int](0,1)(@[1, 2]) == @[1, 2]) + +# public static Function, List> permutationOfList(int... indexes) { +# return (list) -> { +# List result = new ArrayList<>(indexes.length); +# for (int index : indexes) { +# result.add(list.get(index < 0 ? list.size() + index : index)); +# } +# return result; +# }; +# } + +proc withoutSeparators*[T](): proc(list: seq[T]): seq[T] = + ## Returns a function that skips the separators of a given list. + result = proc (list: seq[T]): seq[T] = + newSeq(result, 0) + for i in countup(0, high(list), 2): + result.add(list[i]) + +# public static Function, List> withoutSeparators() { +# return (list) -> { +# List result = new ArrayList<>(); +# for (int i = 0; i < list.size(); i += 2) { +# result.add(list.get(i)); +# } +# return result; +# }; +# } + +proc constant*[T,A](output: T): proc(input: A): T = + ## Returns a function that returns a constant value. + result = proc (input: A): T = output + +# public static Function constant(T output) { +# return (input) -> output; +# } + + +assert(constant[string, string]("hey")("hy") == "hey") diff --git b/parser.nim a/parser.nim new file mode 100644 index 0000000..5579fb3 --- /dev/null +++ a/parser.nim @@ -0,0 +1,3 @@ +# Parser +type + Parser* = ref object of RootObj diff --git b/petitparser.nim a/petitparser.nim new file mode 100644 index 0000000..1fde09f --- /dev/null +++ a/petitparser.nim @@ -0,0 +1,1143 @@ +import context, functions + +# Parser +type + Parser* = ref object of RootObj + + +method parseOn*(self: Parser, context: Context): Result = + raise newException(Exception, "should be implemented by subclass") + +method parse*(self: Parser, input: string): Result = + self.parseOn(newContext(input, 0)) + +method accept*(self: Parser, input: string): bool = + self.parse(input).isSuccess + +method matches*[T](self: Parser, input: string): seq[T] = + result = @[] + self.andd.map(result.add).seq(any).orr(any).star.parse(input) + +method getChildren*(self: Parser): seq[Parser] = + @[] + +method hasEqualProperties*(self, other: Parser): bool = + true + +method replace*(self, source, target: Parser) = + # no referring parsers + discard + +method name*(self: Parser): string = + "Parser" + +method `$`*(self: Parser): string = + self.name + +# public String toString() { +# return getClass().getSimpleName(); +# } + + +# DelegateParser +type + DelegateParser* = ref object of Parser + delegate*: Parser + +proc newDelegateParser*(delegate: Parser): DelegateParser = + DelegateParser(delegate: delegate) + +method parseOn*(self: DelegateParser, context: Context): Result = + self.delegate.parseOn(context) + +method replace*(self: DelegateParser, source, target: Parser) = + procCall(Parser(self).replace(source, target)) + if self.delegate == source: + self.delegate = target + +method getChildren(self: DelegateParser): seq[Parser] = + @[self.delegate] + +method copy(self: DelegateParser): Parser = + newDelegateParser(self.delegate) + +method name*(self: DelegateParser): string = + "DelegateParser" + +## A parser that optionally parsers its delegate, or answers nil. +type + OptionalParser*[T] = ref object of DelegateParser + otherwise*: T + +proc newOptionalParser*[T](delegate: Parser, otherwise: T): OptionalParser = + OptionalParser(delegate: delegate, otherwise: otherwise) + +# public OptionalParser(Parser delegate, Object otherwise) { +# super(delegate); +# this.otherwise = otherwise; +# } + +method parseOn*(self: OptionalParser, context: Context): Result = + result = self.delegate.parseOn(context) + if result.isSuccess: + return + return context.success(self.otherwise) + + +# public Result parseOn(Context context) { +# Result result = delegate.parseOn(context); +# if (result.isSuccess()) { +# return result; +# } else { +# return context.success(otherwise); +# } +# } + +method hasEqualProperties*(self: OptionalParser, other: OptionalParser): bool = + procCall(Parser(self).hasEqualProperties(Parser(other))) and self.otherwise == other.otherwise + +# protected boolean hasEqualProperties(Parser other) { +# return super.hasEqualProperties(other) && +# Objects.equals(otherwise, ((OptionalParser) other).otherwise); +# } + +method copy*(self: OptionalParser): Parser = + newOptionalParser(self.delegate, self.otherwise) + +# @Override +# public Parser copy() { +# return new OptionalParser(delegate, otherwise); +# } + +method name*(self: OptionalParser): string = + "OptionalParser" + +# An abstract parser that repeatedly parses between 'min' and 'max' instances of its delegate. +type + RepeatingParser* = ref object of DelegateParser + min*: int + max*: int + +const UNBOUNDED = 1 + +proc newRepeatingParser*(delegate: Parser, min: int, max: int): RepeatingParser = + if min < 0: + raise newException(Exception, "Invalid min repetitions") + if max != UNBOUNDED and min > max: + raise newException(Exception, "Invalid max repetitions") + RepeatingParser(delegate: delegate, min: min, max: max) + +method hasEqualProperties*(self: RepeatingParser, other: RepeatingParser): bool = + # No super implementation in DelegateParser + procCall(Parser(self).hasEqualProperties(Parser(other))) and self.min == other.min and self.max == other.max + +method name*(self: RepeatingParser): string = + "RepeatingParser" + +method `$`*(self: RepeatingParser): string = + result = procCall($Parser(self)) & "[" & $self.min & ".." + if self.max == UNBOUNDED: + result = result & "*]" + else: + result = result & $self.max & "]" + +# public String toString() { +# return super.toString() + "[" + min + ".." + (max == UNBOUNDED ? "*" : max) + "]"; +# } + +# A greedy parser that repeatedly parses between 'min' and 'max' instances of its delegate. +type + PossessiveRepeatingParser = ref object of RepeatingParser + +proc newPossessiveRepeatingParser*(delegate: Parser, min, max: int): PossessiveRepeatingParser = + PossessiveRepeatingParser(newRepeatingParser(delegate, min, max)) + +method name*(self: PossessiveRepeatingParser): string = + "PossessiveRepeatingParser" + + +method copy*(self: PossessiveRepeatingParser): Parser = + newPossessiveRepeatingParser(self.delegate, self.min, self.max) + +method parseOn*[T](self: PossessiveRepeatingParser, context: Context): Result = + var + current = context + elements = newSeq[T]() + while elements.len < self.min: + result = self.delegate.parseOn(current) + if result.isFailure: + return + elements.add(get[T](result)) + current = result + while self.max == UNBOUNDED or elements.len < max: + result = self.delegate.parseOn(current) + if result.isFailure: + return current.success[T](elements) + elements.add(get[T](result)) + current = result + current.success[T](elements) + + +# Abstract parser that parses a list of things in some way (to be specified by the subclasses). +type + ListParser* = ref object of Parser + parsers*: seq[Parser] + +#proc newListParser*(parsers: seq[Parser] not nil): ListParser = +# ListParser(parsers: parsers) + +method replace*(self: ListParser, source, target: Parser) = + procCall(Parser(self).replace(source, target)) + for i in 0..high(self.parsers): + if self.parsers[i] == source: + self.parsers[i] = target + +# public void replace(Parser source, Parser target) { +# super.replace(source, target); +# for (int i = 0; i < parsers.length; i++) { +# if (parsers[i] == source) { +# parsers[i] = target; +# } +# } +# } + +method getChildren(self: ListParser): seq[Parser] = + self.parsers + +# public List getChildren() { +# return Arrays.asList(parsers); +# } + + +method name*(self: ListParser): string = + "ListParser" + + +# A parser that parses a sequence of parsers. +type + SequenceParser* = ref object of ListParser + +proc newSequenceParser*(parsers: seq[Parser]): SequenceParser = + SequenceParser(parsers: parsers) + +method parseOn*[T](self: SequenceParser, context: Context): Result = + var + current = context + elements = newSeq[T](self.parsers.len) + for parser in self.parsers: + result = parser.parseOn(current) + if result.isFailure: + return + elements.add(get[T](result)) + current = result + current.success[T](elements) + +# public Result parseOn(Context context) { +# Context current = context; +# List elements = new ArrayList<>(parsers.length); +# for (Parser parser : parsers) { +# Result result = parser.parseOn(current); +# if (result.isFailure()) { +# return result; +# } +# elements.add(result.get()); +# current = result; +# } +# return current.success(elements); +# } + +method seq*(self: SequenceParser, others: varargs[Parser]): Parser = + newSequenceParser(self.parsers & @others) +# let all = newSeq[Parser](self.parsers.len + others.len) + +# public Parser seq(Parser... others) { +# Parser[] array = Arrays.copyOf(parsers, parsers.length + others.length); +# System.arraycopy(others, 0, array, parsers.length, others.length); +# return new SequenceParser(array); +# } + +method copy*(self: SequenceParser): Parser = + var parsersCopy = self.parsers + newSequenceParser(parsersCopy) + + +# public Parser copy() { +# return new SequenceParser(Arrays.copyOf(parsers, parsers.length)); +# } + +method name*(self: SequenceParser): string = + "SequenceParser" + +# An abstract parser that repeatedly parses between 'min' and 'max' instances of its delegate and +# that requires the input to be completed with a specified parser 'limit'. Subclasses provide +# repeating behavior as typically seen in regular expression implementations (non-blind). +type + LimitedRepeatingParser* = ref object of RepeatingParser + limit*: Parser + +proc newLimitedRepeatingParser*(delegate: Parser, limit: Parser not nil, min, max: int): LimitedRepeatingParser = + LimitedRepeatingParser(delegate: delegate, limit: limit, min: min, max: max) + +method getChildren(self: LimitedRepeatingParser): seq[Parser] = + @[self.delegate, self.limit] + +method replace*(self: LimitedRepeatingParser, source, target: Parser) = + procCall(DelegateParser(self).replace(source, target)) + if self.limit == source: + self.limit = target + +method name*(self: LimitedRepeatingParser): string = + "LimitedRepeatingParser" + + +# A greedy repeating parser, commonly seen in regular expression implementations. It aggressively +# consumes as much input as possible and then backtracks to meet the 'limit' condition. +type + GreedyRepeatingParser* = ref object of LimitedRepeatingParser + +proc newGreedyRepeatingParser*(delegate, limit: Parser, min, max: int): GreedyRepeatingParser = + GreedyRepeatingParser(delegate: delegate, limit: limit, min: min, max: max) + +method copy*(self: GreedyRepeatingParser): Parser = + newGreedyRepeatingParser(self.delegate, self.limit, self.min, self.max) + +#method parseOn*[T](self: GreedyRepeatingParser, context: Context): Result = +# result = self.delegate.parseOn(context) +# if result.isSuccess: +# return context.success(get[T](result)) + +method parseOn*[T](self: GreedyRepeatingParser, context: Context): Result = + var + current = context + elements = newSeq[T]() + while elements.len < self.min: + result = self.delegate.parseOn(current) + if result.isFailure: + return + elements.add(get[T](result)) + current = result + var contexts = newSeq[Context]() + contexts.add(current) + while self.max == UNBOUNDED or elements.len < max: + result = self.delegate.parseOn(current) + if result.isFailure: + break + elements.add(get[T](result)) + contexts.add(current = result) + while true: + var stop = self.limit.parseOn(contexts[contexts.high]) + if stop.isSuccess: + return contexts[contexts.high].success(elements) + if elements.len == 0: + return stop + contexts.pop() + elements.pop() + if contexts.len == 0: + return stop + + +discard """ + + public Result parseOn(Context context) { + Context current = context; + List elements = new ArrayList<>(); + while (elements.size() < min) { + Result result = delegate.parseOn(current); + if (result.isFailure()) { + return result; + } + elements.add(result.get()); + current = result; + } + List contexts = new ArrayList<>(); + contexts.add(current); + while (max == UNBOUNDED || elements.size() < max) { + Result result = delegate.parseOn(current); + if (result.isFailure()) { + break; + } + elements.add(result.get()); + contexts.add(current = result); + } + while (true) { + Result stop = limit.parseOn(contexts.get(contexts.size() - 1)); + if (stop.isSuccess()) { + return contexts.get(contexts.size() - 1).success(elements); + } + if (elements.isEmpty()) { + return stop; + } + contexts.remove(contexts.size() - 1); + elements.remove(elements.size() - 1); + if (contexts.isEmpty()) { + return stop; + } + } + } + +} +""" + +# A parser that uses the first parser that succeeds. +type + ChoiceParser* = ref object of ListParser + +proc newChoiceParser*(parsers: varargs[Parser]): ChoiceParser = + ChoiceParser(parsers: @parsers) + +method orr*(self: ChoiceParser, others: varargs[Parser]): Parser = + ## Returns a parser that accepts the receiver or `other`. The resulting parser returns the + ## parse result of the receiver, if the receiver fails it returns the parse result of `other` + ## (exclusive ordered choice). + newChoiceParser(self.parsers & @others) + +method copy*(self: ChoiceParser): Parser = + let parsersCopy = self.parsers + newChoiceParser(parsersCopy) + +method parseOn*(self: ChoiceParser, context: Context): Result = + for parser in self.parsers: + result = parser.parseOn(context) + if result.isSuccess: + return + +# public Result parseOn(Context context) { +# Result result = null; +# for (Parser parser : parsers) { +# result = parser.parseOn(context); +# if (result.isSuccess()) { +# return result; +# } +# } +# return result; +# } + +# The and-predicate, a parser that succeeds whenever its delegate does, but does not consume the +# input stream [Parr 1994, 1995]. +type + AndParser* = ref object of DelegateParser + +proc newAndParser*(delegate: Parser): AndParser = + AndParser(delegate) + +method parseOn*[T](self: AndParser, context: Context): Result = + result = self.delegate.parseOn(context) + if result.isSuccess: + return context.success(get[T](result)) + +method copy*(self: AndParser): Parser = + newAndParser(self.delegate) + +# Testing a type class to match the Java interface +# This means, a ContinuationHandler is any type which +# you can call `apply` on, with the given arguments. +type + ContinuationHandler = generic handler + handler.callParseOn(Parser, Context) is Result + #handler.apply(proc(c: Context): Result, Context) is Result + +# Just a sample ContinuationHandler type +type + Sammy = ref object +proc callParseOn*(self: Sammy, p: Parser, c: Context): Result = + p.parseOn(c) + +# Continuation parser that when activated captures a continuation function and passes it together +# with the current context into the handler. +type + ContinuationParser*[T] = ref object of DelegateParser + handler*: T + +proc newContinuationParser*(delegate: Parser, handler: ContinuationHandler): ContinuationParser = + ContinuationParser(delegate: delegate, handler: handler) + +# TODO +method parseOn*(self: ContinuationParser, context: Context): Result = + self.handler.callParseOn(self, context) + +method copy*(self: ContinuationParser): Parser = + newContinuationParser(self.delegate, self.handler) + +method hasEqualProperties*(self: ContinuationParser, other: ContinuationParser): bool = + procCall(Parser(self).hasEqualProperties(Parser(other))) and self.handler == other.handler + + + +# The not-predicate, a parser that succeeds whenever its delegate does not, but consumes no input [Parr 1994, 1995]. +type + NotParser* = ref object of DelegateParser + message*: string + +proc newNotParser*(delegate: Parser, message: string): NotParser = + NotParser(delegate: delegate, message: message) + +method parseOn*[T](self: NotParser, context: Context): Result = + if self.delegate.parseOn(context).isFailure: + return context.success(nil) + else: + return context.failure(self.message) + +method hasEqualProperties*(self: NotParser, other: NotParser): bool = + procCall(Parser(self).hasEqualProperties(Parser(other))) and self.message == other.message + +method copy*(self: NotParser): NotParser = + newNotParser(self.delegate, self.message) + +method `$`*(self: NotParser): string = + procCall($Parser(self)) & "[" & self.message & "]" + + + + + +# Parses a single character. +type + CharacterParser* = ref object of Parser + +proc newCharacterParser*(predicate: CharacterPredicate, message: string): Parser = + CharacterParser(predicate, message) + +proc off*(predicate: CharacterPredicate, message: string): Parser = + newCharacterParser(predicate, message) + +discard """ + /** + * Returns a parser that accepts a specific {@link CharacterPredicate}. + */ + public static Parser of(CharacterPredicate predicate, String message) { + return new CharacterParser(predicate, message); + } + + /** + * Returns a parser that accepts a specific {@code character}. + */ + public static Parser of(char character) { + return of(character, "'" + character + "' expected"); + } + + public static Parser of(char character, String message) { + return of(CharacterPredicate.of(character), message); + } + + /** + * Returns a parser that accepts any character. + */ + public static Parser any() { + return any("any character expected"); + } + + public static Parser any(String message) { + return of(CharacterPredicate.any(), message); + } + + /** + * Returns a parser that accepts any of the provided characters. + */ + public static Parser anyOf(String chars) { + return anyOf(chars, "any of '" + chars + "' expected"); + } + + public static Parser anyOf(String chars, String message) { + return of(CharacterPredicate.anyOf(chars), message); + } + + /** + * Returns a parser that accepts no character. + */ + public static Parser none() { + return none("no character expected"); + } + + public static Parser none(String message) { + return of(CharacterPredicate.none(), message); + } + + /** + * Returns a parser that accepts none of the provided characters. + */ + public static Parser noneOf(String chars) { + return noneOf(chars, "none of '" + chars + "' expected"); + } + + public static Parser noneOf(String chars, String message) { + return of(CharacterPredicate.noneOf(chars), message); + } + + /** + * Returns a parser that accepts a single digit. + */ + public static Parser digit() { + return digit("digit expected"); + } + + public static Parser digit(String message) { + return new CharacterParser(Character::isDigit, message); + } + + /** + * Returns a parser that accepts a single letter. + */ + public static Parser letter() { + return letter("letter expected"); + } + + public static Parser letter(String message) { + return of(Character::isLetter, message); + } + + /** + * Returns a parser that accepts an lower-case letter. + */ + public static Parser lowerCase() { + return lowerCase("lowercase letter expected"); + } + + public static Parser lowerCase(String message) { + return of(Character::isLowerCase, message); + } + + /** + * Returns a parser that accepts a specific character pattern. + *

+ * Characters match themselves. A dash {@code -} between two characters matches the range of those + * characters. A caret {@code ^} at the beginning negates the pattern. + */ + public static Parser pattern(String pattern) { + return pattern(pattern, "[" + pattern + "] expected"); + } + + public static Parser pattern(String pattern, String message) { + return of(CharacterPredicate.pattern(pattern), message); + } + + /** + * Returns a parser that accepts a specific character range. + */ + public static Parser range(char start, char stop) { + return range(start, stop, start + ".." + stop + " expected"); + } + + public static Parser range(char start, char stop, String message) { + return of(CharacterPredicate.range(start, stop), message); + } + + /** + * Returns a parser that accepts an upper-case letter. + */ + public static Parser upperCase() { + return upperCase("uppercase letter expected"); + } + + public static Parser upperCase(String message) { + return of(Character::isUpperCase, message); + } + + /** + * Returns a parser that accepts a single whitespace. + */ + public static Parser whitespace() { + return whitespace("whitespace expected"); + } + + public static Parser whitespace(String message) { + return of(Character::isWhitespace, message); + } + + /** + * Returns a parser that accepts a single letter or digit. + */ + public static Parser word() { + return word("letter or digit expected"); + } + + public static Parser word(String message) { + return of(Character::isLetterOrDigit, message); + } + + private final CharacterPredicate matcher; + private final String message; + + private CharacterParser(CharacterPredicate matcher, String message) { + this.matcher = Objects.requireNonNull(matcher, "Undefined matcher"); + this.message = Objects.requireNonNull(message, "Undefined message"); + } + + @Override + public Result parseOn(Context context) { + String buffer = context.getBuffer(); + int position = context.getPosition(); + if (position < buffer.length()) { + char result = buffer.charAt(position); + if (matcher.test(result)) { + return context.success(result, position + 1); + } + } + return context.failure(message); + } + + @Override + public Parser neg(String message) { + return of(matcher.not(), message); + } + + @Override + protected boolean hasEqualProperties(Parser other) { + return super.hasEqualProperties(other) && + Objects.equals(matcher, ((CharacterParser) other).matcher) && + Objects.equals(message, ((CharacterParser) other).message); + } + + @Override + public Parser copy() { + return of(matcher, message); + } + + @Override + public String toString() { + return super.toString() + "[" + message + "]"; + } +""" + + + +# /** +# * Returns a list of all successful overlapping parses of the {@code input}. +# */ +# @SuppressWarnings("unchecked") +# public List matches(String input) { +# List list = new ArrayList<>(); +# this.and().map(list::add).seq(any()).or(any()).star().parse(input); +# return (List) list; +# } + +method matchesSkipping*[T](self: Parser, input: string): seq[T] = + result = @[] + self.map(result.add).`or`(any).star.parse(input) + +# /** +# * Returns a list of all successful non-overlapping parses of the {@code input}. +# */ +# @SuppressWarnings("unchecked") +# public List matchesSkipping(String input) { +# List list = new ArrayList<>(); +# this.map(list::add).or(any()).star().parse(input); +# return (List) list; +# } + + +method repeat*(self: Parser, min, max: int): Parser = + ## Returns a parser that accepts the receiver between `min` and `max` times. The + ## resulting parser returns a list of the parse results of the receiver. + ## + ## This is a greedy and blind implementation that tries to consume as much input as possible and + ## that does not consider what comes afterwards. + newPossessiveRepeatingParser(self, min, max) + +# public Parser repeat(int min, int max) { +# return new PossessiveRepeatingParser(this, min, max); +# } + + +method optional*[T](self: Parser, otherwise: T): Parser = + ## Returns new parser that accepts the receiver, if possible. + ## The returned value can be provided as `otherwise`. + newOptionalParser(self, otherwise) + +# public Parser optional(Object otherwise) { +# return new OptionalParser(this, otherwise); +# } + +method optional*[T](self: Parser): Parser = + ## Returns new parser that accepts the receiver, if possible. The resulting parser returns the + ## result of the receiver, or `nil` if not applicable. + optional[T](self, nil) + +# public Parser optional() { +# return optional(null); +# } + +method start*(self: Parser): Parser = + ## Returns a parser that accepts the receiver zero or more times. The resulting parser returns a + ## list of the parse results of the receiver. + ## + ## This is a greedy and blind implementation that tries to consume as much input as possible and + ## that does not consider what comes afterwards. + self.repeat(0, UNBOUNDED) + +# public Parser star() { +# return repeat(0, RepeatingParser.UNBOUNDED); +# } + +# Forward dec +method repeatGreedy*(self, limit: Parser, min, max: int): Parser + +method starGreedy*(self, limit: Parser): Parser = + ## Returns a parser that parses the receiver zero or more times until it reaches a `limit`. + ## This is a greedy non-blind implementation of the `star <#star>`_ operator. + ## The `limit` is not consumed. + self.repeatGreedy(limit, 0, UNBOUNDED) + +# public Parser starGreedy(Parser limit) { +# return repeatGreedy(limit, 0, RepeatingParser.UNBOUNDED); +# } +method repeatLazy*(self, limit: Parser, min, max: int): Parser +method starLazy*(self, limit: Parser): Parser = + ## Returns a parser that parses the receiver zero or more times until it reaches a `limit`. + ## This is a lazy non-blind implementation of the `star <#star>`_ operator. + ## The `limit` is not consumed. + self.repeatLazy(limit, 0, UNBOUNDED) + + +# public Parser starLazy(Parser limit) { +# return repeatLazy(limit, 0, RepeatingParser.UNBOUNDED); +# } + +method plus*(self: Parser): Parser = + ## Returns a parser that accepts the receiver one or more times. The resulting parser returns a + ## list of the parse results of the receiver. + ## + ## This is a greedy and blind implementation that tries to consume as much input as possible and + ## that does not consider what comes afterwards. + self.repeat(1, UNBOUNDED) + +# public Parser plus() { +# return repeat(1, RepeatingParser.UNBOUNDED); +# } + + +method plusGreedy*(self, limit: Parser): Parser = + ## Returns a parser that parses the receiver one or more times until it reaches `limit`. + ## This is a reedy non-blind implementation of the `plus <#plus>`_ operator. + ## The `limit` is not consumed. + self.repeatGreedy(limit, 1, UNBOUNDED) + +# public Parser plusGreedy(Parser limit) { +# return repeatGreedy(limit, 1, RepeatingParser.UNBOUNDED); +# } + + +method plusLazy*(self, limit: Parser): Parser = + ## Returns a parser that parses the receiver one or more times until it reaches a `limit`. + ## This is a lazy non-blind implementation of the `plus <#plus>`_ operator. + ## The `limit` is not consumed. + self.repeatLazy(limit, 1, UNBOUNDED) + + +# public Parser plusLazy(Parser limit) { +# return repeatLazy(limit, 1, RepeatingParser.UNBOUNDED); +# } + + +method repeatGreedy*(self, limit: Parser, min, max: int): Parser = + ## Returns a parser that parses the receiver at least `min` and at most `max` times + ## until it reaches a {@code limit}. This is a greedy non-blind implementation of the + ## `repeat <#repeat>`_ operator. The `limit` is not consumed. + newGreedyRepeatingParser(self, limit, min, max) + +# public Parser repeatGreedy(Parser limit, int min, int max) { +# return new GreedyRepeatingParser(this, limit, min, max); +# } + + +method repeatLazy*(self, limit: Parser, min, max: int): Parser = + ## Returns a parser that parses the receiver at least `min` and at most `max` times + ## until it reaches a `limit`. This is a lazy non-blind implementation of the + ## `repeat <#repeat>`_ operator. The `limit` is not consumed. + newGreedyRepeatingParser(self, limit, min, max) + +# public Parser repeatLazy(Parser limit, int min, int max) { +# return new LazyRepeatingParser(this, limit, min, max); +# } + +method times*(self: Parser, count: int): Parser = + ## Returns a parser that accepts the receiver exactly `count` times. + ## The resulting parser returns a list of the parse results of the receiver. + self.repeat(count, count) + +# public Parser times(int count) { +# return repeat(count, count); +# } + + +method seq*(self: Parser, others: varargs[Parser]): Parser = + ## Returns a parser that accepts the receiver followed by `others`. The resulting parser + ## returns a list of the parse result of the receiver followed by the parse result of `others`. + ## Calling this method on an existing sequence code not nest this sequence into a new one, + ## but instead augments the existing sequence with `others`. + + # Alternative low level version of addFirst + #var s = newSeq[Parser](others.len + 1) + #s[0] = self + #var j = 1 + #for p in others: + # s[j] = p + # inc(j) + #newSequenceParser(s) + + # Quick version of addFirst + newSequenceParser(@[self] & @others) + +# public Parser seq(Parser... others) { +# Parser[] parsers = new Parser[1 + others.length]; +# parsers[0] = this; +# System.arraycopy(others, 0, parsers, 1, others.length); +# return new SequenceParser(parsers); +# } + +method orr*(self: Parser, others: varargs[Parser]): Parser = + ## Returns a parser that accepts the receiver or `other`. The resulting parser returns the + ## parse result of the receiver, if the receiver fails it returns the parse result of `other` + ## (exclusive ordered choice). + newChoiceParser(@[self] & @others) + +# public Parser or(Parser... others) { +# Parser[] parsers = new Parser[1 + others.length]; +# parsers[0] = this; +# System.arraycopy(others, 0, parsers, 1, others.length); +# return new ChoiceParser(parsers); +# } + +method andd*(self: Parser): Parser = + ## Returns a parser (logical and-predicate) that succeeds whenever the receiver does, but never + ## consumes input. + newAndParser(self) + +# public Parser and() { +# return new AndParser(this); +# } + +method callCC*(self: Parser, handler: ContinuationHandler): Parser = + ## Returns a parser that is called with its current continuation. + newContinuationParser(self, handler) + +# public Parser callCC(ContinuationParser.ContinuationHandler handler) { +# return new ContinuationParser(this, handler); +# } + +method nott*(self: Parser): Parser = + ## Returns a parser (logical not-predicate) that succeeds whenever the receiver fails, but never + ## consumes input. + raise newException(Exception, "unexpected call to nott") + +# public Parser not() { +# return not("unexpected"); +# } + +method nott*(self: Parser, message: string): Parser = + ## Returns a parser (logical not-predicate) that succeeds whenever the receiver fails, but never + ## consumes input. + newNotParser(self, message) + +# public Parser not(String message) { +# return new NotParser(this, message); +# } + +method neg*(self: Parser, message: string): Parser = + ## Returns a parser that consumes any input token (character), but the receiver. + self.nott(message).seq(CharacterParser.any()).pick(1) + +# public Parser neg(String message) { +# return not(message).seq(CharacterParser.any()).pick(1); +# } + +method neg*(self: Parser): Parser = + ## Returns a parser that consumes any input token (character), but the receiver. + self.neg($self & " not expected") + +# public Parser neg() { +# return neg(this + " not expected"); +# } + +method flatten*(self: Parser): Parser = + ## Returns a parser that discards the result of the receiver, and returns a sub-string of the + ## consumed range in the string/list being parsed. + newFlattenParser(self) + +# public Parser flatten() { +# return new FlattenParser(this); +# } + +method token*(self: Parser): Parser = + ## Returns a parser that returns a {@link Token}. The token carries the parsed value of the + ## receiver {@link Token#getValue()}, as well as the consumed input {@link Token#getInput()} from + ## {@link Token#getStart()} to {@link Token#getStop()} of the input being parsed. + newTokenParser(self) + +# public Parser token() { +# return new TokenParser(this); +# } + +method trim*(self: Parser): Parser = + ## Returns a parser that consumes whitespace before and after the receiver. + self.trim(CharacterParser.whitespace()) + +# public Parser trim() { +# return trim(CharacterParser.whitespace()); +# } + +method trim*(self: Parser, both: Parser): Parser = + ## Returns a parser that consumes input on `both` sides of the receiver. + self.trim(both, both) + +# public Parser trim(Parser both) { +# return trim(both, both); +# } + +method trim*(self, before, after: Parser): Parser = + ## Returns a parser that consumes input {@code before} and {@code after} the receiver. + newTrimmingParser(self, before, after) + +# public Parser trim(Parser before, Parser after) { +# return new TrimmingParser(this, before, after); +# } + +method endd*(self: Parser): Parser = + ## Returns a parser that succeeds only if the receiver consumes the complete input. + self.endd("end of input expected") + +# public Parser end() { +# return end("end of input expected"); +# } + +method endd*(self: Parser, message: string): Parser = + ## Returns a parser that succeeds only if the receiver consumes the complete input, otherwise + ## return a failure with the {@code message}. + newEndOfInputParser(self, message) + +# public Parser end(String message) { +# return new EndOfInputParser(this, message); +# } + +method settable*(self: Parser): SettableParser = + ## Returns a parser that points to the receiver, but can be changed to point to something else at + ## a later point in time. + newSettableParserWith(self) + +# public SettableParser settable() { +# return SettableParser.with(this); +# } + +method map*[A, B](self: Parser, function: proc (x: A): B {.closure.}): Parser = + ## Returns a parser that evaluates a {@code function} as the production action on success of the + ## receiver. + newActionParser(self, function) + +# public Parser map(Function function) { +# return new ActionParser<>(this, function); +# } + +method pick*(self: Parser, index: int): Parser = + ## Returns a parser that transform a successful parse result by returning the element at {@code + ## index} of a list. A negative index can be used to access the elements from the back of the + ## list. + self.map(nthOfList(index)) + +# public Parser pick(int index) { +# return map(Functions.nthOfList(index)); +# } + +method permute*(self: Parser, indexes: varargs[int]): Parser = + ## Returns a parser that transforms a successful parse result by returning the permuted elements + ## at {@code indexes} of a list. Negative indexes can be used to access the elements from the back + ## of the list. + self.map(permutationOfList(indexes)) + + +# public Parser permute(int... indexes) { +# return this.map(Functions.permutationOfList(indexes)); +# } + + ## Returns a new parser that parses the receiver one or more times, separated + ## by a {@code separator}. + newSequenceParser(self, newSequenceParser(separator, self).star()) + .map( + +# public Parser separatedBy(Parser separator) { +# return new SequenceParser(this, new SequenceParser(separator, this).star()) +# .map(new Function>>, List>() { +# @Override +# public List apply(List>> input) { +# List result = new ArrayList<>(); +# result.add(input.get(0)); +# input.get(1).forEach(result::addAll); +# return result; +# } +# }); +# } + +discard """ + /** + * Returns a new parser that parses the receiver one or more times, separated + * and possibly ended by a {@code separator}." + */ + public Parser delimitedBy(Parser separator) { + return separatedBy(separator) + .seq(separator.optional()) + .map(new Function>, List>() { + @Override + public List apply(List> input) { + List result = new ArrayList<>(input.get(0)); + if (input.get(1) != null) { + result.add(input.get(1)); + } + return result; + } + }); + } + + /** + * Returns a shallow copy of the receiver. + */ + public abstract Parser copy(); + + /** + * Recursively tests for structural similarity of two parsers. + * + * The code can automatically deals with recursive parsers and parsers that refer to other + * parsers. This code is supposed to be overridden by parsers that add other state. + */ + public boolean isEqualTo(Parser other) { + return isEqualTo(other, new HashSet<>()); + } + + /** + * Recursively tests for structural similarity of two parsers. + */ + protected boolean isEqualTo(Parser other, Set seen) { + if (this.equals(other) || seen.contains(this)) { + return true; + } + seen.add(this); + return getClass().equals(other.getClass()) + && hasEqualProperties(other) + && hasEqualChildren(other, seen); + } + + /** + * Compares the properties of two parsers. + * + * Override this method in all subclasses that add new state. + */ + protected boolean hasEqualProperties(Parser other) { + return true; + } + + /** + * Compares the children of two parsers. + * + * Normally subclasses should not override this method, but instead {@link #getChildren()}. + */ + protected boolean hasEqualChildren(Parser other, Set seen) { + List thisChildren = this.getChildren(); + List otherChildren = other.getChildren(); + if (thisChildren.size() != otherChildren.size()) { + return false; + } + for (int i = 0; i < thisChildren.size(); i++) { + if (!thisChildren.get(i).isEqualTo(otherChildren.get(i), seen)) { + return false; + } + } + return true; + } +""" diff --git b/repeating.nim a/repeating.nim new file mode 100644 index 0000000..32c9031 --- /dev/null +++ a/repeating.nim @@ -0,0 +1,70 @@ +import petitparser + +# An abstract parser that repeatedly parses between 'min' and 'max' instances of its delegate. +type + RepeatingParser = ref object of DelegateParser + min*: int + max*: int + +const UNBOUNDED = 1 + +proc newRepeatingParser*(delegate: Parser, min, max: int): RepeatingParser = + if min < 0: + raise newException(Exception, "Invalid min repetitions") + if max != UNBOUNDED and min > max: + raise newException(Exception, "Invalid max repetitions") + RepeatingParser(delegate, min, max) + + +discard """ + @Override + public boolean hasEqualProperties(Parser other) { + return super.hasEqualProperties(other) && + Objects.equals(min, ((RepeatingParser) other).min) && + Objects.equals(max, ((RepeatingParser) other).max); + } + + @Override + public String toString() { + return super.toString() + "[" + min + ".." + (max == UNBOUNDED ? "*" : max) + "]"; + } +""" + + +# A greedy parser that repeatedly parses between 'min' and 'max' instances of its delegate. + +type + PossessiveRepeatingParser = ref object of RepeatingParser + +proc newPossessiveRepeatingParser*(delegate: Parser, min, max: int): PossessiveRepeatingParser = + PossessiveRepeatingParser(newRepeatingParser(delegate, min, max)) + +discard """ + @Override + public Result parseOn(Context context) { + Context current = context; + List elements = new ArrayList<>(); + while (elements.size() < min) { + Result result = delegate.parseOn(current); + if (result.isFailure()) { + return result; + } + elements.add(result.get()); + current = result; + } + while (max == UNBOUNDED || elements.size() < max) { + Result result = delegate.parseOn(current); + if (result.isFailure()) { + return current.success(elements); + } + elements.add(result.get()); + current = result; + } + return current.success(elements); + } + + @Override + public Parser copy() { + return new PossessiveRepeatingParser(delegate, min, max); + } +"""