ergolas

package module
v0.0.0-...-5a592db Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 21, 2023 License: AGPL-3.0 Imports: 7 Imported by: 0

README

Ergolas (WIP)

This is just an embeddable random golang language for scripting. Recently I use Golang very often and sometimes I want to add some extensibility features to my projects using some kind of scripting languages or DSL so I made this mini language for experimenting. There is an included tree walking interpreter but I plan to make it really easy to just parse an expression and get an AST to evaluate with a custom interpreter.

The syntax is very simple and pretty general and inherits many things from Lisp, REBOL/Red and Julia.

println "Hello, World!"

Features

  • Parser
    • Function calls without parentheses
    • Property access
    • Quoted forms
    • Binary operators
    • Quasi-quotes with : for quoting and $ for unquoting (might change : to # and comments to //)
    • Unary operators
    • String templating (for now missing, maybe something can be done just using quasiquotes)
  • Interpreter
    • Simple tree walking interpreter
      • Basic operators and arithmetic
      • Basic printing and exiting
      • Basic variable assignment
      • Lexical scoping
      • Control flow
      • Objects and complex values
      • Dynamic scoping
      • Hygienic macros
    • More advanced interpreters...
  • Easily usable as a library
  • Small standard library
  • Interop from and with Go
  • Tooling
    • Syntax highlighting for common editors
    • PKGBUILD for easy global installation on Arch Linux thorough GitHub releases (mostly for trying this out with GitHub Actions)

Usage

To try this out in a REPL (with colors!)

$ go run ./cmd/repl

Reference

Literals
# Integer
1

# Decimal
3.14

# Identifier
an-Example_identifier

# String
"an example string"

# List (?) (not implemented)
[1 2 3 4 5] # equivalent to "List 1 2 3 4 5"

# Maps (?) (not implemented)
{ a -> 1, b -> 2, c -> 3 }
Comments

# This is an inline comment

Functions

Function call don't require parentheses if they

# [x] Parses ok, [x] Evals ok
println "a" "b" c" 
# [x] Parses ok, [x] Evals ok
exit 1
Anonymous Functions
# [x] Parses ok, [ ] Evals ok

# anonymous function with params
my-func := fn x y { x + y }
# [ ] Parses ok, [ ] Evals ok

# anonymous lexical block without params, can be called with a context
my-block := { x + y }
ctx := Map [ x -> 1, y -> 2 ]
call my-block ctx
Operators

The following binds "a" to 9, arithmetic operators don't have any precedence and are all left associative. There are a only a few right associative operators that for now just are :=, :: even if only := is used for binding variables, :: will later be used to tell the type of variables.

# [x] Parses ok, [x] Evals ok
a := 1 + 2 * 3
Overloading
# [x] Parses ok, [ ] Evals ok
operator lhs ++ rhs {
    return List.join lhs rhs
}
Quotes
# [x] Parses ok, [ ] Evals ok
a := (1 + 1) # 2
b := :(1 + 1) # :(1 + 1)
Misc

Some more examples and ideas for the language syntax and semantics

# [x] Parses ok, [ ] Evals ok
len := (v.x ^ 2) + (v.y ^ 2)

# [x] Parses ok, [ ] Evals ok
if { a > b } {
    println "True case"
} {
    println "False case" 
}

# [x] Parses ok, [ ] Evals ok
my-list-1 := list 1 2 3 4 5

# [x] Parses ok, [ ] Evals ok
for item my-list-1 {
    printfln "item = {}" item
}

Documentation

Index

Examples

Constants

This section is empty.

Variables

View Source
var Debug = false

Functions

func Evaluate

func Evaluate(node Node) (any, error)
Example (Arithmetic)
package main

import (
	"fmt"
	"log"

	"github.com/aziis98/ergolas"
)

func main() {
	tokens, err := ergolas.Tokenize(`1 + 2 * 3`)
	if err != nil {
		log.Fatal(err)
	}

	node, err := ergolas.ParseExpression(tokens)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println("Ast:")
	ergolas.PrintAST(node)

	result, err := ergolas.Evaluate(node)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println("Value:")
	fmt.Printf("%v\n", result)

}
Output:

Ast:
- Binary
  - Binary
    - Integer { Value: "1" }
    - Operator { Value: "+" }
    - Integer { Value: "2" }
  - Operator { Value: "*" }
  - Integer { Value: "3" }
Value:
9
Example (Conditionals)
package main

import (
	"fmt"
	"log"

	"github.com/aziis98/ergolas"
)

func main() {
	tokens, err := ergolas.Tokenize(`
		println ">>> " (false && false) " ~ false"
		println ">>> " ("hi" && false) " ~ false"
		println ">>> " (3.0 && false) " ~ false"
		println ">>> " (false && true) " ~ false"
		println ">>> " (true && :example) " ~ :example"
		println ">>> " (true && true) " ~ true"
	`)
	if err != nil {
		log.Fatal(err)
	}

	node, err := ergolas.Parse(tokens)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println("Ast:")
	ergolas.PrintAST(node)

	fmt.Println("Value:")
	if _, err := ergolas.Evaluate(node); err != nil {
		log.Fatal(err)
	}

}
Output:

Ast:
- Program
  - FunctionCall
    - Identifier { Value: "println" }
    - String { Value: ">>> " }
    - Parenthesis
      - Binary
        - Identifier { Value: "false" }
        - Operator { Value: "&&" }
        - Identifier { Value: "false" }
    - String { Value: " ~ false" }
  - FunctionCall
    - Identifier { Value: "println" }
    - String { Value: ">>> " }
    - Parenthesis
      - Binary
        - String { Value: "hi" }
        - Operator { Value: "&&" }
        - Identifier { Value: "false" }
    - String { Value: " ~ false" }
  - FunctionCall
    - Identifier { Value: "println" }
    - String { Value: ">>> " }
    - Parenthesis
      - Binary
        - Float { Value: "3" }
        - Operator { Value: "&&" }
        - Identifier { Value: "false" }
    - String { Value: " ~ false" }
  - FunctionCall
    - Identifier { Value: "println" }
    - String { Value: ">>> " }
    - Parenthesis
      - Binary
        - Identifier { Value: "false" }
        - Operator { Value: "&&" }
        - Identifier { Value: "true" }
    - String { Value: " ~ false" }
  - FunctionCall
    - Identifier { Value: "println" }
    - String { Value: ">>> " }
    - Parenthesis
      - Binary
        - Identifier { Value: "true" }
        - Operator { Value: "&&" }
        - Quoted
          - Identifier { Value: "example" }
    - String { Value: " ~ :example" }
  - FunctionCall
    - Identifier { Value: "println" }
    - String { Value: ">>> " }
    - Parenthesis
      - Binary
        - Identifier { Value: "true" }
        - Operator { Value: "&&" }
        - Identifier { Value: "true" }
    - String { Value: " ~ true" }
Value:
>>> false ~ false
>>> false ~ false
>>> false ~ false
>>> false ~ false
>>> {Quoted [{Identifier example}]} ~ :example
>>> true ~ true

func EvaluateWith

func EvaluateWith(node Node, ctx *Context) (any, error)

func PrintAST

func PrintAST(node Node)

Types

type Context

type Context struct {
	Parent   *Context
	Bindings map[string]any
}

func NewRootContext

func NewRootContext() *Context

func (*Context) GetKey

func (ctx *Context) GetKey(name string) (any, error)

type Node

type Node interface {
	Type() NodeType
	Children() []Node
	Metadata() NodeMetadata
}

func Parse

func Parse(tokens []Token) (Node, error)
Example (Complex_expression)
package main

import (
	"log"

	"github.com/aziis98/ergolas"
)

func main() {
	tokens, err := ergolas.Tokenize(`foo (bar 1 2 3 "hi") (baz (3 * 4.0 + (2 ^ 3)) :symbol)`)
	if err != nil {
		log.Fatal(err)
	}

	node, err := ergolas.Parse(tokens)
	if err != nil {
		log.Fatal(err)
	}

	ergolas.PrintAST(node)

}
Output:

- Program
  - FunctionCall
    - Identifier { Value: "foo" }
    - Parenthesis
      - FunctionCall
        - Identifier { Value: "bar" }
        - Integer { Value: "1" }
        - Integer { Value: "2" }
        - Integer { Value: "3" }
        - String { Value: "hi" }
    - Parenthesis
      - FunctionCall
        - Identifier { Value: "baz" }
        - Parenthesis
          - Binary
            - Binary
              - Integer { Value: "3" }
              - Operator { Value: "*" }
              - Float { Value: "4" }
            - Operator { Value: "+" }
            - Parenthesis
              - Binary
                - Integer { Value: "2" }
                - Operator { Value: "^" }
                - Integer { Value: "3" }
        - Quoted
          - Identifier { Value: "symbol" }
Example (Function_call_precedence)
package main

import (
	"fmt"
	"log"

	"github.com/aziis98/ergolas"
)

func main() {
	tokens, err := ergolas.Tokenize(`f x + f y`)
	if err != nil {
		log.Fatal(err)
	}

	node, err := ergolas.ParseExpression(tokens)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println("Ast:")
	ergolas.PrintAST(node)

}
Output:

Ast:
- FunctionCall
  - Identifier { Value: "f" }
  - Binary
    - Identifier { Value: "x" }
    - Operator { Value: "+" }
    - Identifier { Value: "f" }
  - Identifier { Value: "y" }
Example (Inline_if_blocks)
package main

import (
	"log"

	"github.com/aziis98/ergolas"
)

func main() {
	tokens, err := ergolas.Tokenize(`if { ans == 42 } { println "Yep" } { println "Nope" }`)
	if err != nil {
		log.Fatal(err)
	}

	node, err := ergolas.Parse(tokens)
	if err != nil {
		log.Fatal(err)
	}

	ergolas.PrintAST(node)

}
Output:

- Program
  - FunctionCall
    - Identifier { Value: "if" }
    - Block
      - Binary
        - Identifier { Value: "ans" }
        - Operator { Value: "==" }
        - Integer { Value: "42" }
    - Block
      - FunctionCall
        - Identifier { Value: "println" }
        - String { Value: "Yep" }
    - Block
      - FunctionCall
        - Identifier { Value: "println" }
        - String { Value: "Nope" }
Example (Multiline_if_blocks)
package main

import (
	"log"

	"github.com/aziis98/ergolas"
)

func main() {
	tokens, err := ergolas.Tokenize(`
		if { ans == 42 } { 
			println "Yep"
		} {
			println "Nope"
		}
	`)
	if err != nil {
		log.Fatal(err)
	}

	node, err := ergolas.Parse(tokens)
	if err != nil {
		log.Fatal(err)
	}

	ergolas.PrintAST(node)

}
Output:

- Program
  - FunctionCall
    - Identifier { Value: "if" }
    - Block
      - Binary
        - Identifier { Value: "ans" }
        - Operator { Value: "==" }
        - Integer { Value: "42" }
    - Block
      - FunctionCall
        - Identifier { Value: "println" }
        - String { Value: "Yep" }
    - Block
      - FunctionCall
        - Identifier { Value: "println" }
        - String { Value: "Nope" }
Example (Quasiquote)
package main

import (
	"fmt"
	"log"

	"github.com/aziis98/ergolas"
)

func main() {
	tokens, err := ergolas.Tokenize(`:(1 + 2 + $(2 * 2))`)
	if err != nil {
		log.Fatal(err)
	}

	node, err := ergolas.ParseExpression(tokens)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println("Ast:")
	ergolas.PrintAST(node)

}
Output:

Ast:
- Quoted
  - Parenthesis
    - Binary
      - Binary
        - Integer { Value: "1" }
        - Operator { Value: "+" }
        - Integer { Value: "2" }
      - Operator { Value: "+" }
      - Unquote
        - Parenthesis
          - Binary
            - Integer { Value: "2" }
            - Operator { Value: "*" }
            - Integer { Value: "2" }

func ParseExpression

func ParseExpression(tokens []Token) (Node, error)

func ParseExpressions

func ParseExpressions(tokens []Token) (Node, error)

type NodeMetadata

type NodeMetadata map[string]any

func (NodeMetadata) String

func (nm NodeMetadata) String() string

type NodeType

type NodeType string
var (
	ProgramNode           NodeType = "Program"
	ExpressionsNode       NodeType = "Expressions"
	FunctionCallNode      NodeType = "FunctionCall"
	BinaryExpressionNode  NodeType = "Binary"
	UnaryExpressionNode   NodeType = "Unary"
	QuotedExpressionNode  NodeType = "Quoted"
	UnquoteExpressionNode NodeType = "Unquote"
	PropertyAccessNode    NodeType = "PropertyAccess"
	ParenthesisNode       NodeType = "Parenthesis"
	IdentifierNode        NodeType = "Identifier"
	BlockNode             NodeType = "Block"
	IntegerNode           NodeType = "Integer"
	FloatNode             NodeType = "Float"
	StringNode            NodeType = "String"
	OperatorNode          NodeType = "Operator"
)
var ErrorNodeType NodeType = "ErrorNode"

type Token

type Token struct {
	Type     TokenType
	Value    string
	Location int
}

func Tokenize

func Tokenize(source string) ([]Token, error)

type TokenType

type TokenType string
var (
	FloatToken       TokenType = "Float"
	IntegerToken     TokenType = "Integer"
	StringToken      TokenType = "String"
	QuoteToken       TokenType = "Quote"
	UnquoteToken     TokenType = "Unquote"
	LOperatorToken   TokenType = "LOperator"
	ROperatorToken   TokenType = "ROperator"
	PunctuationToken TokenType = "Punctuation"
	IdentifierToken  TokenType = "Identifier"
	CommentToken     TokenType = "Comment"
	WhitespaceToken  TokenType = "Whitespace"
	NewlineToken     TokenType = "Newline"
)

type TokenizeError

type TokenizeError struct {
	Source   *string
	Location int
	Message  string
}

func (TokenizeError) Error

func (e TokenizeError) Error() string

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL