define ParseState { refines ParseContext<#x> @value String data @value Int index @value Int line @value Int char @value ErrorOr<#x> value @value optional Formatted error consumeAll (parser,data) { ParseContext<#y> context <- parser.run(new(data)) if (context.hasAnyError()) { return context.getValue() } elif (!context.atEof()) { return ErrorOr:error("Parsing did not consume all of the data " + context.getPosition()) } else { return context.getValue() } } run (parser) { return parser.run(self) } runAndGet (parser) { ParseContext<#y> context <- parser.run(self) return context, context.getValue() } getValue () { if (present(error)) { return ErrorOr:error(require(error)) } else { return value } } convertError () { return ParseState{ data, index, line, char, ErrorOr:error(getError()), error } } setValue (value2) { if (hasBrokenInput()) { return convertError() } else { return ParseState<#y>{ data, index, line, char, value2, error } } } setBrokenInput (message) { return ParseState{ data, index, line, char, ErrorOr:error(message), message } } toState () { return self } getPosition () { return String.builder() .append("[line: ") .append(line.formatted()) .append(", char: ") .append(char.formatted()) .append("]") .build() } atEof () { return index >= data.size() } hasAnyError () { return present(error) || value.isError() } hasBrokenInput () { return present(error) } current () { \ sanityCheck() return data.readAt(index) } advance () { \ sanityCheck() if (data.readAt(index) == '\n') { return #self{ data, index+1, line+1, 1, value, error } } else { return #self{ data, index+1, line, char+1, value, error } } } @category new (String) -> (ParseState) new (data) { return ParseState{ data, 0, 1, 1, ErrorOr:value(Void.void()), empty } } @value getError () -> (Formatted) getError () { if (present(error)) { return require(error) } else { return value.getError() } } @value sanityCheck () -> () sanityCheck () { if (hasBrokenInput()) { \ LazyStream.new() .append("Error at ") .append(getPosition()) .append(": ") .append(getError()) .writeTo(SimpleOutput.error()) } if (atEof()) { \ LazyStream.new() .append("Reached end of input at ") .append(getPosition()) .writeTo(SimpleOutput.error()) } } }