From: Brendan Hansen Date: Fri, 14 Dec 2018 07:05:32 +0000 (-0600) Subject: Initial commit X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;h=2f493c870bf6bcee20650b604cb4b13e34fb2e31;p=light.git Initial commit --- 2f493c870bf6bcee20650b604cb4b13e34fb2e31 diff --git a/christmas_proj b/christmas_proj new file mode 100755 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 index 0000000..88bb56a --- /dev/null +++ b/christmas_proj.nimble @@ -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 index 0000000..e8f784f --- /dev/null +++ b/data/progs/test.lgt @@ -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 index 0000000..cf3f5a7 --- /dev/null +++ b/data/shaders/basic.frag @@ -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 index 0000000..d539b87 --- /dev/null +++ b/data/shaders/basic.vert @@ -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 index 0000000..db097b0 --- /dev/null +++ b/docs/plan.txt @@ -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 index 0000000..2267a2b --- /dev/null +++ b/src/christmas_proj.nim @@ -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 index 0000000..10f9cf0 --- /dev/null +++ b/src/gfx/glutils.nim @@ -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 index 0000000..6a80640 --- /dev/null +++ b/src/gfx/window.nim @@ -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 index 0000000..1836b56 --- /dev/null +++ b/src/lang/ast.nim @@ -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 index 0000000..197acb7 --- /dev/null +++ b/src/lang/lexer.nim @@ -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 index 0000000..6de13b5 --- /dev/null +++ b/src/lang/parser.nim @@ -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 index 0000000..7fd56ea --- /dev/null +++ b/src/lang/tokens.nim @@ -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 index 0000000..2ba8c9a --- /dev/null +++ b/src/lang/types.nim @@ -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 index 0000000..42b1966 --- /dev/null +++ b/src/utils/iter.nim @@ -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] +