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: