Initial commit
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Fri, 14 Dec 2018 07:05:32 +0000 (01:05 -0600)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Fri, 14 Dec 2018 07:05:32 +0000 (01:05 -0600)
15 files changed:
christmas_proj [new file with mode: 0755]
christmas_proj.nimble [new file with mode: 0644]
data/progs/test.lgt [new file with mode: 0644]
data/shaders/basic.frag [new file with mode: 0644]
data/shaders/basic.vert [new file with mode: 0644]
docs/plan.txt [new file with mode: 0644]
src/christmas_proj.nim [new file with mode: 0644]
src/gfx/glutils.nim [new file with mode: 0644]
src/gfx/window.nim [new file with mode: 0644]
src/lang/ast.nim [new file with mode: 0644]
src/lang/lexer.nim [new file with mode: 0644]
src/lang/parser.nim [new file with mode: 0644]
src/lang/tokens.nim [new file with mode: 0644]
src/lang/types.nim [new file with mode: 0644]
src/utils/iter.nim [new file with mode: 0644]

diff --git a/christmas_proj b/christmas_proj
new file mode 100755 (executable)
index 0000000..b8d76b0
Binary files /dev/null and b/christmas_proj differ
diff --git a/christmas_proj.nimble b/christmas_proj.nimble
new file mode 100644 (file)
index 0000000..88bb56a
--- /dev/null
@@ -0,0 +1,19 @@
+# Package
+
+version       = "0.1.0"
+author        = "Brendan Hansen"
+description   = "A new awesome nimble package"
+license       = "MIT"
+srcDir        = "src"
+bin           = @["christmas_proj"]
+
+
+# Dependencies
+
+requires "nim >= 0.19.0"
+requires "opengl >= 1.2.0"
+requires "glfw"
+
+task run, "Run project":
+  exec("nimble build")
+  exec("./christmas_proj")
\ No newline at end of file
diff --git a/data/progs/test.lgt b/data/progs/test.lgt
new file mode 100644 (file)
index 0000000..e8f784f
--- /dev/null
@@ -0,0 +1,8 @@
+$MEM_1 = 0
+$MEM_2 = 1
+
+WHILE $MEM_2 < 100000 DO
+       $MEM_3 = $MEM_1 + $MEM_2
+       $MEM_1 = $MEM_2
+       $MEM_2 = $MEM_3
+END
\ No newline at end of file
diff --git a/data/shaders/basic.frag b/data/shaders/basic.frag
new file mode 100644 (file)
index 0000000..cf3f5a7
--- /dev/null
@@ -0,0 +1,11 @@
+#version 300 es
+
+precision mediump float;
+
+in vec4 v_col;
+
+out vec4 fragColor;
+
+void main() {
+       fragColor = v_col * vec4(1.0, 1.0, 1.0, 1.0);
+}
\ No newline at end of file
diff --git a/data/shaders/basic.vert b/data/shaders/basic.vert
new file mode 100644 (file)
index 0000000..d539b87
--- /dev/null
@@ -0,0 +1,13 @@
+#version 300 es
+
+//uniform mat3 u_proj;
+layout(location = 0) in vec2 a_pos;
+layout(location = 1) in vec4 a_col;
+
+out vec4 v_col;
+
+void main() {
+       float scalar = float(gl_InstanceID) * 2.f - 1.f;
+       gl_Position = vec4(a_pos, 0.0, 0.0) * scalar + vec4(0, 0, 0, 1);
+       v_col = a_col;
+}
\ No newline at end of file
diff --git a/docs/plan.txt b/docs/plan.txt
new file mode 100644 (file)
index 0000000..db097b0
--- /dev/null
@@ -0,0 +1,16 @@
+ROBOT PROGRAMMING AND VISUALS
+
+Create custom programming language that instructions robots to move around a grid and perform tasks.
+Visualize this system with OpenGL 3.0 ES
+
+Language may look something like this:
+
+$A = 10
+:LOOP_TOP
+       IF $A ~= 0 THEN
+               $A = $A - 1
+               GOTO :LOOP_TOP
+       END
+
+This is compiled to a bytecode, which is executed by the program.
+Language is compiled at runtime, then bytecode is used.
\ No newline at end of file
diff --git a/src/christmas_proj.nim b/src/christmas_proj.nim
new file mode 100644 (file)
index 0000000..2267a2b
--- /dev/null
@@ -0,0 +1,96 @@
+import os
+import sequtils
+import opengl
+import glfw3 as glfw
+import gfx/window as gfx_window
+import gfx/glutils as glutils
+
+import lang/ast
+import lang/lexer as lexer
+import lang/parser as parser
+import lang/tokens
+
+proc key_down(window: glfw.Window, key, scancode, action, modifier: cint) {.cdecl.} =
+  if key == glfw.KEY_ESCAPE and action == glfw.PRESS:
+    glfw.SetWindowShouldClose(window, glfw.TRUE)
+
+proc main2() =
+  let window = gfxWindow.NewWindow(800, 600, "ASDF")
+
+  window.SetKeyCallback(key_down)
+
+  var version = cast[cstring](glGetString(GL_VERSION))
+  echo $version
+
+  let vertex_shader = glutils.CreateShader(glutils.stVertex, "./data/shaders/basic.vert")
+  let fragment_shader = glutils.CreateShader(glutils.stFragment, "./data/shaders/basic.frag")
+  let program = glutils.LinkProgram(vertex_shader, fragment_shader)
+  glUseProgram(program)
+
+  var vao: GLuint
+  var temp_vao: GLuint
+  glGenVertexArrays(1.GLsizei, vao.addr)
+  glGenVertexArrays(1.GLsizei, temp_vao.addr)
+
+  var vbo: GLuint
+  var cbo: GLuint
+  var ibo: GLuint
+  glGenBuffers(1.GLsizei, vbo.addr)
+  glGenBuffers(1.GLsizei, cbo.addr)
+  glGenBuffers(1.GLsizei, ibo.addr)
+
+  glBindVertexArray(vao)
+  glEnableVertexAttribArray(0)
+  glEnableVertexAttribArray(1)
+
+  var vertex_data: array[6, GLfloat] = [
+    0'f32, 0'f32,
+    0'f32, 1'f32,
+    1'f32, 0'f32,
+  ]
+  glBindBuffer(GL_ARRAY_BUFFER, vbo)
+  glBufferData(GL_ARRAY_BUFFER, vertex_data.sizeof, vertex_data.addr, GL_STATIC_DRAW)
+  glVertexAttribPointer(0, 2, cGL_FLOAT, GL_FALSE, 8.GLsizei, nil)
+
+  var color_data: array[8, GLfloat] = [
+    1'f32, 0'f32, 1'f32, 1'f32,
+    0'f32, 1'f32, 0'f32, 1'f32,
+  ]
+  glBindBuffer(GL_ARRAY_BUFFER, cbo)
+  glBufferData(GL_ARRAY_BUFFER, color_data.sizeof, color_data.addr, GL_STATIC_DRAW)
+  glVertexAttribDivisor(1, 1)
+  glVertexAttribPointer(1, 4, cGL_FLOAT, GL_FALSE, 16.GLsizei, nil)
+
+  var index_data: array[3, GLuint] = [
+    0.GLuint, 1.Gluint, 2.Gluint,
+  ] 
+  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo)
+  glBufferData(GL_ELEMENT_ARRAY_BUFFER, index_data.sizeof, index_data.addr, GL_STATIC_DRAW)
+
+  glBindVertexArray(0)
+
+  while not window.ShouldClose():
+    glClearColor(0, 0, 0, 1)
+    glClear(GL_COLOR_BUFFER_BIT)
+
+    glBindVertexArray(vao)
+    glDrawElementsInstanced(GL_TRIANGLES, 3.GLsizei, GL_UNSIGNED_INT, nil, 2)
+    glBindVertexArray(0)
+
+    window.Refresh()
+
+  window.CloseWindow()
+
+proc main() =
+  let source_code = readFile("data/progs/test.lgt")
+  let tokens = toSeq(lexer.GenerateTokens(source_code))
+  for token in tokens:
+    echo token
+
+  echo "\n\n\n"
+
+  for exp in parser.ParseTokens(tokens):
+    echo exp
+
+when isMainModule:
+  main()
diff --git a/src/gfx/glutils.nim b/src/gfx/glutils.nim
new file mode 100644 (file)
index 0000000..10f9cf0
--- /dev/null
@@ -0,0 +1,71 @@
+import os
+import opengl
+
+type
+  ShaderType* = enum
+    stVertex, stFragment
+
+proc Compile_shader*(shaderType: ShaderType, source: cstringArray): GLuint =
+  let s_type =
+    case shaderType
+    of stVertex:
+      GL_VERTEX_SHADER
+    of stFragment:
+      GL_FRAGMENT_SHADER
+
+  let shader = glCreateShader s_type
+  glShaderSource shader, 1, source, nil
+  glCompileShader shader
+
+  var status: GLint
+  glGetShaderiv shader, GL_COMPILE_STATUS, status.addr
+  if status == 0:
+    var logSize: GLint
+    glGetShaderiv shader, GL_INFO_LOG_LENGTH, logSize.addr
+
+    var
+      errorMsg = cast[ptr GLchar](alloc logSize)
+      logLen: GLsizei
+
+    glGetShaderInfoLog shader, logSize.GLsizei, logLen.addr, errorMsg
+
+    echo "Error compiling shader: " & $errorMsg
+
+    dealloc errorMsg
+    return 0
+
+  return shader
+
+proc Create_shader*(shaderType: ShaderType, fileLocation: string): GLuint =
+  let source = readFile fileLocation
+  let c_string = allocCStringArray([source])
+
+  # Dealloc the string when the procedure returns
+  defer: deallocCStringArray(c_string)
+
+  return Compile_shader(shaderType, c_string)
+
+proc Link_program*(vertex_shader, fragment_shader: GLuint): GLuint =
+  let program = glCreateProgram()
+  glAttachShader program, vertex_shader
+  glAttachShader program, fragment_shader
+  glLinkProgram program
+  
+  var status: GLint
+  glGetProgramiv program, GL_LINK_STATUS, status.addr
+  if status == 0:
+    var logSize: GLint
+    glGetProgramiv program, GL_INFO_LOG_LENGTH, logSize.addr
+
+    var
+      errorMsg = cast[ptr GLchar](alloc logSize)
+      logLen: GLsizei
+
+    glGetProgramInfoLog program, logSize.GLsizei, logLen.addr, errorMsg
+
+    echo "Error linking program: " & $errorMsg
+
+    dealloc errorMsg
+    return 0
+
+  return program
diff --git a/src/gfx/window.nim b/src/gfx/window.nim
new file mode 100644 (file)
index 0000000..6a80640
--- /dev/null
@@ -0,0 +1,56 @@
+import glfw3 as glfw
+import opengl
+
+type
+  Window* = object
+    window: glfw.Window
+    width, height: cint
+    title: cstring
+
+
+proc New_window*(width, height: cint, title: cstring): Window =
+  # Initialize GLFW
+  if glfw.Init() != glfw.TRUE:
+    echo "Failed to initialize GLFW"
+    return
+
+  # Setup error callback for OpenGL
+  # May use logging system in the future
+  discard glfw.SetErrorCallback do (errorCode: cint, description: cstring) {.cdecl.}:
+    echo "GLFW Error: " & $description
+
+  # Create the window
+  let window = glfw.CreateWindow(width, height, title, nil, nil)
+  
+  # Check for failed window creation
+  if window == nil:
+    echo "Window or context creation failed"
+
+  # Use the window for OpenGL operations
+  glfw.MakeContextCurrent(window)
+  glfw.SwapInterval(0)
+
+  # Initialize OpenGL
+  opengl.loadExtensions()
+
+  # Create the wrapper around our window
+  let m_window = Window(window: window, width: width, height: height, title: title)
+
+  return m_window
+
+func Get_size*(window: Window): (int, int) {.inline.} =
+  (int(window.width), int(window.height))
+
+proc Set_key_callback*(window: Window, callback: glfw.KeyFun) {.inline.} =
+  discard glfw.SetKeyCallback(window.window, callback)
+
+proc Should_close*(window: Window): bool {.inline.} =
+  glfw.WindowShouldClose(window.window) == glfw.TRUE 
+
+proc Refresh*(window: Window) =
+  glfw.SwapBuffers(window.window)
+  glfw.PollEvents()
+
+proc Close_window*(window: Window) =
+  glfw.DestroyWindow(window.window)
+  glfw.Terminate()
\ No newline at end of file
diff --git a/src/lang/ast.nim b/src/lang/ast.nim
new file mode 100644 (file)
index 0000000..1836b56
--- /dev/null
@@ -0,0 +1,49 @@
+import ./types
+import ./tokens
+
+type
+  LightExprType* = enum
+    leNull    = 0,
+    leVar     = 1,
+    leNumLit  = 2,
+    leOp      = 3,
+    leAssign  = 4,
+    leLabel   = 5,
+    leGoto    = 6,
+    leIf      = 7,
+    leWhile   = 8,
+    leBreak   = 9
+
+  LightExpr* = ref object
+    case kind*: LightExprType
+    of leVar:
+      var_name*: LightVariable
+    of leNumLit:
+      value*: LightInt
+    of leOp:
+      left*: LightExpr
+      right*: LightExpr
+      operation*: LightOperation
+    of leAssign:
+      variable*: LightVariable
+      expression*: LightExpr
+    of leLabel, leGoto:
+      label*: string
+    of leIf, leWhile:
+      condition*: LightExpr
+      body*: seq[LightExpr]
+    else: 
+      discard
+
+proc `$`*(exp: LightExpr): string =
+  case exp.kind:
+  of leVar: "Var[" & $exp.var_name & "]"
+  of leNumLit: "Num[" & $exp.value & "]"
+  of leOp: "Operation[" & $exp.operation & ", " & $exp.left & ", " & $exp.right & "]"
+  of leAssign: "Assignment[" & $exp.variable & ", " & $exp.expression & "]"
+  of leLabel: "Label[" & $exp.label & "]"
+  of leGoto: "Goto[" & $exp.label & "]"
+  of leIf: "If[" & $exp.condition & " -> " & $exp.body & "]"
+  of leWhile: "While[" & $exp.condition & " -> " & $exp.body & "]"
+  of leBreak: "Break"
+  else: ""
\ No newline at end of file
diff --git a/src/lang/lexer.nim b/src/lang/lexer.nim
new file mode 100644 (file)
index 0000000..197acb7
--- /dev/null
@@ -0,0 +1,88 @@
+import strutils
+import parseutils
+import sequtils
+
+import ./types
+import ./tokens
+
+iterator Generate_tokens*(source: string): LightToken =
+  for token, is_sep in strutils.tokenize(source, {' ', '\n', ';', '\t', '='}):
+    if is_sep:
+      if token.contains({'\n', ';'}):
+        yield LightToken(kind: ltExprDelim)
+      if token.contains({'='}):
+        yield LightToken(kind: ltEq)
+      continue
+
+    if token.startsWith('$'):
+      let varString = token[1 .. ^1]
+      var varName: LightVariable
+
+      if varString == "":
+        raise newException(IOError, "Expected variable name")
+      else:
+        if varString == "MEM_1": varName = var1
+        elif varString == "MEM_2": varName = var2
+        elif varString == "MEM_3": varName = var3
+        elif varString == "MEM_4": varName = var4
+        elif varString == "MEM_5": varName = var5
+        elif varString == "MEM_6": varName = var6
+        elif varString == "MEM_7": varName = var7
+        elif varString == "MEM_8": varName = var8
+        else:
+          raise newException(IOError, "Invalid variable name.")
+
+      yield LightToken(kind: ltVar, var_name: varName)
+
+    elif token == "=":
+      yield LightToken(kind: ltEq)
+
+    elif token.isDigit:
+      var value: int
+      discard parseutils.parseInt(token, value)
+
+      yield LightToken(kind: ltNum, value: value.LightInt)
+
+    elif token.toLowerAscii == "if":
+      yield LightToken(kind: ltIf)
+    elif token.toLowerAscii == "while":
+      yield LightToken(kind: ltWhile)
+    elif token.toLowerAscii == "break":
+      yield LightToken(kind: ltBreak)
+    elif token.toLowerAscii == "then" or token.toLowerAscii == "do" or token == "{":
+      yield LightToken(kind: ltBlockStart)
+    elif token.toLowerAscii == "end" or token == "}":
+      yield LightToken(kind: ltBlockEnd)
+
+    elif token.toLowerAscii == "goto":
+      yield LightToken(kind: ltGoto)
+    elif token.startsWith(':'):
+      let labelName = token[1 .. ^1]
+      yield LightToken(kind: ltLabel, label_name: labelName)
+
+    else:
+      case token:
+      of "+":
+        yield LightToken(kind: ltOp, operation: loAdd)
+      of "-":
+        yield LightToken(kind: ltOp, operation: loSub)
+      of "*":
+        yield LightToken(kind: ltOp, operation: loMul)
+      of "/":
+        yield LightToken(kind: ltOp, operation: loDiv)
+      of "%":
+        yield LightToken(kind: ltOp, operation: loMod)
+      of "<":
+        yield LightToken(kind: ltOp, operation: loLt)
+      of "<=":
+        yield LightToken(kind: ltOp, operation: loLte)
+      of ">":
+        yield LightToken(kind: ltOp, operation: loGt)
+      of ">=":
+        yield LightToken(kind: ltOp, operation: loGte)
+      of "==":
+        yield LightToken(kind: ltOp, operation: loEq)
+      of "~=":
+        yield LightToken(kind: ltOp, operation: loNeq)
+      else:
+        raise newException(IOError, "Invalid token")
diff --git a/src/lang/parser.nim b/src/lang/parser.nim
new file mode 100644 (file)
index 0000000..6de13b5
--- /dev/null
@@ -0,0 +1,122 @@
+
+import ./types
+import ./tokens
+import ./ast
+
+import ../utils/iter
+
+type
+  LightParser* = object
+    tokens: Iter[LightToken]
+  
+func CreateParser*(tokens: Iter[LightToken]): LightParser =
+  LightParser(tokens: tokens)
+func CreateParser*(tokens: seq[LightToken]): LightParser =
+  CreateParser(CreateIter[LightToken](tokens, LightToken(kind: ltNull)))
+
+func NextExpr*(parser: LightParser, prev: LightExpr, stop_at: set[LightTokenType]): LightExpr
+
+func Parse_block(tokens: Iter[LightToken]): seq[LightExpr] =
+  result = @[]
+
+  var last = tokens.Current
+  if last.kind == ltBlockEnd:
+    tokens.Step()
+    return
+
+  var parser = CreateParser(tokens)
+  while last.kind != ltBlockEnd:
+    let p = parser.NextExpr(LightExpr(kind: leNull), {ltExprDelim, ltBlockEnd})
+    if p.kind != leNull:
+      result.add(p)
+    last = parser.tokens.Current
+    parser.tokens.Step()
+  
+func NextExpr*(parser: LightParser, prev: LightExpr, stop_at: set[LightTokenType]): LightExpr =
+  let curr = parser.tokens.Current
+
+  if curr.kind in stop_at:
+    return prev
+
+  parser.tokens.Step()
+
+  if curr.kind in {ltNum, ltVar} and prev.kind == leNull:
+    let prevExpr =
+      case curr.kind:
+      of ltNum: LightExpr(kind: leNumLit, value: curr.value)
+      of ltVar: LightExpr(kind: leVar, var_name: curr.var_name)
+      else: LightExpr(kind: leNull)
+
+    return parser.NextExpr(prevExpr, stop_at)
+
+  elif curr.kind == ltOp:
+    let next = parser.NextExpr(LightExpr(kind: leNull), stop_at)
+    return LightExpr(
+      kind: leOp,
+      left: prev,
+      right: next,
+      operation: curr.operation
+    )
+  
+  elif curr.kind == ltEq:
+    if prev.kind != leVar:
+      raise newException(ValueError, "Expected variable on the left of assignment operator")
+
+    let next = parser.NextExpr(LightExpr(kind: leNull), stop_at)
+
+    return LightExpr(
+      kind: leAssign,
+      variable: prev.var_name,
+      expression: next
+    )
+
+  elif curr.kind == ltLabel:
+    return LightExpr(
+      kind: leLabel,
+      label: curr.label_name
+    )
+  
+  elif curr.kind == ltGoto:
+    let next = parser.tokens.Current
+    if next.kind != ltLabel:
+      raise newException(ValueError, "Expected label after goto")
+    
+    return LightExpr(
+      kind: leGoto,
+      label: next.label_name
+    )
+
+  elif curr.kind == ltIf:
+    let condition = parser.NextExpr(LightExpr(kind: leNull), {ltBlockStart})
+    parser.tokens.Step()
+    let body = Parse_block(parser.tokens)
+
+    return LightExpr(
+      kind: leIf,
+      condition: condition,
+      body: body
+    )
+
+  elif curr.kind == ltWhile:
+    let condition = parser.NextExpr(LightExpr(kind: leNull), {ltBlockStart})
+    parser.tokens.Step()
+    let body = Parse_block(parser.tokens)
+
+    return LightExpr(
+      kind: leWhile,
+      condition: condition,
+      body: body
+    )
+  
+  elif curr.kind == ltBreak:
+    return LightExpr(
+      kind: leBreak
+    )
+  
+iterator Parse_tokens*(tokens: seq[LightToken]): LightExpr =
+  var parser = CreateParser(tokens)
+  while not parser.tokens.ReachedEnd:
+    let next = parser.NextExpr(LightExpr(kind: leNull), {ltExprDelim})
+    if next.kind != leNull:
+      yield next
+    parser.tokens.Step()
diff --git a/src/lang/tokens.nim b/src/lang/tokens.nim
new file mode 100644 (file)
index 0000000..7fd56ea
--- /dev/null
@@ -0,0 +1,52 @@
+import ./types
+
+type
+  LightTokenType* = enum
+    ltNull,
+    ltVar, ltNum,
+    ltExprDelim,
+    ltLabel, ltGoto,
+    ltIf, ltWhile, ltBlockStart, ltBlockEnd,
+    ltBreak,
+    ltOp, ltEq
+
+  LightToken* = ref object
+    case kind*: LightTokenType
+    of ltVar:
+      var_name*: LightVariable
+    of ltNum:
+      value*: LightInt
+    of ltLabel:
+      label_name*: string
+    of ltOp:
+      operation*: LightOperation
+    else:
+      discard
+
+func `$`*(variable: LightVariable): string =
+  case variable:
+  of var1: "MEM_1"
+  of var2: "MEM_2"
+  of var3: "MEM_3"
+  of var4: "MEM_4"
+  of var5: "MEM_5"
+  of var6: "MEM_6"
+  of var7: "MEM_7"
+  of var8: "MEM_8"
+
+proc `$`*(token: LightToken): string =
+  return
+    case token.kind:
+    of ltNull: "NullToken"
+    of ltVar: "VarToken[" & $token.var_name & "]"
+    of ltEq: "EqualsToken"
+    of ltNum: "NumberToken[" & $token.value & "]"
+    of ltExprDelim: "ExprDelimToken"
+    of ltLabel: "LabelToken[" & token.label_name & "]"
+    of ltGoto: "GotoToken"
+    of ltIf: "IfToken"
+    of ltWhile: "WhileToken"
+    of ltBreak: "BreakToken"
+    of ltBlockStart: "BlockStartToken"
+    of ltBlockEnd: "BlockEndToken"
+    of ltOp: "OpeartionToken[" & $token.operation & "]"
\ No newline at end of file
diff --git a/src/lang/types.nim b/src/lang/types.nim
new file mode 100644 (file)
index 0000000..2ba8c9a
--- /dev/null
@@ -0,0 +1,10 @@
+type
+  LightVariable* = enum
+    var1 = 0, var2 = 1, var3 = 2, var4 = 3,
+    var5 = 4, var6 = 5, var7 = 6, var8 = 7
+    
+  LightOperation* = enum
+    loAdd, loSub, loMul, loDiv, loMod,
+    loGt, loGte, loLt, loLte, loEq, loNeq
+
+  LightInt* = int32
diff --git a/src/utils/iter.nim b/src/utils/iter.nim
new file mode 100644 (file)
index 0000000..42b1966
--- /dev/null
@@ -0,0 +1,34 @@
+type
+  Iter*[T] = ref object
+    curr: int
+    list: seq[T]
+    length: int
+    empty: T
+
+proc CreateIter*[T](items: seq[T], empty: T): Iter[T] =
+  Iter[T](curr: 0, list: items, length: items.len, empty: empty)
+
+proc Step*[T](iter: Iter[T]) =
+  iter.curr += 1
+
+func ReachedEnd*[T](iter: Iter[T]): bool =
+  iter.curr >= iter.length
+
+proc Current*[T](iter: Iter[T]): T =
+  if iter.curr < 0 or iter.curr >= iter.length:
+    return iter.empty
+
+  return iter.list[iter.curr]
+
+proc Previous*[T](iter: Iter[T]): T =
+  if iter.curr - 1 < 0 or iter.curr - 1 >= iter.length:
+    return iter.empty
+
+  return iter.list[iter.curr - 1]
+
+proc Next*[T](iter: Iter[T]): T =
+  if iter.curr + 1 < 0 or iter.curr + 1 >= iter.length:
+    return iter.empty
+
+  return iter.list[iter.curr + 1]
+