pass 1
This commit is contained in:
parent
4227b57e0b
commit
cff5c0ae87
67
compiler/compiler.go
Normal file
67
compiler/compiler.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
package compiler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.sharkk.net/Sharkk/Mako/parser"
|
||||||
|
"git.sharkk.net/Sharkk/Mako/vm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Compiler converts AST to bytecode
|
||||||
|
func Compile(program *parser.Program) *vm.Bytecode {
|
||||||
|
c := &compiler{
|
||||||
|
constants: []any{},
|
||||||
|
instructions: []vm.Instruction{},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, stmt := range program.Statements {
|
||||||
|
c.compileStatement(stmt)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &vm.Bytecode{
|
||||||
|
Constants: c.constants,
|
||||||
|
Instructions: c.instructions,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type compiler struct {
|
||||||
|
constants []any
|
||||||
|
instructions []vm.Instruction
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *compiler) compileStatement(stmt parser.Statement) {
|
||||||
|
switch s := stmt.(type) {
|
||||||
|
case *parser.VariableStatement:
|
||||||
|
c.compileExpression(s.Value)
|
||||||
|
nameIndex := c.addConstant(s.Name.Value)
|
||||||
|
c.emit(vm.OpSetGlobal, nameIndex)
|
||||||
|
case *parser.EchoStatement:
|
||||||
|
c.compileExpression(s.Value)
|
||||||
|
c.emit(vm.OpEcho, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *compiler) compileExpression(expr parser.Expression) {
|
||||||
|
switch e := expr.(type) {
|
||||||
|
case *parser.StringLiteral:
|
||||||
|
constIndex := c.addConstant(e.Value)
|
||||||
|
c.emit(vm.OpConstant, constIndex)
|
||||||
|
case *parser.NumberLiteral:
|
||||||
|
constIndex := c.addConstant(e.Value)
|
||||||
|
c.emit(vm.OpConstant, constIndex)
|
||||||
|
case *parser.Identifier:
|
||||||
|
nameIndex := c.addConstant(e.Value)
|
||||||
|
c.emit(vm.OpGetGlobal, nameIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *compiler) addConstant(value any) int {
|
||||||
|
c.constants = append(c.constants, value)
|
||||||
|
return len(c.constants) - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *compiler) emit(op vm.Opcode, operand int) {
|
||||||
|
instruction := vm.Instruction{
|
||||||
|
Opcode: op,
|
||||||
|
Operand: operand,
|
||||||
|
}
|
||||||
|
c.instructions = append(c.instructions, instruction)
|
||||||
|
}
|
121
lexer/lexer.go
Normal file
121
lexer/lexer.go
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
package lexer
|
||||||
|
|
||||||
|
type TokenType byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
TokenEOF TokenType = iota
|
||||||
|
TokenIdentifier
|
||||||
|
TokenString
|
||||||
|
TokenNumber
|
||||||
|
TokenEqual
|
||||||
|
TokenEcho
|
||||||
|
TokenSemicolon
|
||||||
|
)
|
||||||
|
|
||||||
|
type Token struct {
|
||||||
|
Type TokenType
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Lexer struct {
|
||||||
|
input string
|
||||||
|
pos int
|
||||||
|
readPos int
|
||||||
|
ch byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(input string) *Lexer {
|
||||||
|
l := &Lexer{input: input}
|
||||||
|
l.readChar()
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Lexer) readChar() {
|
||||||
|
if l.readPos >= len(l.input) {
|
||||||
|
l.ch = 0
|
||||||
|
} else {
|
||||||
|
l.ch = l.input[l.readPos]
|
||||||
|
}
|
||||||
|
l.pos = l.readPos
|
||||||
|
l.readPos++
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Lexer) NextToken() Token {
|
||||||
|
var tok Token
|
||||||
|
|
||||||
|
l.skipWhitespace()
|
||||||
|
|
||||||
|
switch l.ch {
|
||||||
|
case '=':
|
||||||
|
tok = Token{Type: TokenEqual, Value: "="}
|
||||||
|
case ';':
|
||||||
|
tok = Token{Type: TokenSemicolon, Value: ";"}
|
||||||
|
case '"':
|
||||||
|
tok = Token{Type: TokenString, Value: l.readString()}
|
||||||
|
return tok
|
||||||
|
case 0:
|
||||||
|
tok = Token{Type: TokenEOF, Value: ""}
|
||||||
|
default:
|
||||||
|
if isLetter(l.ch) {
|
||||||
|
tok.Value = l.readIdentifier()
|
||||||
|
if tok.Value == "echo" {
|
||||||
|
tok.Type = TokenEcho
|
||||||
|
} else {
|
||||||
|
tok.Type = TokenIdentifier
|
||||||
|
}
|
||||||
|
return tok
|
||||||
|
} else if isDigit(l.ch) {
|
||||||
|
tok.Type = TokenNumber
|
||||||
|
tok.Value = l.readNumber()
|
||||||
|
return tok
|
||||||
|
} else {
|
||||||
|
tok = Token{Type: TokenEOF, Value: ""}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
l.readChar()
|
||||||
|
return tok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Lexer) skipWhitespace() {
|
||||||
|
for l.ch == ' ' || l.ch == '\t' || l.ch == '\n' || l.ch == '\r' {
|
||||||
|
l.readChar()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Lexer) readIdentifier() string {
|
||||||
|
pos := l.pos
|
||||||
|
for isLetter(l.ch) || isDigit(l.ch) {
|
||||||
|
l.readChar()
|
||||||
|
}
|
||||||
|
return l.input[pos:l.pos]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Lexer) readNumber() string {
|
||||||
|
pos := l.pos
|
||||||
|
for isDigit(l.ch) {
|
||||||
|
l.readChar()
|
||||||
|
}
|
||||||
|
return l.input[pos:l.pos]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Lexer) readString() string {
|
||||||
|
pos := l.pos + 1
|
||||||
|
for {
|
||||||
|
l.readChar()
|
||||||
|
if l.ch == '"' || l.ch == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
str := l.input[pos:l.pos]
|
||||||
|
l.readChar() // Skip closing quote
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
func isLetter(ch byte) bool {
|
||||||
|
return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_'
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDigit(ch byte) bool {
|
||||||
|
return '0' <= ch && ch <= '9'
|
||||||
|
}
|
45
main.go
Normal file
45
main.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// File: cmd/main.go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.sharkk.net/Sharkk/Mako/compiler"
|
||||||
|
"git.sharkk.net/Sharkk/Mako/lexer"
|
||||||
|
"git.sharkk.net/Sharkk/Mako/parser"
|
||||||
|
"git.sharkk.net/Sharkk/Mako/vm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
scanner := bufio.NewScanner(os.Stdin)
|
||||||
|
virtualMachine := vm.New()
|
||||||
|
|
||||||
|
fmt.Println("LuaGo Interpreter (type 'exit' to quit)")
|
||||||
|
for {
|
||||||
|
fmt.Print(">> ")
|
||||||
|
if !scanner.Scan() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
input := scanner.Text()
|
||||||
|
if input == "exit" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
lex := lexer.New(input)
|
||||||
|
p := parser.New(lex)
|
||||||
|
program := p.ParseProgram()
|
||||||
|
|
||||||
|
if len(p.Errors()) > 0 {
|
||||||
|
for _, err := range p.Errors() {
|
||||||
|
fmt.Printf("Error: %s\n", err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
bytecode := compiler.Compile(program)
|
||||||
|
virtualMachine.Run(bytecode)
|
||||||
|
}
|
||||||
|
}
|
69
parser/ast.go
Normal file
69
parser/ast.go
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
package parser
|
||||||
|
|
||||||
|
import "git.sharkk.net/Sharkk/Mako/lexer"
|
||||||
|
|
||||||
|
type Node interface {
|
||||||
|
TokenLiteral() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Statement interface {
|
||||||
|
Node
|
||||||
|
statementNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Expression interface {
|
||||||
|
Node
|
||||||
|
expressionNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Program struct {
|
||||||
|
Statements []Statement
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Program) TokenLiteral() string {
|
||||||
|
if len(p.Statements) > 0 {
|
||||||
|
return p.Statements[0].TokenLiteral()
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type VariableStatement struct {
|
||||||
|
Token lexer.Token
|
||||||
|
Name *Identifier
|
||||||
|
Value Expression
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vs *VariableStatement) statementNode() {}
|
||||||
|
func (vs *VariableStatement) TokenLiteral() string { return vs.Token.Value }
|
||||||
|
|
||||||
|
type EchoStatement struct {
|
||||||
|
Token lexer.Token
|
||||||
|
Value Expression
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *EchoStatement) statementNode() {}
|
||||||
|
func (es *EchoStatement) TokenLiteral() string { return es.Token.Value }
|
||||||
|
|
||||||
|
type Identifier struct {
|
||||||
|
Token lexer.Token
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Identifier) expressionNode() {}
|
||||||
|
func (i *Identifier) TokenLiteral() string { return i.Token.Value }
|
||||||
|
|
||||||
|
type StringLiteral struct {
|
||||||
|
Token lexer.Token
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sl *StringLiteral) expressionNode() {}
|
||||||
|
func (sl *StringLiteral) TokenLiteral() string { return sl.Token.Value }
|
||||||
|
|
||||||
|
type NumberLiteral struct {
|
||||||
|
Token lexer.Token
|
||||||
|
Value float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nl *NumberLiteral) expressionNode() {}
|
||||||
|
func (nl *NumberLiteral) TokenLiteral() string { return nl.Token.Value }
|
110
parser/parser.go
Normal file
110
parser/parser.go
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"git.sharkk.net/Sharkk/Mako/lexer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Parser struct {
|
||||||
|
l *lexer.Lexer
|
||||||
|
curToken lexer.Token
|
||||||
|
peekToken lexer.Token
|
||||||
|
errors []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(l *lexer.Lexer) *Parser {
|
||||||
|
p := &Parser{l: l, errors: []string{}}
|
||||||
|
p.nextToken()
|
||||||
|
p.nextToken()
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) nextToken() {
|
||||||
|
p.curToken = p.peekToken
|
||||||
|
p.peekToken = p.l.NextToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) Errors() []string {
|
||||||
|
return p.errors
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) ParseProgram() *Program {
|
||||||
|
program := &Program{Statements: []Statement{}}
|
||||||
|
|
||||||
|
for p.curToken.Type != lexer.TokenEOF {
|
||||||
|
stmt := p.parseStatement()
|
||||||
|
if stmt != nil {
|
||||||
|
program.Statements = append(program.Statements, stmt)
|
||||||
|
}
|
||||||
|
p.nextToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
return program
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) parseStatement() Statement {
|
||||||
|
switch p.curToken.Type {
|
||||||
|
case lexer.TokenIdentifier:
|
||||||
|
if p.peekToken.Type == lexer.TokenEqual {
|
||||||
|
return p.parseVariableStatement()
|
||||||
|
}
|
||||||
|
case lexer.TokenEcho:
|
||||||
|
return p.parseEchoStatement()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) parseVariableStatement() *VariableStatement {
|
||||||
|
stmt := &VariableStatement{Token: p.curToken}
|
||||||
|
|
||||||
|
stmt.Name = &Identifier{Token: p.curToken, Value: p.curToken.Value}
|
||||||
|
|
||||||
|
p.nextToken() // Skip identifier
|
||||||
|
p.nextToken() // Skip =
|
||||||
|
|
||||||
|
switch p.curToken.Type {
|
||||||
|
case lexer.TokenString:
|
||||||
|
stmt.Value = &StringLiteral{Token: p.curToken, Value: p.curToken.Value}
|
||||||
|
case lexer.TokenNumber:
|
||||||
|
num, err := strconv.ParseFloat(p.curToken.Value, 64)
|
||||||
|
if err != nil {
|
||||||
|
p.errors = append(p.errors, fmt.Sprintf("could not parse %q as float", p.curToken.Value))
|
||||||
|
}
|
||||||
|
stmt.Value = &NumberLiteral{Token: p.curToken, Value: num}
|
||||||
|
case lexer.TokenIdentifier:
|
||||||
|
stmt.Value = &Identifier{Token: p.curToken, Value: p.curToken.Value}
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.peekToken.Type == lexer.TokenSemicolon {
|
||||||
|
p.nextToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
return stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) parseEchoStatement() *EchoStatement {
|
||||||
|
stmt := &EchoStatement{Token: p.curToken}
|
||||||
|
|
||||||
|
p.nextToken()
|
||||||
|
|
||||||
|
switch p.curToken.Type {
|
||||||
|
case lexer.TokenString:
|
||||||
|
stmt.Value = &StringLiteral{Token: p.curToken, Value: p.curToken.Value}
|
||||||
|
case lexer.TokenNumber:
|
||||||
|
num, err := strconv.ParseFloat(p.curToken.Value, 64)
|
||||||
|
if err != nil {
|
||||||
|
p.errors = append(p.errors, fmt.Sprintf("could not parse %q as float", p.curToken.Value))
|
||||||
|
}
|
||||||
|
stmt.Value = &NumberLiteral{Token: p.curToken, Value: num}
|
||||||
|
case lexer.TokenIdentifier:
|
||||||
|
stmt.Value = &Identifier{Token: p.curToken, Value: p.curToken.Value}
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.peekToken.Type == lexer.TokenSemicolon {
|
||||||
|
p.nextToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
return stmt
|
||||||
|
}
|
31
types/types.go
Normal file
31
types/types.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
type ValueType byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
TypeNull ValueType = iota
|
||||||
|
TypeNumber
|
||||||
|
TypeString
|
||||||
|
TypeBoolean
|
||||||
|
)
|
||||||
|
|
||||||
|
type Value struct {
|
||||||
|
Type ValueType
|
||||||
|
Data any
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewString(s string) Value {
|
||||||
|
return Value{Type: TypeString, Data: s}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNumber(n float64) Value {
|
||||||
|
return Value{Type: TypeNumber, Data: n}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBoolean(b bool) Value {
|
||||||
|
return Value{Type: TypeBoolean, Data: b}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNull() Value {
|
||||||
|
return Value{Type: TypeNull, Data: nil}
|
||||||
|
}
|
104
vm/vm.go
Normal file
104
vm/vm.go
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
package vm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.sharkk.net/Sharkk/Mako/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Opcode byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
OpConstant Opcode = iota
|
||||||
|
OpSetGlobal
|
||||||
|
OpGetGlobal
|
||||||
|
OpEcho
|
||||||
|
)
|
||||||
|
|
||||||
|
type Instruction struct {
|
||||||
|
Opcode Opcode
|
||||||
|
Operand int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Bytecode struct {
|
||||||
|
Constants []any
|
||||||
|
Instructions []Instruction
|
||||||
|
}
|
||||||
|
|
||||||
|
type VM struct {
|
||||||
|
constants []any
|
||||||
|
globals map[string]types.Value
|
||||||
|
stack []types.Value
|
||||||
|
sp int // Stack pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() *VM {
|
||||||
|
return &VM{
|
||||||
|
globals: make(map[string]types.Value),
|
||||||
|
stack: make([]types.Value, 1024), // Fixed stack size for now
|
||||||
|
sp: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vm *VM) Run(bytecode *Bytecode) {
|
||||||
|
vm.constants = bytecode.Constants
|
||||||
|
|
||||||
|
for ip := 0; ip < len(bytecode.Instructions); ip++ {
|
||||||
|
instruction := bytecode.Instructions[ip]
|
||||||
|
|
||||||
|
switch instruction.Opcode {
|
||||||
|
case OpConstant:
|
||||||
|
// Push constant to stack
|
||||||
|
constIndex := instruction.Operand
|
||||||
|
constant := vm.constants[constIndex]
|
||||||
|
|
||||||
|
switch v := constant.(type) {
|
||||||
|
case string:
|
||||||
|
vm.push(types.NewString(v))
|
||||||
|
case float64:
|
||||||
|
vm.push(types.NewNumber(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
case OpSetGlobal:
|
||||||
|
// Set global variable
|
||||||
|
constIndex := instruction.Operand
|
||||||
|
name := vm.constants[constIndex].(string)
|
||||||
|
value := vm.pop()
|
||||||
|
vm.globals[name] = value
|
||||||
|
|
||||||
|
case OpGetGlobal:
|
||||||
|
// Get global variable
|
||||||
|
constIndex := instruction.Operand
|
||||||
|
name := vm.constants[constIndex].(string)
|
||||||
|
if val, ok := vm.globals[name]; ok {
|
||||||
|
vm.push(val)
|
||||||
|
} else {
|
||||||
|
vm.push(types.NewNull())
|
||||||
|
}
|
||||||
|
|
||||||
|
case OpEcho:
|
||||||
|
// Print value
|
||||||
|
value := vm.pop()
|
||||||
|
switch value.Type {
|
||||||
|
case types.TypeString:
|
||||||
|
fmt.Println(value.Data.(string))
|
||||||
|
case types.TypeNumber:
|
||||||
|
fmt.Println(value.Data.(float64))
|
||||||
|
case types.TypeBoolean:
|
||||||
|
fmt.Println(value.Data.(bool))
|
||||||
|
case types.TypeNull:
|
||||||
|
fmt.Println("null")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vm *VM) push(value types.Value) {
|
||||||
|
vm.stack[vm.sp] = value
|
||||||
|
vm.sp++
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vm *VM) pop() types.Value {
|
||||||
|
vm.sp--
|
||||||
|
return vm.stack[vm.sp]
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user