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