diff --git a/.luaurc b/.luaurc new file mode 100644 index 0000000..4614cc8 --- /dev/null +++ b/.luaurc @@ -0,0 +1,3 @@ +{ + "languageMode": "strict" +} \ No newline at end of file diff --git a/Script/colour.luau b/Script/colour.luau new file mode 100644 index 0000000..81cef7a --- /dev/null +++ b/Script/colour.luau @@ -0,0 +1,50 @@ +local stdio = require "@lune/stdio" +local colour = stdio.color +local style = stdio.style + +local blue = colour "blue" +local green = colour "green" +local purple = colour "purple" +local red = colour "red" +local yellow = colour "yellow" +local cyan = colour "cyan" + +local bold = style "bold" +local dim = style "dim" +local reset = style "reset" + +local Colour = {} + +function Colour.blue(str: string) + return blue .. str .. reset +end + +function Colour.green(str: string) + return green .. str .. reset +end + +function Colour.purple(str: string) + return purple .. str .. reset +end + +function Colour.red(str: string) + return red .. str .. reset +end + +function Colour.yellow(str: string) + return yellow .. str .. reset +end + +function Colour.cyan(str: string) + return cyan .. str .. reset +end + +function Colour.bold(str: string) + return bold .. str .. reset +end + +function Colour.dim(str: string) + return dim .. str .. reset +end + +return Colour diff --git a/Script/main.go b/Script/main.go index 6f73a4d..0343168 100644 --- a/Script/main.go +++ b/Script/main.go @@ -22,59 +22,54 @@ type Expr interface { Node } -type Identifier struct { +type ExprProps struct { startToken Token - name string } -type AssignmentExpr struct { - startToken Token - left Expr - right Expr -} +// type Identifier struct { +// *ExprProps +// name string +// } -type BinaryExpr struct { - startToken Token - left Expr - right Expr -} +// type AssignmentExpr struct { +// *ExprProps +// left Expr +// right Expr +// } -type UnaryExpr struct { - startToken Token - expr Expr -} +// type BinaryExpr struct { +// *ExprProps +// left Expr +// right Expr +// } + +// type UnaryExpr struct { +// *ExprProps +// expr Expr +// } type IfExpr struct { - startToken Token - condition Expr - block BlockExpr + *ExprProps + condition Expr + block BlockExpr } type ElseIfExpr struct { - startToken Token - condition Expr - block BlockExpr + *ExprProps + condition Expr + block BlockExpr } type ElseExpr struct { - startToken Token - block BlockExpr + *ExprProps + block BlockExpr } type BlockExpr struct { - startNode Node + *ExprProps expressions []Expr } -func (e Identifier) Kind() string { return "Identifier" } -func (e AssignmentExpr) Kind() string { return "AssignmentExpr" } -func (e BinaryExpr) Kind() string { return "BinaryExpr" } -func (e UnaryExpr) Kind() string { return "UnaryExpr" } -func (e IfExpr) Kind() string { return "IfExpr" } -func (e ElseIfExpr) Kind() string { return "ElseIfExpr" } -func (e ElseExpr) Kind() string { return "ElseExpr" } -func (e BlockExpr) Kind() string { return "BlockExpr" } - const ( EOF = "EOF" INDENT = "INDENT" @@ -129,7 +124,7 @@ func generate(program []Expr) string { var output string for i := 0; i < len(program); i++ { - expr := program[i] + var expr = program[i] fmt.Println(expr) } @@ -192,6 +187,11 @@ func parse(tokens []Token) []Expr { return condTokens } + props := + &ExprProps{ + startToken: token, + } + switch token.kind { case INDENT: currentIndent++ @@ -204,22 +204,22 @@ func parse(tokens []Token) []Expr { blockTokens := getBlock() addExpr(IfExpr{ - startToken: token, - condition: parse(condTokens), + ExprProps: props, + condition: parse(condTokens), block: BlockExpr{ - startNode: token, + ExprProps: props, expressions: parse(blockTokens), }, }) + case "elseif": condTokens := getCondition() blockTokens := getBlock() addExpr(ElseIfExpr{ - startToken: token, - condition: parse(condTokens), - block: BlockExpr{ - startNode: token, + ExprProps: props, + condition: parse(condTokens), + block: BlockExpr{ExprProps: props, expressions: parse(blockTokens), }, }) @@ -230,9 +230,9 @@ func parse(tokens []Token) []Expr { blockTokens := getBlock() addExpr(ElseExpr{ - startToken: token, + ExprProps: props, block: BlockExpr{ - startNode: token, + ExprProps: props, expressions: parse(blockTokens), }, }) @@ -270,7 +270,6 @@ func lex(source string) []Token { }) } -ParseLoop: for i := 0; i < len(source); i++ { char := source[i] column++ @@ -323,6 +322,8 @@ ParseLoop: os.Exit(1) } + fmt.Println("got a string literal", stringLiteral, startLine, startColumn) + addToken(STRING, stringLiteral, startLine, startColumn) case '+': @@ -362,7 +363,7 @@ ParseLoop: startLine := line startColumn := column - var number string + var number string // lele // keep going until we hit a non-number for i < len(source) && source[i] >= '0' && source[i] <= '9' { @@ -376,9 +377,10 @@ ParseLoop: } else if char >= 'a' && char <= 'z' || char >= 'A' && char <= 'Z' { startLine := line startColumn := column - // keep going until we hit a non-letter - var identifierOrKeyword string + var identifierOrKeyword string + + // keep going until we hit a non-letter for i < len(source) && (source[i] >= 'a' && source[i] <= 'z' || source[i] >= 'A' && source[i] <= 'Z' || @@ -400,13 +402,13 @@ ParseLoop: // check if it's a text operator if textOperators[identifierOrKeyword] { addToken(TEXTOPERATOR, identifierOrKeyword, startLine, startColumn) - continue ParseLoop + continue } // check if it's a keyword if keywords[identifierOrKeyword] { addToken(KEYWORD, identifierOrKeyword, startLine, startColumn) - continue ParseLoop + continue } addToken(IDENTIFIER, identifierOrKeyword, startLine, startColumn) diff --git a/Script/main.luau b/Script/main.luau new file mode 100644 index 0000000..6b5bfd1 --- /dev/null +++ b/Script/main.luau @@ -0,0 +1,308 @@ +local fs = require "@lune/fs" +local process = require "@lune/process" + +local exit = process.exit +local colour = require "colour" + +local EOF = "EOF" +local INDENT = "INDENT" +local SPACE = "SPACE" +local NEWLINE = "NEWLINE" +-- Literals +local IDENTIFIER = "IDENTIFIER" +local NUMBER = "NUMBER" +local COMMENT = "COMMENT" +local STRING = "STRING" +local KEYWORD = "KEYWORD" +-- Operators +local TEXTOPERATOR = "TEXTOPERATOR" +local EQUALS = "EQUALS" +local PLUS = "PLUS" +local PLUSPLUS = "PLUSPLUS" +local PLUSEQUALS = "PLUSEQUALS" +local MINUS = "MINUS" +local MINUSMINUS = "MINUSMINUS" +local MINUSEQUALS = "MINUSEQUALS" +local TIMES = "TIMES" +local DIVIDE = "DIVIDE" +local MODULO = "MODULO" +-- OPEN_BRACE = "OPEN_BRACE" +-- CLOSE_BRACE = "CLOSE_BRACE" + +local keywords = { + ["if"] = true, + ["elseif"] = true, + ["else"] = true, + ["loop"] = true, + ["for"] = true, + ["break"] = true, + ["continue"] = true, +} + +local textOperators = { + ["is"] = true, + ["and"] = true, + ["or"] = true, + ["not"] = true, +} + +type Token = { + kind: string, + value: string, + line: number, + column: number, +} + +-- local function generate(program): string +-- return "" +-- end + +-- local function parse(tokens: { Token }) +-- return {} +-- end + +local function s(str: string) + return function(n: number) + return string.sub(str, n, n) + end +end + +local function lex(source: string): { Token } + local tokens: { Token } = {} + + local function last(n: number): Token + return tokens[#tokens - n] + end + local line, column = 1, 0 + + local function addToken( + kind: string, + value: string, + newLine: number?, + newColumn: number? + ) + table.insert(tokens, { + kind = kind, + value = value, + line = newLine or line, + column = newColumn or column, + }) + end + + for i = 1, #source do + local char = s(source)(i) + column += 1 + + if char == "=" then + addToken(EQUALS, "=") + elseif char == "\n" then + addToken(NEWLINE, "\n") + line += 1 + column = 0 + elseif char == " " then + addToken(SPACE, " ") + elseif char == "\t" then + -- only if last line is a newline or an indent + if last(1).kind == NEWLINE or last(1).kind == INDENT then + addToken(INDENT, "\t") + column += 3 + else + addToken(SPACE, "\t") + end + elseif char == ";" then + -- parse till end of line + local startColumn = column + i += 1 -- skip the semicolon + local comment = "" + while i < #source and s(source)(i) ~= "\n" do + comment ..= s(source)(i) + column += 1 + i += 1 + end + column -= 1 + i -= 1 + addToken(COMMENT, comment, line, startColumn) + elseif char == '"' then + local startLine, startColumn = line, column + + local stringLiteral = "" + + column += 1 + i += 1 -- skip the first quote + + while i < #source and s(source)(i) ~= '"' do + stringLiteral ..= s(source)(i) + column += 1 + i += 1 + end + + if i == #source then + print(colour.red "unclosed string literal") + exit(1) + end + + print("got a string literal", stringLiteral, startLine, startColumn) + + addToken(STRING, stringLiteral, startLine, startColumn) + elseif char == "+" then + -- check if it's a ++ or a += or just a + + if i + 1 < #source and s(source)(i + 1) == "+" then + addToken(PLUSPLUS, "++") + i += 1 + column += 1 + elseif i + 1 < #source and s(source)(i + 1) == "=" then + addToken(PLUSEQUALS, "+=") + i += 1 + column += 1 + else + addToken(PLUS, "+") + end + elseif char == "-" then + -- check if it's a -- or a -= or just a - + if i + 1 < #source and s(source)(i + 1) == "-" then + addToken(MINUSMINUS, "--") + i += 1 + column += 1 + elseif i + 1 < #source and s(source)(i + 1) == "=" then + addToken(MINUSEQUALS, "-=") + i += 1 + column += 1 + else + addToken(MINUS, "-") + end + elseif char == "*" then + addToken(TIMES, "*") + elseif char == "/" then + addToken(DIVIDE, "/") + elseif char == "%" then + addToken(MODULO, "%") + else + if char >= "0" and char <= "9" then + local startLine, startColumn = line, column + + local number = "" + + -- keep going until we hit a non-number + while + i < #source + and s(source)(i) >= "0" + and s(source)(i) <= "9" + do + number ..= s(source)(i) + column += 1 + i += 1 + end + column -= 1 + i -= 1 + addToken(NUMBER, number, startLine, startColumn) + elseif + char >= "a" and char <= "z" or char >= "A" and char <= "Z" + then + local startLine, startColumn = line, column + + local identifierOrKeyword = "" + + -- keep going until we hit a non-letter + while + i < #source + and ( + s(source)(i) >= "a" and s(source)(i) <= "z" + or s(source)(i) >= "A" and s(source)(i) <= "Z" + or s(source)(i) >= "0" and s(source)(i) <= "9" + ) + do + identifierOrKeyword ..= s(source)(i) + column += 1 + i += 1 + end + + if i == #source then + -- you can't end a program with an identifier + print(colour.red "cant end program with identifier") + exit(1) + end + + column -= 1 + i -= 1 + + -- check if it's a text operator + if textOperators[identifierOrKeyword] then + addToken( + TEXTOPERATOR, + identifierOrKeyword, + startLine, + startColumn + ) + continue + end + + -- check if it's a keyword + if keywords[identifierOrKeyword] then + addToken( + KEYWORD, + identifierOrKeyword, + startLine, + startColumn + ) + continue + end + + addToken( + IDENTIFIER, + identifierOrKeyword, + startLine, + startColumn + ) + else + print(colour.red "that isnt a valid character", colour.yellow(char)) + exit(1) + end + end + end + + return tokens +end + +local function main() + if #process.args < 1 then + print(colour.red "No target file specified!") + print(colour.blue "Run 'melt-script help' for more information.") + exit(1) + end + local target = process.args[1] + + local fi = fs.metadata(target) + if not fi.exists then + print( + colour.red "Target file", + colour.bold(target), + colour.red "does not exist!" + ) + exit(1) + end + if fi.kind == "dir" then + print( + colour.bold(target), + colour.red "is a directory, please choose a file to compile!" + ) + exit(1) + end + + local source = fs.readFile(target) + + -- replace \r\n with \n + source = string.gsub(source, "\r\n", "\n") + -- remove trailing newlines + source = string.gsub(source, "\n+$", "") + + local tokens = lex(source) + + print(tokens) + + -- local program = parse(tokens) + -- local out = generate(program) + + -- print(out) +end + +main() diff --git a/aftman.toml b/aftman.toml index a1737b0..c872941 100644 --- a/aftman.toml +++ b/aftman.toml @@ -3,6 +3,7 @@ # To add a new tool, add an entry to this table. [tools] -selene = "Kampfkarren/selene@0.25.0" -stylua = "johnnymorganz/stylua@0.18.2" -darklua = "seaofvoices/darklua@0.10.3" +selene = "Kampfkarren/selene@0.26.1" +stylua = "johnnymorganz/stylua@0.20.0" +darklua = "seaofvoices/darklua@0.12.1" +lune = "lune-org/lune@0.8.0"