context.nim 4.17 KB
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: