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: