Experiment with some dumb stuff in Luau

This commit is contained in:
Lewin Kelly 2024-02-13 23:51:32 +00:00
parent a2aad0e4b9
commit 3c287f0b8f
5 changed files with 417 additions and 53 deletions

3
.luaurc Normal file
View File

@ -0,0 +1,3 @@
{
"languageMode": "strict"
}

50
Script/colour.luau Normal file
View File

@ -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

View File

@ -22,59 +22,54 @@ type Expr interface {
Node Node
} }
type Identifier struct { type ExprProps struct {
startToken Token startToken Token
name string
} }
type AssignmentExpr struct { // type Identifier struct {
startToken Token // *ExprProps
left Expr // name string
right Expr // }
}
type BinaryExpr struct { // type AssignmentExpr struct {
startToken Token // *ExprProps
left Expr // left Expr
right Expr // right Expr
} // }
type UnaryExpr struct { // type BinaryExpr struct {
startToken Token // *ExprProps
expr Expr // left Expr
} // right Expr
// }
// type UnaryExpr struct {
// *ExprProps
// expr Expr
// }
type IfExpr struct { type IfExpr struct {
startToken Token *ExprProps
condition Expr condition Expr
block BlockExpr block BlockExpr
} }
type ElseIfExpr struct { type ElseIfExpr struct {
startToken Token *ExprProps
condition Expr condition Expr
block BlockExpr block BlockExpr
} }
type ElseExpr struct { type ElseExpr struct {
startToken Token *ExprProps
block BlockExpr block BlockExpr
} }
type BlockExpr struct { type BlockExpr struct {
startNode Node *ExprProps
expressions []Expr 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 ( const (
EOF = "EOF" EOF = "EOF"
INDENT = "INDENT" INDENT = "INDENT"
@ -129,7 +124,7 @@ func generate(program []Expr) string {
var output string var output string
for i := 0; i < len(program); i++ { for i := 0; i < len(program); i++ {
expr := program[i] var expr = program[i]
fmt.Println(expr) fmt.Println(expr)
} }
@ -192,6 +187,11 @@ func parse(tokens []Token) []Expr {
return condTokens return condTokens
} }
props :=
&ExprProps{
startToken: token,
}
switch token.kind { switch token.kind {
case INDENT: case INDENT:
currentIndent++ currentIndent++
@ -204,22 +204,22 @@ func parse(tokens []Token) []Expr {
blockTokens := getBlock() blockTokens := getBlock()
addExpr(IfExpr{ addExpr(IfExpr{
startToken: token, ExprProps: props,
condition: parse(condTokens), condition: parse(condTokens),
block: BlockExpr{ block: BlockExpr{
startNode: token, ExprProps: props,
expressions: parse(blockTokens), expressions: parse(blockTokens),
}, },
}) })
case "elseif": case "elseif":
condTokens := getCondition() condTokens := getCondition()
blockTokens := getBlock() blockTokens := getBlock()
addExpr(ElseIfExpr{ addExpr(ElseIfExpr{
startToken: token, ExprProps: props,
condition: parse(condTokens), condition: parse(condTokens),
block: BlockExpr{ block: BlockExpr{ExprProps: props,
startNode: token,
expressions: parse(blockTokens), expressions: parse(blockTokens),
}, },
}) })
@ -230,9 +230,9 @@ func parse(tokens []Token) []Expr {
blockTokens := getBlock() blockTokens := getBlock()
addExpr(ElseExpr{ addExpr(ElseExpr{
startToken: token, ExprProps: props,
block: BlockExpr{ block: BlockExpr{
startNode: token, ExprProps: props,
expressions: parse(blockTokens), expressions: parse(blockTokens),
}, },
}) })
@ -270,7 +270,6 @@ func lex(source string) []Token {
}) })
} }
ParseLoop:
for i := 0; i < len(source); i++ { for i := 0; i < len(source); i++ {
char := source[i] char := source[i]
column++ column++
@ -323,6 +322,8 @@ ParseLoop:
os.Exit(1) os.Exit(1)
} }
fmt.Println("got a string literal", stringLiteral, startLine, startColumn)
addToken(STRING, stringLiteral, startLine, startColumn) addToken(STRING, stringLiteral, startLine, startColumn)
case '+': case '+':
@ -362,7 +363,7 @@ ParseLoop:
startLine := line startLine := line
startColumn := column startColumn := column
var number string var number string // lele
// keep going until we hit a non-number // keep going until we hit a non-number
for i < len(source) && source[i] >= '0' && source[i] <= '9' { 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' { } else if char >= 'a' && char <= 'z' || char >= 'A' && char <= 'Z' {
startLine := line startLine := line
startColumn := column 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) && for i < len(source) &&
(source[i] >= 'a' && source[i] <= 'z' || (source[i] >= 'a' && source[i] <= 'z' ||
source[i] >= 'A' && source[i] <= 'Z' || source[i] >= 'A' && source[i] <= 'Z' ||
@ -400,13 +402,13 @@ ParseLoop:
// check if it's a text operator // check if it's a text operator
if textOperators[identifierOrKeyword] { if textOperators[identifierOrKeyword] {
addToken(TEXTOPERATOR, identifierOrKeyword, startLine, startColumn) addToken(TEXTOPERATOR, identifierOrKeyword, startLine, startColumn)
continue ParseLoop continue
} }
// check if it's a keyword // check if it's a keyword
if keywords[identifierOrKeyword] { if keywords[identifierOrKeyword] {
addToken(KEYWORD, identifierOrKeyword, startLine, startColumn) addToken(KEYWORD, identifierOrKeyword, startLine, startColumn)
continue ParseLoop continue
} }
addToken(IDENTIFIER, identifierOrKeyword, startLine, startColumn) addToken(IDENTIFIER, identifierOrKeyword, startLine, startColumn)

308
Script/main.luau Normal file
View File

@ -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()

View File

@ -3,6 +3,7 @@
# To add a new tool, add an entry to this table. # To add a new tool, add an entry to this table.
[tools] [tools]
selene = "Kampfkarren/selene@0.25.0" selene = "Kampfkarren/selene@0.26.1"
stylua = "johnnymorganz/stylua@0.18.2" stylua = "johnnymorganz/stylua@0.20.0"
darklua = "seaofvoices/darklua@0.10.3" darklua = "seaofvoices/darklua@0.12.1"
lune = "lune-org/lune@0.8.0"