context2.nim 4.15 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

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: