Improve parser and begin work on outputting Lua

This commit is contained in:
Lewin Kelly 2024-01-22 23:46:49 +00:00
parent 181334c028
commit 81c7c11868
4 changed files with 188 additions and 32 deletions

View File

@ -2,4 +2,4 @@ module MeltScript
go 1.21.3
require github.com/TwiN/go-color v1.4.1 // indirect
require github.com/TwiN/go-color v1.4.1

View File

@ -29,23 +29,47 @@ const (
KEYWORD = "KEYWORD"
// Operators
EQUALS = "EQUALS"
TEXTOPERATOR = "TEXTOPERATOR"
EQUALS = "EQUALS"
PLUS = "PLUS"
PLUSPLUS = "PLUSPLUS"
PLUSEQUALS = "PLUSEQUALS"
MINUS = "MINUS"
MINUSMINUS = "MINUSMINUS"
MINUSEQUALS = "MINUSEQUALS"
TIMES = "TIMES"
DIVIDE = "DIVIDE"
MODULO = "MODULO"
// OPEN_BRACE = "OPEN_BRACE"
// CLOSE_BRACE = "CLOSE_BRACE"
)
func lex(source string) []token {
keywords := map[string]bool{
"if": true,
"elseif": true,
"else": true,
"loop": true,
"for": true,
"break": true,
"continue": true,
}
var keywords = map[string]bool{
"if": true,
"elseif": true,
"else": true,
"loop": true,
"for": true,
"break": true,
"continue": true,
}
var textOperators = map[string]bool{
"is": true,
"and": true,
"or": true,
"not": true,
// "nand": true,
// "xor": true,
// "nor": true,
// "xnor": true,
}
func lex(source string) []token {
var tokens []token
last := func(n int) token {
@ -114,7 +138,45 @@ ParseLoop:
i++
}
if i == len(source) {
fmt.Println(c.InRed("unclosed string literal"))
os.Exit(1)
}
addToken(STRING, stringLiteral, startLine, startColumn)
case '+':
// check if it's a ++ or += or just a +
if i+1 < len(source) && source[i+1] == '+' {
addToken(PLUSPLUS, "++")
i++
column++
} else if i+1 < len(source) && source[i+1] == '=' {
addToken(PLUSEQUALS, "+=")
i++
column++
} else {
addToken(PLUS, "+")
}
case '-':
// check if it's a -- or -= or just a -
if i+1 < len(source) && source[i+1] == '-' {
addToken(MINUSMINUS, "--")
i++
column++
} else if i+1 < len(source) && source[i+1] == '=' {
addToken(MINUSEQUALS, "-=")
i++
column++
} else {
addToken(MINUS, "-")
}
case '*':
addToken(TIMES, "*")
case '/':
addToken(DIVIDE, "/")
case '%':
addToken(MODULO, "%")
default:
if char >= '0' && char <= '9' {
startLine := line
@ -137,19 +199,14 @@ ParseLoop:
// keep going until we hit a non-letter
var identifierOrKeyword string
for i < len(source) && (source[i] >= 'a' && source[i] <= 'z' || source[i] >= 'A' && source[i] <= 'Z') {
for i < len(source) &&
(source[i] >= 'a' && source[i] <= 'z' ||
source[i] >= 'A' && source[i] <= 'Z' ||
source[i] >= '0' && source[i] <= '9') {
identifierOrKeyword += string(source[i])
column++
i++
}
column--
i--
// check if it's a keyword
if keywords[identifierOrKeyword] {
addToken(KEYWORD, identifierOrKeyword, startLine, startColumn)
continue ParseLoop
}
if i == len(source) {
// you can't end a program with an identifier (yet)
@ -157,6 +214,21 @@ ParseLoop:
os.Exit(1)
}
column--
i--
// check if it's a text operator
if textOperators[identifierOrKeyword] {
addToken(TEXTOPERATOR, identifierOrKeyword, startLine, startColumn)
continue ParseLoop
}
// check if it's a keyword
if keywords[identifierOrKeyword] {
addToken(KEYWORD, identifierOrKeyword, startLine, startColumn)
continue ParseLoop
}
addToken(IDENTIFIER, identifierOrKeyword, startLine, startColumn)
} else {
fmt.Println(c.InRed("that isnt a valid character"), c.InYellow(string(char)))
@ -168,6 +240,80 @@ ParseLoop:
return tokens
}
func generate(tokens []token) string {
var output string
for i := 0; i < len(tokens); i++ {
currentToken := tokens[i]
nextToken := func(n int) token {
// gets the nth token after the current token
// skips over spaces and newlines
for i+n < len(tokens) {
if tokens[i+n].kind == SPACE || tokens[i+n].kind == NEWLINE {
n++
} else {
return tokens[i+n]
}
}
return token{}
}
usedIdentifiers := map[string]bool{}
switch currentToken.kind {
case NEWLINE:
output += "\n"
case INDENT:
output += "\t"
case NUMBER:
output += currentToken.value
case STRING:
output += fmt.Sprintf("\"%s\"", currentToken.value)
case IDENTIFIER:
nextKind := nextToken(1).kind
switch nextKind {
case EQUALS:
// variable assignment
if !usedIdentifiers[currentToken.value] {
output += "local "
}
output += currentToken.value
usedIdentifiers[currentToken.value] = true
case PLUSPLUS:
output += currentToken.value + " = " + currentToken.value + " + 1"
i++
case MINUSMINUS:
output += currentToken.value + " = " + currentToken.value + " - 1"
i++
default:
output += currentToken.value + " "
}
case EQUALS:
output += " = "
case COMMENT:
output += " --" + currentToken.value
case TEXTOPERATOR:
switch currentToken.value {
case "is":
output += "== "
default:
output += currentToken.value
}
// case KEYWORD:
// switch currentToken.value {
// case "loop":
// output += "while true do"
// default:
// output += currentToken.value
// }
// output += " "
}
}
return output
}
func main() {
if len(os.Args) < 2 {
fmt.Println(c.InRed("No target file specified!"))
@ -194,25 +340,30 @@ func main() {
// replace \r\n with \n
sourceString := strings.Replace(string(source), "\r\n", "\n", -1)
// remove trailing newlines
sourceString = strings.TrimRight(sourceString, "\n")
tokens := lex(sourceString)
// out := generate(tokens)
for _, token := range tokens {
if token.kind == "SPACE" {
continue
}
if token.kind == "NEWLINE" {
fmt.Println("──────┼─────────────┼─────────────────────────────")
fmt.Println("───────────────────────────────┼─────────────────────────────")
continue
}
toPrint := []string{
fmt.Sprintf("%d:%d", token.line, token.column),
toPrint := []any{
fmt.Sprintf("%s:%d:%d", target, token.line, token.column),
c.InYellow(token.kind),
c.InPurple(token.value),
}
// print in a nice format
fmt.Printf("%-5s │ %-20s │ %s\n", toPrint[0], toPrint[1], toPrint[2])
fmt.Printf("%-15s │ %-22s │ %s\n", toPrint...)
}
out := generate(tokens)
fmt.Println(out)
}

View File

@ -13,3 +13,13 @@ loop
print "infinite loop"
print "just kidding"
break
for 5
x++
print "this prints 5 times"
print "x is now {x}"
y = if x is 6
"yes"
else
"no"

View File

@ -1,5 +0,0 @@
import { Elysia } from "elysia"
const app = new Elysia().get("/", () => "Hello World!").listen(2013)
console.log(`Serving at http://${app.server?.hostname}:${app.server?.port}`)