Add importing, lists and objects, and add basic functions (global and on values)
This commit is contained in:
parent
449f7cd815
commit
634a4a2b61
11 changed files with 919 additions and 60 deletions
51
cli/main.go
51
cli/main.go
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"neemek.com/anglais/core"
|
"neemek.com/anglais/core"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Context struct {
|
type Context struct {
|
||||||
|
|
@ -16,6 +17,37 @@ type RunCmd struct {
|
||||||
File string `arg:"" name:"file" help:"File to read program from" type:"existingfile"`
|
File string `arg:"" name:"file" help:"File to read program from" type:"existingfile"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WorkingDirectoryResolver resolves imports relative to the working directory
|
||||||
|
type WorkingDirectoryResolver struct {
|
||||||
|
workingDirectory string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *WorkingDirectoryResolver) Resolve(path string) (core.Node, error) {
|
||||||
|
pth := filepath.Join(r.workingDirectory, path)
|
||||||
|
f, err := os.ReadFile(pth)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
str := string(f)
|
||||||
|
|
||||||
|
l := core.NewLexer(str)
|
||||||
|
|
||||||
|
tokens, err := l.Tokenize()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
p := core.NewParser(tokens)
|
||||||
|
|
||||||
|
tree, err := p.Parse()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tree, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (cmd *RunCmd) Run(ctx *Context) error {
|
func (cmd *RunCmd) Run(ctx *Context) error {
|
||||||
if ctx.Debug {
|
if ctx.Debug {
|
||||||
log.Println("Reading file")
|
log.Println("Reading file")
|
||||||
|
|
@ -72,6 +104,15 @@ func (cmd *RunCmd) Run(ctx *Context) error {
|
||||||
}
|
}
|
||||||
c := core.NewCompiler()
|
c := core.NewCompiler()
|
||||||
|
|
||||||
|
if ctx.Debug {
|
||||||
|
log.Println("Setting imports resolver")
|
||||||
|
}
|
||||||
|
|
||||||
|
dir, _ := filepath.Split(cmd.File)
|
||||||
|
c.SetImportsResolver(&WorkingDirectoryResolver{
|
||||||
|
dir,
|
||||||
|
})
|
||||||
|
|
||||||
if ctx.Debug {
|
if ctx.Debug {
|
||||||
log.Println("Compiling parse tree")
|
log.Println("Compiling parse tree")
|
||||||
}
|
}
|
||||||
|
|
@ -162,8 +203,18 @@ func (cmd *CompileCmd) Run(ctx *Context) error {
|
||||||
if ctx.Debug {
|
if ctx.Debug {
|
||||||
log.Println("Initialized compiler")
|
log.Println("Initialized compiler")
|
||||||
}
|
}
|
||||||
|
|
||||||
c := core.NewCompiler()
|
c := core.NewCompiler()
|
||||||
|
|
||||||
|
if ctx.Debug {
|
||||||
|
log.Println("Setting import resolver")
|
||||||
|
}
|
||||||
|
|
||||||
|
dir, _ := filepath.Split(cmd.File)
|
||||||
|
c.SetImportsResolver(&WorkingDirectoryResolver{
|
||||||
|
dir,
|
||||||
|
})
|
||||||
|
|
||||||
if ctx.Debug {
|
if ctx.Debug {
|
||||||
log.Println("Compiling parse tree")
|
log.Println("Compiling parse tree")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,16 @@ type Compiler struct {
|
||||||
ip Pos
|
ip Pos
|
||||||
scope Pos
|
scope Pos
|
||||||
|
|
||||||
|
imports map[string]Node
|
||||||
|
resolver ImportsResolver
|
||||||
|
|
||||||
stack *Stack[LocalVariable]
|
stack *Stack[LocalVariable]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ImportsResolver interface {
|
||||||
|
Resolve(path string) (Node, error)
|
||||||
|
}
|
||||||
|
|
||||||
type LocalVariable struct {
|
type LocalVariable struct {
|
||||||
name string
|
name string
|
||||||
scope int
|
scope int
|
||||||
|
|
@ -15,10 +22,11 @@ type LocalVariable struct {
|
||||||
|
|
||||||
func NewCompiler() *Compiler {
|
func NewCompiler() *Compiler {
|
||||||
c := &Compiler{
|
c := &Compiler{
|
||||||
Chunk: NewChunk(make([]Bytecode, 0), make([]Value, 0)),
|
Chunk: NewChunk(make([]Bytecode, 0), make([]Value, 0)),
|
||||||
ip: 0,
|
ip: 0,
|
||||||
scope: 0,
|
scope: 0,
|
||||||
stack: NewStack[LocalVariable](256),
|
stack: NewStack[LocalVariable](256),
|
||||||
|
imports: make(map[string]Node),
|
||||||
}
|
}
|
||||||
|
|
||||||
return c
|
return c
|
||||||
|
|
@ -63,6 +71,14 @@ func (c *Compiler) Compile(tree Node) {
|
||||||
c.add(InstructionConstant)
|
c.add(InstructionConstant)
|
||||||
c.addConstant(tree.(*NumberNode).value)
|
c.addConstant(tree.(*NumberNode).value)
|
||||||
|
|
||||||
|
case ListNodeType:
|
||||||
|
v := tree.(*ListNode).items
|
||||||
|
c.add(InstructionNewList)
|
||||||
|
for _, n := range v {
|
||||||
|
c.Compile(n)
|
||||||
|
c.add(InstructionAppend)
|
||||||
|
}
|
||||||
|
|
||||||
case ReferenceNodeType:
|
case ReferenceNodeType:
|
||||||
c.getVar(tree.(*ReferenceNode).name)
|
c.getVar(tree.(*ReferenceNode).name)
|
||||||
|
|
||||||
|
|
@ -155,7 +171,7 @@ func (c *Compiler) Compile(tree Node) {
|
||||||
c.Compile(arg)
|
c.Compile(arg)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.getVar(n.name)
|
c.Compile(n.source)
|
||||||
|
|
||||||
c.add(InstructionCall)
|
c.add(InstructionCall)
|
||||||
|
|
||||||
|
|
@ -194,12 +210,28 @@ func (c *Compiler) Compile(tree Node) {
|
||||||
n.name,
|
n.name,
|
||||||
n.params,
|
n.params,
|
||||||
c.Chunk,
|
c.Chunk,
|
||||||
|
nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
// restore old chunk and ip
|
// restore old chunk and ip
|
||||||
c.Chunk = mc
|
c.Chunk = mc
|
||||||
c.ip = mip
|
c.ip = mip
|
||||||
|
|
||||||
|
case AccessNodeType:
|
||||||
|
n := tree.(*AccessNode)
|
||||||
|
c.Compile(n.source)
|
||||||
|
c.add(InstructionAccessProperty)
|
||||||
|
c.addConstant(StringValue(n.property))
|
||||||
|
|
||||||
|
case ImportNodeType:
|
||||||
|
n := tree.(*ImportNode)
|
||||||
|
|
||||||
|
t := c.resolveImport(n.path).(*BlockNode)
|
||||||
|
|
||||||
|
for _, statement := range t.statements {
|
||||||
|
c.Compile(statement)
|
||||||
|
}
|
||||||
|
|
||||||
case ReturnNodeType:
|
case ReturnNodeType:
|
||||||
c.Compile(tree.(*ReturnNode).value)
|
c.Compile(tree.(*ReturnNode).value)
|
||||||
c.add(InstructionReturn)
|
c.add(InstructionReturn)
|
||||||
|
|
@ -305,6 +337,26 @@ func (c *Compiler) descend() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Compiler) resolveImport(path string) Node {
|
||||||
|
if chunk, ok := c.imports[path]; ok {
|
||||||
|
return chunk
|
||||||
|
}
|
||||||
|
|
||||||
|
// find tree
|
||||||
|
tree, err := c.resolver.Resolve(path)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.imports[path] = tree
|
||||||
|
|
||||||
|
return tree
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Compiler) SetImportsResolver(resolver ImportsResolver) {
|
||||||
|
c.resolver = resolver
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Compiler) advance(amount Pos) {
|
func (c *Compiler) advance(amount Pos) {
|
||||||
c.ip += amount
|
c.ip += amount
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -267,6 +267,7 @@ func GetCompileTestData() map[string]CompileTestData {
|
||||||
StringValue("a"), StringValue("b"),
|
StringValue("a"), StringValue("b"),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
nil,
|
||||||
},
|
},
|
||||||
0,
|
0,
|
||||||
},
|
},
|
||||||
|
|
@ -296,7 +297,9 @@ func GetCompileTestData() map[string]CompileTestData {
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
&CallNode{
|
&CallNode{
|
||||||
"a",
|
&ReferenceNode{
|
||||||
|
"a",
|
||||||
|
},
|
||||||
[]Node{},
|
[]Node{},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
|
@ -321,6 +324,7 @@ func GetCompileTestData() map[string]CompileTestData {
|
||||||
NumberValue(1), StringValue("b"),
|
NumberValue(1), StringValue("b"),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
nil,
|
||||||
},
|
},
|
||||||
0,
|
0,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,8 @@ const (
|
||||||
|
|
||||||
TokenOpenParenthesis
|
TokenOpenParenthesis
|
||||||
TokenCloseParenthesis
|
TokenCloseParenthesis
|
||||||
|
TokenOpenBracket
|
||||||
|
TokenCloseBracket
|
||||||
TokenOpenBrace
|
TokenOpenBrace
|
||||||
TokenCloseBrace
|
TokenCloseBrace
|
||||||
|
|
||||||
|
|
@ -47,6 +49,7 @@ const (
|
||||||
TokenVar
|
TokenVar
|
||||||
TokenIf
|
TokenIf
|
||||||
TokenElse
|
TokenElse
|
||||||
|
TokenImport
|
||||||
|
|
||||||
TokenComma
|
TokenComma
|
||||||
TokenDot
|
TokenDot
|
||||||
|
|
@ -144,6 +147,10 @@ func (t TokenType) String() string {
|
||||||
return "double ampersand"
|
return "double ampersand"
|
||||||
case TokenDoublePipe:
|
case TokenDoublePipe:
|
||||||
return "double pipe"
|
return "double pipe"
|
||||||
|
case TokenOpenBracket:
|
||||||
|
return "open bracket"
|
||||||
|
case TokenCloseBracket:
|
||||||
|
return "close bracket"
|
||||||
}
|
}
|
||||||
|
|
||||||
return "UNDEFINED TOKENTYPE STRING CONVERSION"
|
return "UNDEFINED TOKENTYPE STRING CONVERSION"
|
||||||
|
|
@ -210,6 +217,10 @@ func (l *Lexer) NextToken() (Token, error) {
|
||||||
return l.makeToken(TokenOpenParenthesis), nil
|
return l.makeToken(TokenOpenParenthesis), nil
|
||||||
case ')':
|
case ')':
|
||||||
return l.makeToken(TokenCloseParenthesis), nil
|
return l.makeToken(TokenCloseParenthesis), nil
|
||||||
|
case '[':
|
||||||
|
return l.makeToken(TokenOpenBracket), nil
|
||||||
|
case ']':
|
||||||
|
return l.makeToken(TokenCloseBracket), nil
|
||||||
case '{':
|
case '{':
|
||||||
return l.makeToken(TokenOpenBrace), nil
|
return l.makeToken(TokenOpenBrace), nil
|
||||||
case '}':
|
case '}':
|
||||||
|
|
@ -309,6 +320,8 @@ func (l *Lexer) NextToken() (Token, error) {
|
||||||
return l.makeToken(TokenBreakpoint), nil
|
return l.makeToken(TokenBreakpoint), nil
|
||||||
case "return":
|
case "return":
|
||||||
return l.makeToken(TokenReturn), nil
|
return l.makeToken(TokenReturn), nil
|
||||||
|
case "import":
|
||||||
|
return l.makeToken(TokenImport), nil
|
||||||
default:
|
default:
|
||||||
return l.makeToken(TokenName), nil
|
return l.makeToken(TokenName), nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,12 @@ func GetLexerTestData() map[string]LexerTestData {
|
||||||
TokenOpenBrace, TokenReturn, TokenName, TokenPlus, TokenName, TokenCloseBrace,
|
TokenOpenBrace, TokenReturn, TokenName, TokenPlus, TokenName, TokenCloseBrace,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"list": {
|
||||||
|
"data := [3, 1, 4, 1]",
|
||||||
|
[]TokenType{
|
||||||
|
TokenName, TokenDeclare, TokenOpenBracket, TokenNumber, TokenComma, TokenNumber, TokenComma, TokenNumber, TokenComma, TokenNumber, TokenCloseBracket,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ const (
|
||||||
ReferenceNodeType
|
ReferenceNodeType
|
||||||
BooleanNodeType
|
BooleanNodeType
|
||||||
NilNodeType
|
NilNodeType
|
||||||
|
ListNodeType
|
||||||
BinaryNodeType
|
BinaryNodeType
|
||||||
BlockNodeType
|
BlockNodeType
|
||||||
ConditionalNodeType
|
ConditionalNodeType
|
||||||
|
|
@ -27,6 +28,8 @@ const (
|
||||||
CallNodeType
|
CallNodeType
|
||||||
FunctionNodeType
|
FunctionNodeType
|
||||||
ReturnNodeType
|
ReturnNodeType
|
||||||
|
AccessNodeType
|
||||||
|
ImportNodeType
|
||||||
BreakpointNodeType
|
BreakpointNodeType
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -58,6 +61,14 @@ func (n NodeType) String() string {
|
||||||
return "Function"
|
return "Function"
|
||||||
case ReturnNodeType:
|
case ReturnNodeType:
|
||||||
return "Return"
|
return "Return"
|
||||||
|
case ListNodeType:
|
||||||
|
return "List"
|
||||||
|
case AccessNodeType:
|
||||||
|
return "Access"
|
||||||
|
case BreakpointNodeType:
|
||||||
|
return "Breakpoint"
|
||||||
|
case ImportNodeType:
|
||||||
|
return "Import"
|
||||||
}
|
}
|
||||||
return "Invalid Node Type"
|
return "Invalid Node Type"
|
||||||
}
|
}
|
||||||
|
|
@ -101,6 +112,41 @@ func (n NumberNode) String() string {
|
||||||
return strconv.FormatFloat(float64(n.value), 'g', -1, NumberSize)
|
return strconv.FormatFloat(float64(n.value), 'g', -1, NumberSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListNode a list or sequence of values (items)
|
||||||
|
type ListNode struct {
|
||||||
|
items []Node
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n ListNode) Type() NodeType {
|
||||||
|
return ListNodeType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n ListNode) String() string {
|
||||||
|
sb := strings.Builder{}
|
||||||
|
sb.WriteString("[")
|
||||||
|
for i, item := range n.items {
|
||||||
|
sb.WriteString(item.String())
|
||||||
|
if i > 0 {
|
||||||
|
sb.WriteString(", ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.WriteString("]")
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccessNode struct {
|
||||||
|
source Node
|
||||||
|
property string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n AccessNode) Type() NodeType {
|
||||||
|
return AccessNodeType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n AccessNode) String() string {
|
||||||
|
return fmt.Sprintf("(%s from %s)", n.property, n.source)
|
||||||
|
}
|
||||||
|
|
||||||
type BinaryOperation uint
|
type BinaryOperation uint
|
||||||
|
|
||||||
func (n BinaryOperation) String() string {
|
func (n BinaryOperation) String() string {
|
||||||
|
|
@ -125,6 +171,10 @@ func (n BinaryOperation) String() string {
|
||||||
return "less or equal"
|
return "less or equal"
|
||||||
case BinaryGreaterEqual:
|
case BinaryGreaterEqual:
|
||||||
return "greater or equal"
|
return "greater or equal"
|
||||||
|
case BinaryAnd:
|
||||||
|
return "and"
|
||||||
|
case BinaryOr:
|
||||||
|
return "or"
|
||||||
}
|
}
|
||||||
|
|
||||||
return "undefined arithmetic operation"
|
return "undefined arithmetic operation"
|
||||||
|
|
@ -207,6 +257,18 @@ func (n BlockNode) String() string {
|
||||||
return builder.String()
|
return builder.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ImportNode struct {
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n ImportNode) Type() NodeType {
|
||||||
|
return ImportNodeType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n ImportNode) String() string {
|
||||||
|
return fmt.Sprintf("import %s", n.path)
|
||||||
|
}
|
||||||
|
|
||||||
// ConditionalNode conditionals (if statements)
|
// ConditionalNode conditionals (if statements)
|
||||||
type ConditionalNode struct {
|
type ConditionalNode struct {
|
||||||
condition Node
|
condition Node
|
||||||
|
|
@ -253,9 +315,9 @@ func (n AssignNode) String() string {
|
||||||
|
|
||||||
// function call
|
// function call
|
||||||
type CallNode struct {
|
type CallNode struct {
|
||||||
name string
|
source Node
|
||||||
args []Node
|
args []Node
|
||||||
keep bool
|
keep bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n CallNode) Type() NodeType {
|
func (n CallNode) Type() NodeType {
|
||||||
|
|
@ -263,7 +325,7 @@ func (n CallNode) Type() NodeType {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n CallNode) String() string {
|
func (n CallNode) String() string {
|
||||||
return fmt.Sprintf("call %s with args (%s)", n.name, n.args)
|
return fmt.Sprintf("call %s with args (%s)", n.source.String(), n.args)
|
||||||
}
|
}
|
||||||
|
|
||||||
// definition of function
|
// definition of function
|
||||||
|
|
|
||||||
120
core/parser.go
120
core/parser.go
|
|
@ -176,6 +176,30 @@ func (p *Parser) factor() (Node, error) {
|
||||||
p.advance()
|
p.advance()
|
||||||
return &NilNode{}, nil
|
return &NilNode{}, nil
|
||||||
|
|
||||||
|
case TokenOpenBracket:
|
||||||
|
p.advance()
|
||||||
|
|
||||||
|
var values []Node
|
||||||
|
for !p.accept(TokenCloseBracket) {
|
||||||
|
if len(values) > 0 {
|
||||||
|
if err := p.expect(TokenComma); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err := p.condition()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
values = append(values, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ListNode{
|
||||||
|
values,
|
||||||
|
}, nil
|
||||||
|
|
||||||
// unary minus
|
// unary minus
|
||||||
case TokenMinus:
|
case TokenMinus:
|
||||||
p.advance()
|
p.advance()
|
||||||
|
|
@ -200,7 +224,9 @@ func (p *Parser) factor() (Node, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return &CallNode{
|
return &CallNode{
|
||||||
name,
|
&ReferenceNode{
|
||||||
|
name,
|
||||||
|
},
|
||||||
args,
|
args,
|
||||||
true,
|
true,
|
||||||
}, nil
|
}, nil
|
||||||
|
|
@ -247,8 +273,44 @@ func (p *Parser) factor() (Node, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Parser) prop() (Node, error) {
|
||||||
|
v, err := p.factor()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse chains of prop-getting ( "".split().join().length.round() )
|
||||||
|
for p.accept(TokenDot) {
|
||||||
|
if err := p.expect(TokenName); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
property := (*p.prev).Lexeme
|
||||||
|
|
||||||
|
v = &AccessNode{
|
||||||
|
v,
|
||||||
|
property,
|
||||||
|
}
|
||||||
|
|
||||||
|
// if called, also add
|
||||||
|
if (*p.curr).Type == TokenOpenParenthesis {
|
||||||
|
args, err := p.parseArgs()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
v = &CallNode{
|
||||||
|
v,
|
||||||
|
args,
|
||||||
|
true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Parser) product() (Node, error) {
|
func (p *Parser) product() (Node, error) {
|
||||||
left, err := p.factor()
|
left, err := p.prop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -260,7 +322,7 @@ func (p *Parser) product() (Node, error) {
|
||||||
op = BinaryDivision
|
op = BinaryDivision
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := p.factor()
|
f, err := p.prop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -414,14 +476,49 @@ func (p *Parser) statement() (Node, error) {
|
||||||
p.advance()
|
p.advance()
|
||||||
name := (*p.prev).Lexeme
|
name := (*p.prev).Lexeme
|
||||||
|
|
||||||
if p.curr.Type == TokenOpenParenthesis {
|
if (*p.curr).Type == TokenDot {
|
||||||
|
var v Node = &ReferenceNode{
|
||||||
|
name,
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse chains of prop-getting ( "".split().join().length.round() )
|
||||||
|
for p.accept(TokenDot) {
|
||||||
|
if err := p.expect(TokenName); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
property := (*p.prev).Lexeme
|
||||||
|
|
||||||
|
v = &AccessNode{
|
||||||
|
v,
|
||||||
|
property,
|
||||||
|
}
|
||||||
|
|
||||||
|
// if called, also add
|
||||||
|
if (*p.curr).Type == TokenOpenParenthesis {
|
||||||
|
args, err := p.parseArgs()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
v = &CallNode{
|
||||||
|
v,
|
||||||
|
args,
|
||||||
|
(*p.curr).Type == TokenDot, // if the chain is continued, keep the value.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return v, nil
|
||||||
|
} else if p.curr.Type == TokenOpenParenthesis {
|
||||||
args, err := p.parseArgs()
|
args, err := p.parseArgs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &CallNode{
|
return &CallNode{
|
||||||
name,
|
&ReferenceNode{
|
||||||
|
name,
|
||||||
|
},
|
||||||
args,
|
args,
|
||||||
false,
|
false,
|
||||||
}, nil
|
}, nil
|
||||||
|
|
@ -441,6 +538,19 @@ func (p *Parser) statement() (Node, error) {
|
||||||
return p.condition()
|
return p.condition()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case TokenImport:
|
||||||
|
p.advance()
|
||||||
|
|
||||||
|
if err := p.expect(TokenString); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
path := p.prev.Lexeme[1 : len(p.prev.Lexeme)-1]
|
||||||
|
|
||||||
|
return &ImportNode{
|
||||||
|
path,
|
||||||
|
}, nil
|
||||||
|
|
||||||
case TokenFunc:
|
case TokenFunc:
|
||||||
p.advance()
|
p.advance()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -426,6 +426,91 @@ func GetTokenTestData() map[string]TokenTestData {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"prop_getting": {
|
||||||
|
[]Token{
|
||||||
|
NewToken(TokenName, 0, 1, 0, "p"),
|
||||||
|
NewToken(TokenDeclare, 1, 2, 0, ":="),
|
||||||
|
NewToken(TokenName, 3, 1, 0, "a"),
|
||||||
|
NewToken(TokenDot, 4, 1, 0, "."),
|
||||||
|
NewToken(TokenName, 5, 1, 0, "b"),
|
||||||
|
|
||||||
|
NewToken(TokenEOF, 23, 0, 2, ""),
|
||||||
|
},
|
||||||
|
&BlockNode{
|
||||||
|
[]Node{
|
||||||
|
&AssignNode{
|
||||||
|
"p",
|
||||||
|
&AccessNode{
|
||||||
|
&ReferenceNode{
|
||||||
|
"a",
|
||||||
|
},
|
||||||
|
"b",
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"list_init": {
|
||||||
|
[]Token{
|
||||||
|
NewToken(TokenName, 0, 4, 0, "data"),
|
||||||
|
NewToken(TokenDeclare, 4, 2, 0, ":="),
|
||||||
|
|
||||||
|
NewToken(TokenOpenBracket, 6, 1, 0, "["),
|
||||||
|
NewToken(TokenName, 8, 1, 0, "a"),
|
||||||
|
NewToken(TokenComma, 11, 1, 0, ","),
|
||||||
|
|
||||||
|
NewToken(TokenNumber, 8, 1, 0, "3.141"),
|
||||||
|
NewToken(TokenComma, 11, 1, 0, ","),
|
||||||
|
|
||||||
|
NewToken(TokenString, 6, 1, 0, "\"Hello world!\""),
|
||||||
|
NewToken(TokenComma, 11, 1, 0, ","),
|
||||||
|
|
||||||
|
NewToken(TokenTrue, 6, 1, 0, "true"),
|
||||||
|
NewToken(TokenComma, 11, 1, 0, ","),
|
||||||
|
|
||||||
|
NewToken(TokenOpenBracket, 6, 1, 0, "["),
|
||||||
|
NewToken(TokenNumber, 6, 1, 0, "2"),
|
||||||
|
NewToken(TokenComma, 11, 1, 0, ","),
|
||||||
|
|
||||||
|
NewToken(TokenNumber, 6, 1, 0, "3"),
|
||||||
|
NewToken(TokenCloseBracket, 6, 1, 0, "]"),
|
||||||
|
|
||||||
|
NewToken(TokenCloseBracket, 6, 1, 0, "]"),
|
||||||
|
|
||||||
|
NewToken(TokenEOF, 23, 0, 2, ""),
|
||||||
|
},
|
||||||
|
&BlockNode{
|
||||||
|
[]Node{
|
||||||
|
&AssignNode{
|
||||||
|
"data",
|
||||||
|
&ListNode{
|
||||||
|
[]Node{
|
||||||
|
&ReferenceNode{
|
||||||
|
"a",
|
||||||
|
},
|
||||||
|
&NumberNode{
|
||||||
|
3.141,
|
||||||
|
},
|
||||||
|
&StringNode{
|
||||||
|
"Hello world!",
|
||||||
|
"\"Hello world!\"",
|
||||||
|
},
|
||||||
|
&BooleanNode{
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
&ListNode{
|
||||||
|
[]Node{
|
||||||
|
&NumberNode{2}, &NumberNode{3},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -533,11 +618,7 @@ func NodeEquality(t *testing.T, n1 Node, n2 Node) {
|
||||||
n := n1.(*CallNode)
|
n := n1.(*CallNode)
|
||||||
m := n2.(*CallNode)
|
m := n2.(*CallNode)
|
||||||
|
|
||||||
if n.name != m.name {
|
NodeEquality(t, n.source, m.source)
|
||||||
t.Errorf("Call node names don't match (%s and %s)", n.name, m.name)
|
|
||||||
} else {
|
|
||||||
t.Logf("Call node names match (%s)", n.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if n.keep == m.keep {
|
if n.keep == m.keep {
|
||||||
t.Logf("Call node keep modifier doesn't match (%v and %v)", n.keep, m.keep)
|
t.Logf("Call node keep modifier doesn't match (%v and %v)", n.keep, m.keep)
|
||||||
|
|
|
||||||
359
core/values.go
359
core/values.go
|
|
@ -1,8 +1,12 @@
|
||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ValueType int
|
type ValueType int
|
||||||
|
|
@ -12,6 +16,8 @@ const (
|
||||||
BoolValueType
|
BoolValueType
|
||||||
NumberValueType
|
NumberValueType
|
||||||
StringValueType
|
StringValueType
|
||||||
|
ListValueType
|
||||||
|
ObjectValueType
|
||||||
FunctionValueType
|
FunctionValueType
|
||||||
BuiltinFunctionValueType
|
BuiltinFunctionValueType
|
||||||
VariableValueType
|
VariableValueType
|
||||||
|
|
@ -23,10 +29,14 @@ func (v ValueType) String() string {
|
||||||
return "nil"
|
return "nil"
|
||||||
case BoolValueType:
|
case BoolValueType:
|
||||||
return "bool"
|
return "bool"
|
||||||
|
case ObjectValueType:
|
||||||
|
return "object"
|
||||||
case NumberValueType:
|
case NumberValueType:
|
||||||
return "number"
|
return "number"
|
||||||
case StringValueType:
|
case StringValueType:
|
||||||
return "string"
|
return "string"
|
||||||
|
case ListValueType:
|
||||||
|
return "list"
|
||||||
case FunctionValueType:
|
case FunctionValueType:
|
||||||
return "function"
|
return "function"
|
||||||
case BuiltinFunctionValueType:
|
case BuiltinFunctionValueType:
|
||||||
|
|
@ -38,30 +48,55 @@ func (v ValueType) String() string {
|
||||||
return "undefined"
|
return "undefined"
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetType(v string) ValueType {
|
// GoToValue convert go values to anglais VM-values. Works for some values (nil, bool, float64, string, slices, maps)
|
||||||
switch v {
|
func GoToValue(gov interface{}) Value {
|
||||||
case "nil":
|
switch v := gov.(type) {
|
||||||
return NilValueType
|
case nil:
|
||||||
case "bool":
|
return NilValue{}
|
||||||
return BoolValueType
|
case bool:
|
||||||
case "number":
|
return BoolValue(v)
|
||||||
return NumberValueType
|
case float64:
|
||||||
case "string":
|
return NumberValue(v)
|
||||||
return StringValueType
|
case string:
|
||||||
case "function":
|
return StringValue(v)
|
||||||
return FunctionValueType
|
case []interface{}:
|
||||||
case "builtin":
|
values := make([]Value, len(v))
|
||||||
return BuiltinFunctionValueType
|
for i, value := range v {
|
||||||
case "variable":
|
values[i] = GoToValue(value)
|
||||||
return VariableValueType
|
}
|
||||||
|
|
||||||
|
return ListValue{
|
||||||
|
values,
|
||||||
|
}
|
||||||
|
case map[string]interface{}:
|
||||||
|
values := map[string]Value{}
|
||||||
|
for key, value := range v {
|
||||||
|
values[key] = GoToValue(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ObjectValue{
|
||||||
|
values,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0
|
panic(fmt.Sprintf("unsupported automatic type conversion: %v (%s)", gov, reflect.TypeOf(gov).Name()))
|
||||||
}
|
}
|
||||||
|
|
||||||
type Value interface {
|
type Value interface {
|
||||||
|
// Type get the type of the value (a ValueType)
|
||||||
Type() ValueType
|
Type() ValueType
|
||||||
|
|
||||||
|
// String Convert this value to a string fit for human consumption
|
||||||
String() string
|
String() string
|
||||||
|
|
||||||
|
// DebugString get a debug string of this value. Used in lists.
|
||||||
|
DebugString() string
|
||||||
|
|
||||||
|
// Equals Check if two values are equal
|
||||||
|
Equals(Value) bool
|
||||||
|
|
||||||
|
// Get a member from the value. An error is returned if the member does not exist
|
||||||
|
Get(string) (Value, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type NilValue struct{}
|
type NilValue struct{}
|
||||||
|
|
@ -74,6 +109,18 @@ func (v NilValue) String() string {
|
||||||
return "nil"
|
return "nil"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v NilValue) DebugString() string {
|
||||||
|
return v.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v NilValue) Equals(other Value) bool {
|
||||||
|
return other.Type() == NilValueType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v NilValue) Get(key string) (Value, error) {
|
||||||
|
return nil, errors.New("nil has no properties")
|
||||||
|
}
|
||||||
|
|
||||||
type BoolValue bool
|
type BoolValue bool
|
||||||
|
|
||||||
func (v BoolValue) Type() ValueType {
|
func (v BoolValue) Type() ValueType {
|
||||||
|
|
@ -88,6 +135,58 @@ func (v BoolValue) String() string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v BoolValue) DebugString() string {
|
||||||
|
return v.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v BoolValue) Equals(other Value) bool {
|
||||||
|
return other.Type() == BoolValueType && bool(other.(BoolValue)) == bool(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v BoolValue) Get(key string) (Value, error) {
|
||||||
|
return nil, errors.New("booleans have no properties")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObjectValue An object with any number of members (key-value pairs)
|
||||||
|
type ObjectValue struct {
|
||||||
|
members map[string]Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v ObjectValue) Type() ValueType {
|
||||||
|
return ObjectValueType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v ObjectValue) String() string {
|
||||||
|
out := "{"
|
||||||
|
for key, value := range v.members {
|
||||||
|
if out != "{" {
|
||||||
|
out += ", "
|
||||||
|
}
|
||||||
|
|
||||||
|
out += fmt.Sprintf("%q=%s", key, value.String())
|
||||||
|
}
|
||||||
|
out += "}"
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v ObjectValue) DebugString() string {
|
||||||
|
return v.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v ObjectValue) Equals(other Value) bool {
|
||||||
|
// TODO implement object equality check
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v ObjectValue) Get(key string) (Value, error) {
|
||||||
|
if member := v.members[key]; member == nil {
|
||||||
|
return nil, errors.New("no property found with name \"" + key + "\"")
|
||||||
|
} else {
|
||||||
|
return member, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NumberValue Integer or floating-point values
|
// NumberValue Integer or floating-point values
|
||||||
type NumberValue float64
|
type NumberValue float64
|
||||||
|
|
||||||
|
|
@ -101,6 +200,19 @@ func (v NumberValue) String() string {
|
||||||
return strconv.FormatFloat(float64(v), 'g', -1, NumberSize)
|
return strconv.FormatFloat(float64(v), 'g', -1, NumberSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v NumberValue) DebugString() string {
|
||||||
|
return v.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v NumberValue) Equals(other Value) bool {
|
||||||
|
return other.Type() == NumberValueType && float64(other.(NumberValue)) == float64(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v NumberValue) Get(key string) (Value, error) {
|
||||||
|
// TODO maybe add standard functions for number values?
|
||||||
|
return nil, errors.New("numbers have no properties")
|
||||||
|
}
|
||||||
|
|
||||||
type StringValue string
|
type StringValue string
|
||||||
|
|
||||||
func (v StringValue) Type() ValueType {
|
func (v StringValue) Type() ValueType {
|
||||||
|
|
@ -111,10 +223,179 @@ func (v StringValue) String() string {
|
||||||
return string(v)
|
return string(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v StringValue) DebugString() string {
|
||||||
|
return "\"" + v.String() + "\""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v StringValue) Equals(other Value) bool {
|
||||||
|
return other.Type() == StringValueType && string(other.(StringValue)) == string(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
var StringPrototype = map[string]BuiltinFunctionValue{
|
||||||
|
"split": {
|
||||||
|
"split",
|
||||||
|
[]string{"seperator"},
|
||||||
|
func(vm *VM, this Value, m map[string]Value) (Value, error) {
|
||||||
|
str := this.(StringValue).String()
|
||||||
|
sep := m["seperator"].(StringValue).String()
|
||||||
|
|
||||||
|
var out []string
|
||||||
|
tmp := strings.Builder{}
|
||||||
|
for i := 0; i < len(str)-len(sep); i++ {
|
||||||
|
tmp.WriteRune([]rune(str)[i])
|
||||||
|
|
||||||
|
if str[i:i+len(sep)] == sep {
|
||||||
|
out = append(out, tmp.String())
|
||||||
|
tmp.Reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return GoToValue(out), nil
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v StringValue) Get(key string) (Value, error) {
|
||||||
|
if prop, ok := StringPrototype[key]; ok {
|
||||||
|
return prop, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New(fmt.Sprintf("string has no property \"%s\"", key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListValue a dynamic list of values
|
||||||
|
type ListValue struct {
|
||||||
|
items []Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v ListValue) Type() ValueType {
|
||||||
|
return ListValueType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v ListValue) String() string {
|
||||||
|
out := "["
|
||||||
|
for i, item := range v.items {
|
||||||
|
if i != 0 {
|
||||||
|
out += ", "
|
||||||
|
}
|
||||||
|
out += item.DebugString()
|
||||||
|
}
|
||||||
|
out += "]"
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v ListValue) DebugString() string {
|
||||||
|
return v.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v ListValue) Equals(other Value) bool {
|
||||||
|
return other.Type() == ListValueType &&
|
||||||
|
slices.Equal(v.items, other.(ListValue).items)
|
||||||
|
}
|
||||||
|
|
||||||
|
var ListPrototype = map[string]BuiltinFunctionValue{
|
||||||
|
"append": {
|
||||||
|
"append",
|
||||||
|
[]string{"item"},
|
||||||
|
func(_ *VM, this Value, p map[string]Value) (Value, error) {
|
||||||
|
return ListValue{
|
||||||
|
append(this.(ListValue).items, p["item"]),
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
"at": {
|
||||||
|
"at",
|
||||||
|
[]string{"index"},
|
||||||
|
func(_ *VM, this Value, p map[string]Value) (Value, error) {
|
||||||
|
index := int(p["index"].(NumberValue))
|
||||||
|
items := this.(ListValue).items
|
||||||
|
|
||||||
|
if index >= len(items) {
|
||||||
|
return nil, errors.New(fmt.Sprintf("list index %x out of range", index))
|
||||||
|
}
|
||||||
|
|
||||||
|
return items[index], nil
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
"length": {
|
||||||
|
"length",
|
||||||
|
[]string{},
|
||||||
|
func(_ *VM, this Value, p map[string]Value) (Value, error) {
|
||||||
|
return NumberValue(len(this.(ListValue).items)), nil
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
"map": {
|
||||||
|
"map",
|
||||||
|
[]string{"f"},
|
||||||
|
func(vm *VM, value Value, m map[string]Value) (Value, error) {
|
||||||
|
list := value.(ListValue)
|
||||||
|
|
||||||
|
v := m["f"]
|
||||||
|
var f Value
|
||||||
|
f, ok := v.(FunctionValue)
|
||||||
|
if !ok {
|
||||||
|
f, ok = v.(BuiltinFunctionValue)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New(fmt.Sprintf("not a function to apply: %s", v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, item := range list.items {
|
||||||
|
var err error
|
||||||
|
list.items[i], err = vm.Call(f, []Value{
|
||||||
|
item,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list, nil
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
"reduce": {
|
||||||
|
"reduce",
|
||||||
|
[]string{"f", "start"},
|
||||||
|
func(vm *VM, value Value, m map[string]Value) (Value, error) {
|
||||||
|
list := value.(ListValue)
|
||||||
|
f := m["f"]
|
||||||
|
sum := m["start"]
|
||||||
|
|
||||||
|
for _, v := range list.items {
|
||||||
|
result, err := vm.Call(f, []Value{sum, v})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sum = result
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum, nil
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v ListValue) Get(key string) (Value, error) {
|
||||||
|
if prop, ok := ListPrototype[key]; ok {
|
||||||
|
return prop, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New(fmt.Sprintf("list has no property \"%s\"", key))
|
||||||
|
}
|
||||||
|
|
||||||
type FunctionValue struct {
|
type FunctionValue struct {
|
||||||
Name string
|
Name string
|
||||||
Params []string
|
Params []string
|
||||||
Chunk *Chunk
|
Chunk *Chunk
|
||||||
|
Parent Value
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v FunctionValue) Type() ValueType {
|
func (v FunctionValue) Type() ValueType {
|
||||||
|
|
@ -125,10 +406,25 @@ func (v FunctionValue) String() string {
|
||||||
return fmt.Sprintf("<function name=%s>", v.Name)
|
return fmt.Sprintf("<function name=%s>", v.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v FunctionValue) DebugString() string {
|
||||||
|
return v.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v FunctionValue) Equals(other Value) bool {
|
||||||
|
return other.Type() == FunctionValueType &&
|
||||||
|
v.Name == other.(FunctionValue).Name &&
|
||||||
|
v.Chunk == other.(FunctionValue).Chunk
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v FunctionValue) Get(_ string) (Value, error) {
|
||||||
|
return nil, errors.New("functions have no properties")
|
||||||
|
}
|
||||||
|
|
||||||
type BuiltinFunctionValue struct {
|
type BuiltinFunctionValue struct {
|
||||||
Name string
|
Name string
|
||||||
Parameters []string
|
Parameters []string
|
||||||
F func(map[string]Value) Value
|
F func(*VM, Value, map[string]Value) (Value, error)
|
||||||
|
Parent Value
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v BuiltinFunctionValue) Type() ValueType {
|
func (v BuiltinFunctionValue) Type() ValueType {
|
||||||
|
|
@ -139,6 +435,19 @@ func (v BuiltinFunctionValue) String() string {
|
||||||
return fmt.Sprintf("<function name=%s builtin>", v.Name)
|
return fmt.Sprintf("<function name=%s builtin>", v.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v BuiltinFunctionValue) DebugString() string {
|
||||||
|
return v.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v BuiltinFunctionValue) Equals(other Value) bool {
|
||||||
|
return other.Type() == BuiltinFunctionValueType &&
|
||||||
|
v.Name == other.(BuiltinFunctionValue).Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v BuiltinFunctionValue) Get(_ string) (Value, error) {
|
||||||
|
return nil, errors.New("functions have no properties")
|
||||||
|
}
|
||||||
|
|
||||||
// VariableValue a value wrapper for variables kept on the stack
|
// VariableValue a value wrapper for variables kept on the stack
|
||||||
type VariableValue struct {
|
type VariableValue struct {
|
||||||
name string
|
name string
|
||||||
|
|
@ -157,6 +466,16 @@ func (v VariableValue) String() string {
|
||||||
//panic("tried getting string value of a unreachable value")
|
//panic("tried getting string value of a unreachable value")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v VariableValue) equals(other VariableValue) bool {
|
func (v VariableValue) DebugString() string {
|
||||||
return v.name == other.name && v.value == other.value
|
return v.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v VariableValue) Equals(other Value) bool {
|
||||||
|
return other.Type() == VariableValueType &&
|
||||||
|
v.name == other.(VariableValue).name &&
|
||||||
|
v.value.Equals(other.(VariableValue).value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v VariableValue) Get(_ string) (Value, error) {
|
||||||
|
return nil, errors.New("variables have no properties")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
149
core/vm.go
149
core/vm.go
|
|
@ -41,6 +41,8 @@ const (
|
||||||
// InstructionGreaterOrEqual pops two from stack, pushes whether the lowest is greater or equal than the highest
|
// InstructionGreaterOrEqual pops two from stack, pushes whether the lowest is greater or equal than the highest
|
||||||
InstructionGreaterOrEqual
|
InstructionGreaterOrEqual
|
||||||
|
|
||||||
|
// InstructionAccessProperty gets a property from a value, and pops it onto the stack
|
||||||
|
InstructionAccessProperty
|
||||||
// InstructionCall pops a function object from the stack and begins execution of the chunk
|
// InstructionCall pops a function object from the stack and begins execution of the chunk
|
||||||
InstructionCall
|
InstructionCall
|
||||||
|
|
||||||
|
|
@ -89,6 +91,14 @@ const (
|
||||||
// InstructionNil Push a nil literal to the stack
|
// InstructionNil Push a nil literal to the stack
|
||||||
InstructionNil
|
InstructionNil
|
||||||
|
|
||||||
|
// InstructionNewList Push a new (empty) list to the stack
|
||||||
|
InstructionNewList
|
||||||
|
// InstructionAppend Append to a list. stack: (... > list > item) => (... > list)
|
||||||
|
InstructionAppend
|
||||||
|
// InstructionFormList Form items on the stack into a list. The 2 bytes after the instructions are the amount of
|
||||||
|
// items to include minus one. (value of 0 => 1 item, value of 1 => 2 items, etc.)
|
||||||
|
InstructionFormList
|
||||||
|
|
||||||
// InstructionBreakpoint for debugging purposes
|
// InstructionBreakpoint for debugging purposes
|
||||||
InstructionBreakpoint
|
InstructionBreakpoint
|
||||||
)
|
)
|
||||||
|
|
@ -161,8 +171,16 @@ func (b Bytecode) String() string {
|
||||||
return "AND"
|
return "AND"
|
||||||
case InstructionOr:
|
case InstructionOr:
|
||||||
return "OR"
|
return "OR"
|
||||||
|
case InstructionFormList:
|
||||||
|
return "FORM_LIST"
|
||||||
case InstructionBreakpoint:
|
case InstructionBreakpoint:
|
||||||
return "BREAKPOINT"
|
return "BREAKPOINT"
|
||||||
|
case InstructionNewList:
|
||||||
|
return "NEW_LIST"
|
||||||
|
case InstructionAppend:
|
||||||
|
return "APPEND"
|
||||||
|
case InstructionAccessProperty:
|
||||||
|
return "ACCESS_PROPERTY"
|
||||||
}
|
}
|
||||||
return "UNDEFINED"
|
return "UNDEFINED"
|
||||||
}
|
}
|
||||||
|
|
@ -268,18 +286,32 @@ var DefaultGlobals = map[string]Value{
|
||||||
"write": BuiltinFunctionValue{
|
"write": BuiltinFunctionValue{
|
||||||
"write", // always remember where you come from...
|
"write", // always remember where you come from...
|
||||||
[]string{"value"},
|
[]string{"value"},
|
||||||
func(v map[string]Value) Value {
|
func(_ *VM, this Value, v map[string]Value) (Value, error) {
|
||||||
println(v["value"].String())
|
println(v["value"].String())
|
||||||
return nil
|
return nil, nil
|
||||||
},
|
},
|
||||||
|
nil,
|
||||||
},
|
},
|
||||||
"print": BuiltinFunctionValue{
|
"print": BuiltinFunctionValue{
|
||||||
"print",
|
"print",
|
||||||
[]string{"value"},
|
[]string{"value"},
|
||||||
func(v map[string]Value) Value {
|
func(_ *VM, this Value, v map[string]Value) (Value, error) {
|
||||||
print(v["value"].String())
|
print(v["value"].String())
|
||||||
return nil
|
return nil, nil
|
||||||
},
|
},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
"assert": BuiltinFunctionValue{
|
||||||
|
"assert",
|
||||||
|
[]string{"condition"},
|
||||||
|
func(vm *VM, this Value, params map[string]Value) (Value, error) {
|
||||||
|
if !params["condition"].(BoolValue) {
|
||||||
|
return nil, errors.New("assertion failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
return NilValue{}, nil
|
||||||
|
},
|
||||||
|
nil,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -300,7 +332,7 @@ func NewVM(chunk *Chunk, stackSize Pos, callstackSize Pos) *VM {
|
||||||
func (vm *VM) Next() bool {
|
func (vm *VM) Next() bool {
|
||||||
switch vm.NextByte() {
|
switch vm.NextByte() {
|
||||||
case InstructionReturn:
|
case InstructionReturn:
|
||||||
if vm.call.Current <= 0 {
|
if vm.call.Current == 0 {
|
||||||
return false
|
return false
|
||||||
} else {
|
} else {
|
||||||
v := vm.stack.Pop()
|
v := vm.stack.Pop()
|
||||||
|
|
@ -351,16 +383,14 @@ func (vm *VM) Next() bool {
|
||||||
vm.stack.Push(l / r)
|
vm.stack.Push(l / r)
|
||||||
|
|
||||||
case InstructionEquals:
|
case InstructionEquals:
|
||||||
r := vm.stack.Pop().(NumberValue)
|
vm.stack.Push(
|
||||||
l := vm.stack.Pop().(NumberValue)
|
BoolValue(vm.stack.Pop().Equals(vm.stack.Pop())),
|
||||||
|
)
|
||||||
vm.stack.Push(BoolValue(l == r))
|
|
||||||
|
|
||||||
case InstructionNotEqual:
|
case InstructionNotEqual:
|
||||||
r := vm.stack.Pop().(NumberValue)
|
vm.stack.Push(
|
||||||
l := vm.stack.Pop().(NumberValue)
|
BoolValue(!vm.stack.Pop().Equals(vm.stack.Pop())),
|
||||||
|
)
|
||||||
vm.stack.Push(BoolValue(l != r))
|
|
||||||
|
|
||||||
case InstructionNot:
|
case InstructionNot:
|
||||||
b := vm.stack.Pop().(BoolValue)
|
b := vm.stack.Pop().(BoolValue)
|
||||||
|
|
@ -412,7 +442,6 @@ func (vm *VM) Next() bool {
|
||||||
scope: vm.scope,
|
scope: vm.scope,
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO Fix the variables sometimes being wrongly assigned
|
|
||||||
for i := len(f.Params) - 1; i >= 0; i-- {
|
for i := len(f.Params) - 1; i >= 0; i-- {
|
||||||
p := vm.stack.Current - Pos(len(f.Params)) + Pos(i)
|
p := vm.stack.Current - Pos(len(f.Params)) + Pos(i)
|
||||||
vm.stack.items[p] = &VariableValue{
|
vm.stack.items[p] = &VariableValue{
|
||||||
|
|
@ -422,6 +451,10 @@ func (vm *VM) Next() bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if f.Parent != nil {
|
||||||
|
vm.addVar("this", f.Parent)
|
||||||
|
}
|
||||||
|
|
||||||
vm.variableEnd = vm.stack.Current
|
vm.variableEnd = vm.stack.Current
|
||||||
|
|
||||||
vm.chunk = f.Chunk
|
vm.chunk = f.Chunk
|
||||||
|
|
@ -433,9 +466,14 @@ func (vm *VM) Next() bool {
|
||||||
args[f.Parameters[i]] = vm.stack.Pop()
|
args[f.Parameters[i]] = vm.stack.Pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
vm.stack.Push(f.F(args))
|
v, err := f.F(vm, f.Parent, args)
|
||||||
|
if err != nil {
|
||||||
|
vm.error(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
vm.stack.Push(v)
|
||||||
default:
|
default:
|
||||||
vm.error(fmt.Sprintf("value called is not a function (%s)", v.String()))
|
vm.error(fmt.Sprintf("value called is not a function (%s)", v.DebugString()))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -495,6 +533,17 @@ func (vm *VM) Next() bool {
|
||||||
case InstructionNil:
|
case InstructionNil:
|
||||||
vm.stack.Push(NilValue{})
|
vm.stack.Push(NilValue{})
|
||||||
|
|
||||||
|
case InstructionFormList:
|
||||||
|
|
||||||
|
case InstructionNewList:
|
||||||
|
vm.stack.Push(ListValue{[]Value{}})
|
||||||
|
|
||||||
|
case InstructionAppend:
|
||||||
|
value := vm.stack.Pop()
|
||||||
|
list := vm.stack.Pop().(ListValue)
|
||||||
|
list.items = append(list.items, value)
|
||||||
|
vm.stack.Push(list)
|
||||||
|
|
||||||
case InstructionDescend:
|
case InstructionDescend:
|
||||||
vm.descend()
|
vm.descend()
|
||||||
|
|
||||||
|
|
@ -517,6 +566,28 @@ func (vm *VM) Next() bool {
|
||||||
|
|
||||||
vm.stack.Push(r, l)
|
vm.stack.Push(r, l)
|
||||||
|
|
||||||
|
case InstructionAccessProperty:
|
||||||
|
source := vm.stack.Pop()
|
||||||
|
property := vm.ReadConstant()
|
||||||
|
|
||||||
|
member, err := source.Get(property.(StringValue).String())
|
||||||
|
if err != nil {
|
||||||
|
vm.error(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// add parent if function with a little switcheroo
|
||||||
|
if member.Type() == FunctionValueType {
|
||||||
|
f := member.(FunctionValue)
|
||||||
|
f.Parent = source
|
||||||
|
member = f
|
||||||
|
} else if member.Type() == BuiltinFunctionValueType {
|
||||||
|
f := member.(BuiltinFunctionValue)
|
||||||
|
f.Parent = source
|
||||||
|
member = f
|
||||||
|
}
|
||||||
|
|
||||||
|
vm.stack.Push(member)
|
||||||
|
|
||||||
case InstructionBreakpoint:
|
case InstructionBreakpoint:
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
@ -526,6 +597,52 @@ func (vm *VM) Next() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (vm *VM) Call(v Value, args []Value) (Value, error) {
|
||||||
|
switch f := v.(type) {
|
||||||
|
case FunctionValue:
|
||||||
|
vm.call.Push(Call{
|
||||||
|
chunk: vm.chunk,
|
||||||
|
ip: vm.ip,
|
||||||
|
stackEnd: vm.stack.Current,
|
||||||
|
variableEnd: vm.variableEnd,
|
||||||
|
scope: vm.scope,
|
||||||
|
})
|
||||||
|
|
||||||
|
for i := 0; i < len(f.Params); i++ {
|
||||||
|
vm.addVar(f.Params[i], args[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.Parent != nil {
|
||||||
|
vm.addVar("this", f.Parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
vm.variableEnd = vm.stack.Current
|
||||||
|
|
||||||
|
vm.chunk = f.Chunk
|
||||||
|
vm.ip = 0
|
||||||
|
|
||||||
|
for vm.chunk.Bytecode[vm.ip] != InstructionReturn && vm.Next() {
|
||||||
|
}
|
||||||
|
|
||||||
|
if vm.HasNext() {
|
||||||
|
vm.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
return vm.stack.Pop(), nil
|
||||||
|
|
||||||
|
case BuiltinFunctionValue:
|
||||||
|
argies := map[string]Value{}
|
||||||
|
|
||||||
|
for i, arg := range args {
|
||||||
|
argies[f.Parameters[i]] = arg
|
||||||
|
}
|
||||||
|
|
||||||
|
return f.F(vm, f.Parent, argies)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New(fmt.Sprintf("value is not a function (%s)", v.DebugString()))
|
||||||
|
}
|
||||||
|
|
||||||
func (vm *VM) TryNextByte() (Bytecode, error) {
|
func (vm *VM) TryNextByte() (Bytecode, error) {
|
||||||
if !vm.HasNext() {
|
if !vm.HasNext() {
|
||||||
return 0, errors.New("there are no more instructions")
|
return 0, errors.New("there are no more instructions")
|
||||||
|
|
|
||||||
52
wasm/wasm.go
52
wasm/wasm.go
|
|
@ -3,11 +3,44 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"log"
|
"log"
|
||||||
"neemek.com/anglais/core"
|
"neemek.com/anglais/core"
|
||||||
"syscall/js"
|
"syscall/js"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type JsResolver struct {
|
||||||
|
jsResolver js.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *JsResolver) Resolve(name string) (core.Node, error) {
|
||||||
|
jsv := r.jsResolver.Invoke(name)
|
||||||
|
|
||||||
|
if jsv.Type() == js.TypeUndefined {
|
||||||
|
return nil, errors.New("cannot find import with name " + name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if jsv.Type() != js.TypeString {
|
||||||
|
return nil, errors.New("invalid value for source: " + jsv.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
source := jsv.String()
|
||||||
|
|
||||||
|
l := core.NewLexer(source)
|
||||||
|
tokens, err := l.Tokenize()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
p := core.NewParser(tokens)
|
||||||
|
tree, err := p.Parse()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tree, nil
|
||||||
|
}
|
||||||
|
|
||||||
func jsError(err error) interface{} {
|
func jsError(err error) interface{} {
|
||||||
return jsErrorOfString(err.Error())
|
return jsErrorOfString(err.Error())
|
||||||
}
|
}
|
||||||
|
|
@ -22,6 +55,7 @@ func jsErrorOfString(err string) interface{} {
|
||||||
func run(this js.Value, args []js.Value) interface{} {
|
func run(this js.Value, args []js.Value) interface{} {
|
||||||
source := args[0].String()
|
source := args[0].String()
|
||||||
outputHandler := args[1]
|
outputHandler := args[1]
|
||||||
|
resolver := args[2]
|
||||||
log.Printf("got source: %s", source)
|
log.Printf("got source: %s", source)
|
||||||
|
|
||||||
lexer := core.NewLexer(source)
|
lexer := core.NewLexer(source)
|
||||||
|
|
@ -45,6 +79,16 @@ func run(this js.Value, args []js.Value) interface{} {
|
||||||
|
|
||||||
compiler := core.NewCompiler()
|
compiler := core.NewCompiler()
|
||||||
|
|
||||||
|
compiler.SetImportsResolver(&JsResolver{
|
||||||
|
resolver,
|
||||||
|
})
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
log.Printf("panic recovered: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
compiler.Compile(tree)
|
compiler.Compile(tree)
|
||||||
|
|
||||||
log.Printf("Compiled tree (into %i instructions)", len(compiler.Chunk.Bytecode))
|
log.Printf("Compiled tree (into %i instructions)", len(compiler.Chunk.Bytecode))
|
||||||
|
|
@ -55,19 +99,19 @@ func run(this js.Value, args []js.Value) interface{} {
|
||||||
vm.SetGlobal("write", core.BuiltinFunctionValue{
|
vm.SetGlobal("write", core.BuiltinFunctionValue{
|
||||||
Name: "write",
|
Name: "write",
|
||||||
Parameters: []string{"value"},
|
Parameters: []string{"value"},
|
||||||
F: func(v map[string]core.Value) core.Value {
|
F: func(vm *core.VM, this core.Value, v map[string]core.Value) (core.Value, error) {
|
||||||
log.Printf("Writing value: %s", v["value"].String())
|
log.Printf("Writing value: %s", v["value"].String())
|
||||||
outputHandler.Invoke(js.ValueOf(v["value"].String() + "\n"))
|
outputHandler.Invoke(js.ValueOf(v["value"].String() + "\n"))
|
||||||
return nil
|
return nil, nil
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
vm.SetGlobal("print", core.BuiltinFunctionValue{
|
vm.SetGlobal("print", core.BuiltinFunctionValue{
|
||||||
Name: "print",
|
Name: "print",
|
||||||
Parameters: []string{"value"},
|
Parameters: []string{"value"},
|
||||||
F: func(v map[string]core.Value) core.Value {
|
F: func(vm *core.VM, this core.Value, v map[string]core.Value) (core.Value, error) {
|
||||||
log.Printf("Printing value: %s", v["value"].String())
|
log.Printf("Printing value: %s", v["value"].String())
|
||||||
outputHandler.Invoke(js.ValueOf(v["value"].String()))
|
outputHandler.Invoke(js.ValueOf(v["value"].String()))
|
||||||
return nil
|
return nil, nil
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue