├── .deepsource.toml ├── .gitignore ├── .vscode ├── launch.json └── settings.json ├── README.md ├── base ├── elara.go ├── execution.go └── repl.go ├── build-all.sh ├── cli.go ├── doc ├── examples │ ├── DataTypes.elr │ ├── FunctionalProgramming.elr │ ├── HelloWorld.elr │ ├── JavaInterop.elr │ ├── Logic.elr │ ├── Mutable.elr │ ├── ObjectOrientedProgramming.elr │ ├── PureImpure.elr │ └── TypeClasses.elr └── specification.md ├── elara.elr ├── go.mod ├── grammar ├── ElaraLexer.g4 └── ElaraParser.g4 ├── interpreter ├── built-in-types.go ├── collections.go ├── command.go ├── context.go ├── contexts.go ├── extension.go ├── function.go ├── instance.go ├── interpreter.go ├── ints.go ├── maps.go ├── modifiers.go ├── types.go ├── value.go ├── values.go └── variables.go ├── lexer ├── chars.go ├── lexer.go ├── lexer_benchmark_test.go ├── lexer_test.go ├── scanner.go ├── token.go └── token_type.go ├── parser ├── defined_type.go ├── expressions.go ├── function.go ├── generics.go ├── meta.go ├── parser.go ├── statements.go ├── struct.go └── type.go ├── samples └── fizzbuzz.elr ├── tests ├── elara_benchmark_test.go ├── execution_benchmark_test.go ├── variable_assignment_test.go └── variable_reassignment_test.go ├── typer └── typer.go └── util └── strings.go /.deepsource.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | [[analyzers]] 4 | name = "go" 5 | enabled = true 6 | 7 | [analyzers.meta] 8 | import_paths = ["github.com/ElaraLang/elara"] 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.iml 3 | elara.exe 4 | go.sum 5 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch", 9 | "type": "go", 10 | "request": "launch", 11 | "mode": "auto", 12 | "program": "${fileDirname}", 13 | "env": {}, 14 | "args": [] 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "ANTLR", 4 | "Elara" 5 | ] 6 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | elara logo 3 |

4 | 5 | # Elara 6 | https://discord.gg/xu5gSTV 7 | 8 | Elara is a multi-paradigm (although primarily functional) language with a clean syntax with little "noise". 9 | 10 | Unlike most functional languages, Elara will not restrict the programmer for the sake of it, 11 | and prioritises developer freedom wherever possible. 12 | 13 | ## Basic Syntax 14 | *(all syntax is subject to change)* 15 | ### Variable Declaration 16 | Variables are declared with the syntax: 17 | 18 | `let [name] = [value]` with type inference. 19 | 20 | For example, `let age = 23` 21 | 22 | Explicit type specification: 23 | 24 | `let [name]: [Type] = [value]` 25 | 26 | For example, `let name: String = "Bob"` 27 | 28 | By default, variables are reference immutable. 29 | For mutability, the syntax `let mut [name] = [value]` should be used. 30 | This is discouraged as immutable values should always be preferred. 31 | 32 | 33 | ### Function Declaration 34 | 35 | Functions are first class types, and are declared in a near identical way to variables: 36 | 37 | Note that the **Arrow Syntax** (`=>`) is used with functions to distinguish from a function *call*. 38 | ``` 39 | let printHello() => { 40 | print("Hello World!") 41 | } 42 | ``` 43 | 44 | Functions with a single expression can be declared with a more concise syntax: 45 | ``` 46 | let printHello => print("Hello World!") 47 | ``` 48 | 49 | Functions with parameters: 50 | ``` 51 | let printTwice(String message) => { 52 | print(message) 53 | print(message) 54 | } 55 | ``` 56 | 57 | Functions with a clearly defined return type: 58 | ``` 59 | let add(Int a, Int b) => Int { 60 | a + b 61 | } 62 | ``` 63 | 64 | ### Function Calling 65 | 66 | Elara supports a wide range of function calling syntax to try and make programming more natural and less restrictive: 67 | 68 | #### Simple Calling: 69 | `printTwice("Hello")` 70 | 71 | `printHello()` 72 | 73 | 74 | #### OOP style calling on a receiver function: 75 | `"Hello".printTwice()` 76 | 77 | This feature works with multiple parameters: 78 | ``` 79 | let addTo(Int a, Int b) => { 80 | a + b 81 | } 82 | 83 | 3.addTo(4) 84 | addTo(3, 4) 85 | ``` 86 | 87 | the 2 calls are identical 88 | 89 | #### Infix Style calling 90 | You can also omit the parentheses and commas with infix functions (functions with 2 parameters): 91 | ``` 92 | 3 addTo 4 93 | ``` 94 | 95 | ### Collections 96 | Elara has collection literals for the 2 main types: 97 | 98 | #### Lists 99 | List literals are a comma separated list of elements, surrounded by square brackets. 100 | 101 | - Empty List: `[]` 102 | - Single Element Lists: `[1]` 103 | - Multi Element Lists: `[1, 2, 3, 4]` 104 | 105 | Lists are immutable, and the recommended implementation is a persistent one to make copying more efficient. 106 | 107 | Lists should aim to be as homogeneous as possible - that is, 108 | Lists should try to form a union of all elements' types to form the List's type. 109 | 110 | List types are declared in the format `[ElementType]` 111 | For example `[Any]`, `[Int]`, `[() => Unit]` 112 | 113 | #### Maps 114 | Map literals are a comma separated list of **Entries**, surrounded by curly brackets. 115 | 116 | Entries are composed of a Key and a Value, separated by a colon. 117 | An Entry's Key and Value must both be valid expressions. 118 | 119 | - Empty Map: `{}` 120 | - Single Element Map: `{a: "b"}` (this assumes a variable named `a` is present in the current scope) 121 | - Multi Element Map: 122 | ``` 123 | { 124 | a: "b", 125 | c: "d" 126 | } 127 | ``` 128 | (Again, this assumes the presence of `a` and `c`) 129 | 130 | Maps are also immutable, and are typically implemented as a hash table. 131 | 132 | Map types follow the format `{K : V}` 133 | For example: `{Int : String}`, `{String : () => Unit}`, `{Person : Int}` 134 | 135 | ### Structs 136 | 137 | Structs in Elara are **Data Only** 138 | They are declared with the following syntax: 139 | ``` 140 | struct Person { 141 | String name 142 | mut Int age 143 | Int height = 110 144 | } 145 | ``` 146 | 147 | And can be instantiated like so: 148 | `let mark = Person("Mark", 32, 160)` 149 | 150 | 151 | #### OOP Structs: 152 | Structs can easily replicate objects with extension syntax, which is the most idiomatic way of adding functionality to structs: 153 | 154 | ``` 155 | struct Person { 156 | //blah 157 | } 158 | extend Person { 159 | let celebrateBirthday => { 160 | print("Happy Birthday " + name + "!") 161 | age += 1 162 | } 163 | } 164 | ``` 165 | 166 | from here we can do `somePerson.celebrateBirthday()` as if it was a method. 167 | 168 | The `extend` syntax works with any type and can be done from any file. 169 | 170 | #### Inheritance: 171 | The `extend` syntax effectively adds inheritance too: 172 | 173 | ``` 174 | struct Person { 175 | //blah 176 | } 177 | extend Person { 178 | struct Student { 179 | Topic major 180 | } 181 | } 182 | ``` 183 | 184 | This is not true "inheritance". Instead, the `Student` struct will copy all the properties of `Person`. 185 | Because the type system is contract based (that is, type `B` can be assigned to type `A` if it has the same contract in its members), 186 | this is effectively inheritance - we can use an instance of `Student` wherever we use a `Person`. 187 | ### Type System 188 | 189 | Elara features a simple, linear type system. 190 | `Any` is the root of the type hierarchy, with subtypes such as `Int`, `String` and `Person`. 191 | 192 | However, there are also a few quirks that aim to make the type system more flexible: 193 | 194 | **Contract based type parameters** 195 | 196 | Type parameters for generics support contract based boundary. 197 | Take for example the simple generic function, ignoring the unnecessary generic (since T can be any type): 198 | ``` 199 | #T 200 | let printAndReturn(T data) => T { 201 | print(data) 202 | return data 203 | } 204 | ``` 205 | 206 | We cannot guarantee that every type will give a user-friendly value for `print`. 207 | 208 | To work around this, we can add a boundary to `T`, that only accepts types that define a `toString` function: 209 | 210 | ``` 211 | String } > 212 | let printAndReturn(T data) => T { 213 | print(data.toString()) 214 | return data 215 | } 216 | ``` 217 | 218 | This gives programmers extra flexibility in that they can program to a specific contract, rather than a type 219 | 220 | 221 | ### Namespaces and Importing 222 | 223 | The namespace system in Elara is simple. 224 | 225 | Declaring a namespace is usually done at the top of the file: 226 | `namespace elara/core` 227 | 228 | Namespaces follow the format `base/module`, similar to languages like Clojure. 229 | 230 | Importing a namespace is simple: 231 | `import elara/core` 232 | 233 | The files will now have access to the contents of all files in that namespace. 234 | 235 | ### Functional Features 236 | * Lambdas are defined identically to functions: 237 | `let lambda = (Type name) => {}` 238 | Parameter types can be omitted if possible to infer from context. 239 | 240 | * Functions are first class: 241 | ``` 242 | let add1 = (Int a) => a + 1 243 | 244 | let added1List = someList map add1 245 | ``` 246 | 247 | * Function chaining is trivial: 248 | ``` 249 | someList.map(add1).filter(isEven).forEach(print) 250 | ``` 251 | ### Conclusion 252 | 253 | Elara is in its very early stages, with the evaluator being nowhere near finished. 254 | 255 | The eventual plan includes: 256 | * Static typing. 257 | * Compiling to native code, but also supporting other backends such as JavaScript or JVM Bytecode. 258 | * A proper standard library. 259 | * Allowing type inference for function parameters. 260 | -------------------------------------------------------------------------------- /base/elara.go: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import ( 4 | "fmt" 5 | "github.com/mholt/archiver" 6 | "io" 7 | "io/ioutil" 8 | "net/http" 9 | "os" 10 | "os/user" 11 | "path" 12 | "path/filepath" 13 | "time" 14 | ) 15 | 16 | func ExecuteFull(fileName string, scriptMode bool) { 17 | LoadStdLib() 18 | 19 | input := loadFile(fileName) 20 | start := time.Now() 21 | _, lexTime, parseTime, execTime := Execute(&fileName, string(input), scriptMode) 22 | 23 | totalTime := time.Since(start) 24 | 25 | fmt.Println("===========================") 26 | fmt.Printf("Lexing took %s\nParsing took %s\nExecution took %s\nExecuted in %s.\n", lexTime, parseTime, execTime, totalTime) 27 | fmt.Println("===========================") 28 | } 29 | 30 | func LoadStdLib() { 31 | usr, err := user.Current() 32 | if err != nil { 33 | panic(err) 34 | } 35 | elaraPath := path.Join(usr.HomeDir, ".elara/") 36 | 37 | err = os.Mkdir(elaraPath, os.ModePerm) 38 | if err != nil && !os.IsExist(err) { 39 | panic(err) 40 | } 41 | 42 | filePath := path.Join(elaraPath, "stdlib/") 43 | err = os.Mkdir(filePath, os.ModePerm) 44 | if err != nil && !os.IsExist(err) { 45 | panic(err) 46 | } 47 | downloadStandardLibrary(filePath) 48 | 49 | filepath.Walk(elaraPath, loadWalkedFile) 50 | } 51 | 52 | func downloadStandardLibrary(to string) { 53 | zipPath := path.Join(to, "stdlib.zip") 54 | _, err := os.Stat(zipPath) 55 | if err == nil && !os.IsNotExist(err) { 56 | return 57 | } 58 | 59 | standardLibraryURL := "https://github.com/ElaraLang/elara-stdlib/archive/main.zip" 60 | resp, err := http.Get(standardLibraryURL) 61 | if err != nil { 62 | panic(err) 63 | } 64 | defer resp.Body.Close() 65 | 66 | out, err := os.Create(zipPath) 67 | if err != nil { 68 | panic(err) 69 | } 70 | defer out.Close() 71 | _, err = io.Copy(out, resp.Body) 72 | if err != nil { 73 | panic(err) 74 | } 75 | 76 | err = archiver.NewZip().Unarchive(zipPath, to) 77 | if err != nil { 78 | panic(err) 79 | } 80 | } 81 | 82 | func loadWalkedFile(path string, info os.FileInfo, err error) error { 83 | if err != nil { 84 | panic(err) 85 | } 86 | if info.IsDir() { 87 | return nil 88 | } 89 | if filepath.Ext(path) != ".elr" { 90 | return nil 91 | } 92 | content := loadFile(path) 93 | Execute(&path, string(content), false) 94 | return nil 95 | } 96 | 97 | func loadFile(fileName string) []byte { 98 | 99 | input, err := ioutil.ReadFile(fileName) 100 | 101 | if err != nil { 102 | panic(err) 103 | } 104 | return input 105 | } 106 | -------------------------------------------------------------------------------- /base/execution.go: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ElaraLang/elara/interpreter" 6 | "github.com/ElaraLang/elara/lexer" 7 | "github.com/ElaraLang/elara/parser" 8 | "os" 9 | "time" 10 | ) 11 | 12 | func Execute(fileName *string, code string, scriptMode bool) (results []*interpreter.Value, lexTime, parseTime, execTime time.Duration) { 13 | start := time.Now() 14 | result := lexer.Lex(code) 15 | lexTime = time.Since(start) 16 | 17 | start = time.Now() 18 | psr := parser.NewParser(result) 19 | parseRes, errs := psr.Parse() 20 | parseTime = time.Since(start) 21 | 22 | if len(errs) != 0 { 23 | file := "Unknown File" 24 | if fileName != nil { 25 | file = *fileName 26 | } 27 | _, _ = os.Stderr.WriteString(fmt.Sprintf("Syntax Errors found in %s: \n", file)) 28 | for _, err := range errs { 29 | _, _ = os.Stderr.WriteString(fmt.Sprintf("%s\n", err)) 30 | } 31 | return []*interpreter.Value{}, lexTime, parseTime, time.Duration(-1) 32 | } 33 | 34 | start = time.Now() 35 | evaluator := interpreter.NewInterpreter(parseRes) 36 | 37 | results = evaluator.Exec(scriptMode) 38 | execTime = time.Since(start) 39 | return results, lexTime, parseTime, execTime 40 | } 41 | -------------------------------------------------------------------------------- /base/repl.go: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ElaraLang/elara/interpreter" 6 | "github.com/ElaraLang/elara/lexer" 7 | "github.com/ElaraLang/elara/parser" 8 | ) 9 | 10 | var replFile = "Repl" 11 | 12 | type ReplSession struct { 13 | Parser parser.Parser 14 | Evaluator interpreter.Interpreter 15 | } 16 | 17 | func NewReplSession() ReplSession { 18 | return ReplSession{ 19 | Parser: *parser.NewEmptyParser(), 20 | Evaluator: *interpreter.NewEmptyInterpreter(), 21 | } 22 | } 23 | 24 | func (repl *ReplSession) Execute(input string) interface{} { 25 | tokens := lexer.Lex(input) 26 | repl.Parser.Reset(tokens) 27 | result, err := repl.Parser.Parse() 28 | if len(err) > 0 { 29 | fmt.Println("Errors found: ", err) 30 | return nil 31 | } 32 | repl.Evaluator.ResetLines(&result) 33 | evalRes := repl.Evaluator.Exec(true) 34 | return evalRes 35 | } 36 | -------------------------------------------------------------------------------- /build-all.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Based on https://gist.github.com/eduncan911/68775dba9d3c028181e4 4 | # but improved to use the `go` command so it never goes out of date. 5 | 6 | type setopt >/dev/null 2>&1 7 | 8 | contains() { 9 | # Source: https://stackoverflow.com/a/8063398/7361270 10 | [[ $1 =~ (^|[[:space:]])$2($|[[:space:]]) ]] 11 | } 12 | 13 | SOURCE_FILE=$(echo "$@" | sed 's/\.go//') 14 | CURRENT_DIRECTORY="${PWD##*/}" 15 | OUTPUT=${SOURCE_FILE:-$CURRENT_DIRECTORY}/bin # if no src file given, use current dir name 16 | FAILURES="" 17 | 18 | # You can set your own flags on the command line 19 | FLAGS=${FLAGS:-"-ldflags=\"-s -w\""} 20 | 21 | # A list of OSes to not build for, space-separated 22 | # It can be set from the command line when the script is called. 23 | NOT_ALLOWED_OS=${NOT_ALLOWED_OS:-"js android ios solaris illumos aix"} 24 | 25 | # Get all targets 26 | while IFS= read -r target; do 27 | GOOS=${target%/*} 28 | GOARCH=${target#*/} 29 | BIN_FILENAME="${OUTPUT}-${GOOS}-${GOARCH}" 30 | 31 | if contains "$NOT_ALLOWED_OS" "$GOOS" ; then 32 | continue 33 | fi 34 | 35 | # Check for arm and set arm version 36 | if [[ $GOARCH == "arm" ]]; then 37 | # Set what arm versions each platform supports 38 | if [[ $GOOS == "darwin" ]]; then 39 | arms="7" 40 | elif [[ $GOOS == "windows" ]]; then 41 | # This is a guess, it's not clear what Windows supports from the docs 42 | # But I was able to build all these on my machine 43 | arms="5 6 7" 44 | elif [[ $GOOS == *"bsd" ]]; then 45 | arms="6 7" 46 | else 47 | # Linux goes here 48 | arms="5 6 7" 49 | fi 50 | 51 | # Now do the arm build 52 | for GOARM in $arms; do 53 | BIN_FILENAME="${OUTPUT}-${GOOS}-${GOARCH}${GOARM}" 54 | if [[ "${GOOS}" == "windows" ]]; then BIN_FILENAME="${BIN_FILENAME}.exe"; fi 55 | CMD="GOARM=${GOARM} GOOS=${GOOS} GOARCH=${GOARCH} go build $FLAGS -o ${BIN_FILENAME} $@" 56 | echo "${CMD}" 57 | eval "${CMD}" || FAILURES="${FAILURES} ${GOOS}/${GOARCH}${GOARM}" 58 | done 59 | else 60 | # Build non-arm here 61 | if [[ "${GOOS}" == "windows" ]]; then BIN_FILENAME="${BIN_FILENAME}.exe"; fi 62 | CMD="GOOS=${GOOS} GOARCH=${GOARCH} go build $FLAGS -o ${BIN_FILENAME} $@" 63 | echo "${CMD}" 64 | eval "${CMD}" || FAILURES="${FAILURES} ${GOOS}/${GOARCH}" 65 | fi 66 | done <<< "$(go tool dist list)" 67 | 68 | if [[ "${FAILURES}" != "" ]]; then 69 | echo "" 70 | echo "${SCRIPT_NAME} failed on: ${FAILURES}" 71 | exit 1 72 | fi 73 | -------------------------------------------------------------------------------- /cli.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "github.com/ElaraLang/elara/base" 6 | "github.com/urfave/cli/v2" 7 | "os" 8 | ) 9 | 10 | func main() { 11 | app := &cli.App{ 12 | Name: "Elara", 13 | Usage: "ExecuteFull Elara Code", 14 | 15 | Flags: []cli.Flag{ 16 | &cli.BoolFlag{ 17 | Name: "script", 18 | Value: false, 19 | Usage: "Script Mode (print the result of every expression)", 20 | }, 21 | }, 22 | Action: func(c *cli.Context) error { 23 | fileName := c.Args().Get(0) 24 | if fileName == "" { 25 | return errors.New("no file provided to execute - nothing to do") 26 | } 27 | 28 | scriptMode := c.Bool("script") 29 | base.ExecuteFull(fileName, scriptMode) 30 | return nil 31 | }, 32 | } 33 | 34 | err := app.Run(os.Args) 35 | if err != nil { 36 | panic(err) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /doc/examples/DataTypes.elr: -------------------------------------------------------------------------------- 1 | module DataTypes 2 | 3 | // Simple type alias 4 | type Identifier = String 5 | 6 | def id : Identifier 7 | let id = "12ab4c" 8 | 9 | 10 | // Algebraic Data Types 11 | 12 | type BinaryOperator = 13 | Addition 14 | | Subtraction 15 | | Multiplication 16 | | Division 17 | 18 | // More complex Algebraic Data Type with each type constructor having a different signature 19 | type Entity = 20 | | Empty 21 | | NPC { name : String } 22 | | Player { name : String, level : mut Int } 23 | 24 | // record type 25 | 26 | type User = { 27 | email : String, 28 | passwordHash : String, 29 | signUpDate: Timestamp 30 | } 31 | 32 | // Generic type 33 | type List a = Empty | Cons a (List a) 34 | 35 | let user = User "a@example.com" "" "-1" 36 | println (user.email) // prints a@example.com -------------------------------------------------------------------------------- /doc/examples/FunctionalProgramming.elr: -------------------------------------------------------------------------------- 1 | module FunctionalProgramming 2 | 3 | // Optional type declaration for compose 4 | def compose : (a -> b) -> (b -> c) -> (a -> c) 5 | let compose f g = \x -> f (g x) 6 | 7 | let add1 x = x + 1 8 | let double y = y * 2 9 | 10 | // Creates a new function that calls double first, then compose 11 | let add1AndDouble = compose add1 double 12 | 13 | let output = println (add1AndDouble 3) // prints 7 -------------------------------------------------------------------------------- /doc/examples/HelloWorld.elr: -------------------------------------------------------------------------------- 1 | module Main // This is optional when compiling / executing a single file, but required when compiling a multiple-file project 2 | 3 | let main = 4 | println "Hello World!" -------------------------------------------------------------------------------- /doc/examples/JavaInterop.elr: -------------------------------------------------------------------------------- 1 | module JavaInterop 2 | 3 | import java.lang.String 4 | import Elara.String 5 | import Elara.Unsafe 6 | 7 | // Calling java methods 8 | 9 | // toJavaString converts an Elara String into a java.lang.String 10 | def hello : java.lang.String 11 | let hello = toJavaString "hello world!" 12 | 13 | // Now we have an issue. Unless it is obvious (eg a constructor, or has a @Contract("pure") annotation), the compiler has no way of knowing if any given java method 14 | // is pure or not. 15 | // As a safety measure it assumes that they are all impure, but because there will be cases when the user knows better, 16 | // we can use `run!` to call an impure function as if it was pure. 17 | // This should be used sparingly, and only when the you can guarantee that the function is pure 18 | 19 | let upperHello = run! hello.toUpperCase () 20 | 21 | 22 | // Constructing and using java objects 23 | 24 | import java.util.ArrayList 25 | 26 | def createArrayList : Int => ArrayList Int 27 | let createArrayList a = 28 | let list = ArrayList () 29 | list.add 1 // This is very impure and so we're not going to use `run!` 30 | list.add 2 31 | list.addAll [3, 4, 5, a] 32 | list 33 | 34 | // However, the function as a whole is pure. It uses some impure functions internally, but due to their scoping their are no visible side effects 35 | // What can we do here? 36 | 37 | // We can create a wrapper function using let and use `run!` 38 | 39 | def createArrayListPure : Int -> ArrayList Int 40 | let createArrayListPure a = 41 | let f () = 42 | let list = ArrayList () 43 | list.add 1 // This is very impure and so we're not going to use `run!` 44 | list.add 2 45 | list.addAll [3, 4, 5, a] 46 | list 47 | run! f () 48 | 49 | 50 | // Extending java classes and interfaces 51 | 52 | type Player = ... 53 | 54 | class PlayerGroup <: Iterable Player where 55 | def players : [Player] // Internally uses an Elara List 56 | 57 | def iterator : () => Iterator Player // Similarly, when extending Java methods, they're assumed to be impure unless explicitly mentioned otherwise 58 | let iterator () = getJavaIterator players -------------------------------------------------------------------------------- /doc/examples/Logic.elr: -------------------------------------------------------------------------------- 1 | module Logic 2 | 3 | let simpleIfElse = 4 | if 3 < 4 then println "good" else println "bad" 5 | 6 | def when : Boolean -> lazy b => () 7 | let when a (lazy b) = if a then b else () 8 | 9 | let whenUsage = when (3 < 4) (println "good") 10 | 11 | // Pattern matching on booleans 12 | let matching = match 3 < 4 13 | True -> println "good" 14 | False -> println "bad" -------------------------------------------------------------------------------- /doc/examples/Mutable.elr: -------------------------------------------------------------------------------- 1 | // In Elara everything is immutable by default, however most things can also be made mutable 2 | 3 | let a = 3 // This creates an immutable binding between the name "a" and the value 3 4 | 5 | def pure : () -> Int 6 | let pure = 7 | a // Reading immutable variables is pure 8 | a = 4 // Obviously, writing is very bad and won't compile 9 | 10 | let mut b = 3 // This creates a mutable variable named "b" with the initial value 3 11 | 12 | def impure : () => Int 13 | let impure = 14 | b // Reading OR writing to a mutable variable is an impure operation and so can't be done in a pure function 15 | b = 5 16 | 17 | 18 | // So far we've seen "reference immutability", but we also need value immutability 19 | 20 | // Lists are immutable by default 21 | 22 | let list = [1, 2, 3] 23 | 24 | // but can be made mutable by adding the mut keyword: 25 | 26 | let mutList = mut [1, 2, 3] 27 | 28 | list.add 4 // List.add is impure 29 | 30 | // Note that this list is value-mutable, but still reference-immutable, therefore 31 | mutList = mut [] // this will NOT compile 32 | 33 | // but this will 34 | let mut doubleMut = mut [] 35 | doubleMut = mut [1] 36 | doubleMut.add 2 37 | 38 | 39 | // the mut prefix 40 | // the mut prefix can be added to any type. `mut T` means "a mutable reference to a value of type T" 41 | 42 | -------------------------------------------------------------------------------- /doc/examples/ObjectOrientedProgramming.elr: -------------------------------------------------------------------------------- 1 | module ObjectOrientedProgramming 2 | 3 | 4 | class Entity where 5 | def name : String // Name is an immutable property of Entity 6 | def health : mut Int // Health is a mutable property of Entity 7 | 8 | // Constructor-like function, is called *after* name and health are assigned 9 | let init = // This constructor does side effects 10 | println "Created new entity named " + name 11 | 12 | let doDamage damage = 13 | let newHealth = this.health - damage 14 | // When is a function in the standard library that acts as syntax sugar for if / else with side effects 15 | when (newHealth <= 0) (println name + " died!") 16 | this.health = newHealth 17 | 18 | class Player <: Entity, Levelled where 19 | // Player is a subtype of Entity and so it will inherit name and health 20 | override def level : mut Int // We use override here because level is overriding from the declaration in Levelled 21 | 22 | 23 | interface Levelled where 24 | def level : mut Int 25 | 26 | -------------------------------------------------------------------------------- /doc/examples/PureImpure.elr: -------------------------------------------------------------------------------- 1 | module PureImpure 2 | 3 | // Pure functions have type a -> b 4 | def pureAdd : Int -> Int 5 | let pureAdd a = a + 3 6 | 7 | // Impure functions have type a => bad 8 | def impureAdd : Int => Int 9 | let impureAdd a = 10 | println "Adding 3 to " + (show a) + "!" 11 | a + 3 12 | 13 | // We can't call impure functions from pure ones 14 | def doesNotCompile : Int -> Int 15 | let doesNotCompile a = impureAdd a 16 | 17 | // But we can do the opposite... 18 | def doesCompile : Int => Int 19 | let doesCompile a = pureAdd a 20 | 21 | // However, the compiler always prefers pure functions and so should you 22 | def better : Int -> Int 23 | let better a = pureAdd a 24 | 25 | // Point free style 26 | let evenBetter = pureAdd -------------------------------------------------------------------------------- /doc/examples/TypeClasses.elr: -------------------------------------------------------------------------------- 1 | module TypeClasses 2 | 3 | // This creates a simple type class / trait that can show string representations of a value 4 | type class Show a where 5 | show : a -> String 6 | 7 | // This creates a simple record type that just wraps a String 8 | type Player = { 9 | name : String 10 | } 11 | 12 | // Defines the Show instance for Player that produces a String in the format "Player {name}" 13 | instance Show Player where 14 | show p = "Player { name = " + p.name + "} 15 | 16 | 17 | let player = Player "A" 18 | 19 | println (show player) // prints "Player {A}" 20 | 21 | 22 | // Type class constraints 23 | 24 | def print : (Show a) := a => () 25 | let print s = println (show s) -------------------------------------------------------------------------------- /doc/specification.md: -------------------------------------------------------------------------------- 1 | # Elara Programming Language Specification 2 | 3 | ## Chapter 0 - Grammar Syntax 4 | 5 | This specification uses a modified form of 6 | [ANTLR4 Grammar Syntax](https://github.com/antlr/antlr4/blob/master/doc/index.md) 7 | to describe the grammar of Elara. 8 | 9 | In many cases, natural language may be used instead of a 10 | strictly ANTLR-compliant syntax for the sake of simplicity. 11 | 12 | Literal patterns (for example a pattern matching the character sequence `/*`) 13 | should have their characters separated by spaces 14 | (the previous example becomes `/ *`). 15 | 16 | ## Chapter 1 - Lexical Structure 17 | 18 | ### 1.1 - Unicode 19 | 20 | Elara programs are encoded in the [Unicode character set](https://unicode.org) 21 | and almost all Unicode characters are supported in source code. 22 | 23 | Elara Source Code should either be encoded in a UTF-8 or UTF-16 format. 24 | 25 | ### 1.2 - Line Terminators 26 | 27 | Elara recognizes 3 different options for line terminator characters: 28 | `CR`, `LF`, or `CR` immediately followed by `LF`. 29 | 30 | Each of these patterns are treated as 1 single line terminator. 31 | These are used to determine when expressions start and end, 32 | and will determine line numbers in errors produced. 33 | 34 | ```antlr 35 | LineTerminator: 36 | | The ASCII CR Character 37 | | The ASCII LF Character 38 | | The ASCII CR Character immediately followed by the ASCII LF Character 39 | ``` 40 | 41 | ### 1.3 - Whitespace 42 | 43 | ```antlr 44 | WhiteSpace: 45 | | The ASCII SP Character, ` ` 46 | | The ASCII HT Character, `\t` 47 | | LineTerminator 48 | 49 | InputCharacter: Any Unicode Character except WhiteSpace 50 | ``` 51 | 52 | ### 1.4 - Comments 53 | 54 | Comment Properties: 55 | 56 | - Comments cannot be nested 57 | - 1 comment type's syntax has no special meaning in another comment 58 | - Comments do not apply in string literals 59 | 60 | Elara uses 2 main formats for comments: 61 | 62 | #### Single Line Comments 63 | 64 | Single line comments are denoted with the literal text `//`. 65 | Any source code following from this pattern until a **Line Terminator** 66 | character is encountered is ignored. 67 | 68 | ```antlr 69 | SingleLineComment: 70 | / / InputCharacter+ 71 | ``` 72 | 73 | #### Multi line comments 74 | 75 | Multi line comments start with `/*` and continue until `*/` is encountered. 76 | If no closing comment is found (i.e `EOF` is reached before a `*/`), 77 | an error should be raised by the compiler. 78 | 79 | ```antlr 80 | StartMultiLineComment: / * 81 | 82 | EndMultiLineComment: * / 83 | 84 | MultiLineComment: StartMultiLineComment InputCharacter+ EndMultiLineComment 85 | ``` 86 | 87 | ### 1.5 - Normal Identifiers 88 | 89 | Identifiers are unlimited length sequences of any of the following characters: 90 | 91 | - Any characters of the alphabet, upper or lower case 92 | - Any denary digit 93 | 94 | Additionally, a valid identifier must satisfy all of the following: 95 | 96 | - Must start with a character that is **NOT** a digit (i.e 0-9) 97 | - Must not directly match any reserved Elara keywords 98 | 99 | ```antlr 100 | IdentifierCharacter: Any characters of the latin alphabet, upper or lowercase 101 | 102 | IdentifierCharacterOrDigit: IdentifierCharacter or 0-9 103 | 104 | Identifier: IdentifierCharacter IdentifierCharacterOrDigit+ but not a Keyword 105 | ``` 106 | 107 | #### 1.5.1 - Type vs Binding Identifiers 108 | 109 | If an identifier starts with an uppercase character, 110 | it is implied to be referencing a type's identifier. 111 | 112 | On the other hand, starting with lowercase implies a "binding" identifier 113 | (that is, referring to some named expression) 114 | 115 | This means that all type names must start with uppercase 116 | (excluding generics), and all bindings must start with lowercase. 117 | 118 | ### 1.6 Operator Identifiers 119 | 120 | Elara supports custom operators and operator overloading. 121 | A separate type of identifier is defined for these. 122 | These identifiers may only consist of the following symbols: 123 | 124 | #### Valid Operator Symbols 125 | 126 | - `.` 127 | - `>` 128 | - `<` 129 | - `=` 130 | - `-` 131 | - `/` 132 | - `+` 133 | - `*` 134 | - `&` 135 | - `|` 136 | - `!` 137 | - `?` 138 | - `%` 139 | - `^` 140 | - `:` 141 | - `~` 142 | - `#` 143 | - `_` 144 | - `\` 145 | 146 | Additionally, they must not match any of the following patterns: 147 | 148 | - `=` 149 | - `/*` or `*/` 150 | - `//` 151 | 152 | When referenced in any context apart from infix application, 153 | the operator's identifier must also be surrounded in parentheses. 154 | For example, to define an operator `/=` we do 155 | 156 | ```fsharp 157 | let (/=) x y = [implementation] 158 | ``` 159 | 160 | ```antlr 161 | InvalidOperatorSymbol: 162 | | = 163 | | / * 164 | | * / 165 | | / / 166 | 167 | OperatorSymbol: Any character described in "Valid Operator Symbols" except InvalidOperatorSymbol 168 | 169 | QualifiedOperator: ( UnqualifiedOperator ) 170 | 171 | UnqualifiedOperator: OperatorSymbol+ 172 | ``` 173 | 174 | ### 1.7 - Number Literals 175 | 176 | Number Literals are unlimited sequences of numeric characters. 177 | 178 | For clarity, any number literals may contain `_` which can be used in place of 179 | a comma or dot in real world numbers. These should be ignored by the lexer and 180 | do not affect the resultant number in any way. 181 | For example, the literal `21_539` is functionally identical to `21539` 182 | 183 | Number Literals are translated to values of some type implementing 184 | the `Num` type class. That is, all number literals are polymorphic by default. 185 | 186 | ```antlr 187 | DecimalDigit: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 188 | 189 | BinaryDigit: 0 | 1 190 | 191 | HexadecimalDigit: DecimalDigit | a | b | c | d | e | f | A | B | C | D | E | F 192 | 193 | Separator: _ 194 | 195 | DecimalLiteral: (DecimalDigit | Separator)+ 196 | 197 | HexadecimalPrefix: 0x 198 | 199 | HexadecimalLiteral: HexadecimalPrefix (HexadecimalDigit | Separator)+ 200 | 201 | BinaryPrefix: 0b 202 | 203 | BinaryLiteral: BinaryPrefix (BinaryDigit | Separator)+ 204 | 205 | FloatingPointSeparator: . 206 | 207 | FloatingPoint: DecimalLiteral FloatingPointSeparator DecimalLiteral 208 | ``` 209 | 210 | #### 1.7.1 - Integer Literals 211 | 212 | ##### 1.7.1.1 - Decimal Integer Literals 213 | 214 | Decimal Integer Literals follow a base-10 notation and 215 | so any characters between `0` and `9` are accepted. 216 | 217 | ##### 1.7.1.2 - Hexadecimal Integer Literals 218 | 219 | Hexadecimal Integer Literals represent numbers in a base-16 format. 220 | Any number literal directly preceeded by a `0x` sequence of characters 221 | should be treated as hexadecimal. 222 | Any characters between `0` and `9`, along with upper or lowercase letters 223 | between `a` and `f` are accepted. 224 | 225 | ##### 1.7.1.3 - Binary Integer Literals 226 | 227 | These represent numbers in a base-2 format. Any number literal directly preceeded 228 | by a `0b` sequence of characters should be treated as binary. 229 | These only accept 2 characters: `0` and `1` 230 | 231 | #### 1.7.2 - Floating Point Literals 232 | 233 | Floating point literals represent numbers with a decimal point. 234 | 235 | Only base-10 notation is supported in floating points. 236 | That is, Hexadecimal and Binary floats are illegal and should 237 | raise a compiler error if a `.` is encountered. 238 | 239 | ### 1.8 - Character Literals 240 | 241 | ```antlr 242 | EscapeSequence: 243 | | \ b (backspace character, Unicode \u0008) 244 | | \ s (space character, Unicode \u0020) 245 | | \ h (horizontal tab, Unicode \u0009) 246 | | \ n (newline LF, Unicode \u000a) 247 | | \ f (form feed FF, Unicode \u000c) 248 | | \ r (carriage return CR, Unicode \u000d) 249 | | \ " (double quote ") 250 | | \ ' (single quote ') 251 | | \ \ (backslash \) 252 | | UnicodeEscape 253 | 254 | UnicodeEscape: \ u {HexadecimalDigit, 4} 255 | 256 | CharacterLiteralValue: EscapeSequence | Any Raw Unicode character 257 | 258 | CharacterLiteral: ' CharacterLiteralValue ' 259 | ``` 260 | 261 | It is a compile time error for a character literal to contain 262 | more than 1 `CharacterLiteralValue` in between the single quotes, 263 | or for a `LineTerminator` character to appear between the quotes. 264 | 265 | `UnicodeEscape` values may only represent UTF-16 code units. 266 | They are limited to `\u0000` to `\uffff`. 267 | 268 | Character literals are converted to values of the `Char` type. 269 | 270 | ### 1.9 - String Literals 271 | 272 | A String Literal consists of zero or more characters 273 | surrounded by double quotes (`"`). 274 | Special characters (such as newlines) must be represented 275 | as escapes, rather than their literal values. 276 | 277 | ```antlr 278 | StringCharacter: 279 | | Any Unicode Character except [", \] 280 | | EscapeSequence 281 | 282 | StringLiteral: " StringCharacter+ " 283 | ``` 284 | 285 | If any Line Terminator Character appears between the opening and closing `"` 286 | , an compiler error will be thrown. 287 | 288 | String literals are translated to values of the `[Char]` type (a list of `Char`s). 289 | 290 | ### 1.10 - Text Blocks 291 | 292 | A Text Block ("Multiline String") consists of zero or more characters 293 | surrounded by 3 double quotes (`"""`). Similar to String Literals, 294 | special characters must be represented with escape sequences. 295 | 296 | ```antlr 297 | TextBlockCharacter: 298 | | Any Unicode Character except \ 299 | | EscapeSequence 300 | 301 | TextBlock: " " " TextBlockCharacter+ " " " 302 | ``` 303 | 304 | The output of a Text Block should trim any consistent indentation 305 | at compile time, and replace raw `LineTerminator` characters with 306 | their corresponding escape sequences. For example, the following code: 307 | 308 | ```fsharp 309 | let message = """ 310 | hello 311 | world 312 | """ 313 | ``` 314 | 315 | should be translated to a normal String Literal equivalent to the following: `"\nhello\nworld\n"` 316 | 317 | #### 1.10.1 - Raw Text Blocks 318 | 319 | If the indentation trimming functionality of a standard Text Block 320 | is not desired, we can use a Raw Text Block. 321 | These are defined as zero or more characters directly preceeded by 322 | `!"""` and directly terminated by `"""!`. 323 | 324 | They behave identically to normal text blocks, 325 | except not attempting to trim any indentation. 326 | 327 | Adjusting the previous example to use a raw text block gives: 328 | 329 | ```fsharp 330 | let message = !""" 331 | hello 332 | world 333 | """! 334 | ``` 335 | 336 | Which translates to the string literal `"\n hello\n world\n"` 337 | 338 | ### 1.11 - Keywords 339 | 340 | Keywords are special sequences of characters that are allowed as a 341 | standard identifier. They should also generally be treated as separate tokens. 342 | 343 | The following sequences of characters are reserved as keywords 344 | and are not permitted for use as identifiers: 345 | 346 | - `let` 347 | - `def` 348 | - `type` 349 | - `in` 350 | - `where` 351 | - `class` 352 | - `instance` 353 | - `if` 354 | - `else` 355 | - `then` 356 | 357 | ## Chapter 2 - Parser and Grammar 358 | 359 | ### 2.1 - Bindings 360 | 361 | Bindings can be declared anywhere in a source file with the following syntax: 362 | 363 | ```fsharp 364 | let [identifier] = [value] 365 | ``` 366 | 367 | Bindings declared in this form are accessible from anywhere in the code 368 | that is after the binding declaration, and in the same, or a child scope. 369 | 370 | Bindings are **NOT** expressions, and so they cannot be recursively defined. 371 | 372 | #### 2.1.1 - Verbose Binding Syntax 373 | 374 | An alternative binding form is provided that **is** an expression: 375 | 376 | ```fsharp 377 | let [identifier] = [value] in [expression] 378 | ``` 379 | 380 | In this form, the binding is only present in the scope of `expression`. 381 | The entire construct evaluates to the result of `expression`. 382 | 383 | #### 2.1.2 - Binding Type Declarations 384 | 385 | Bindings can optionally declare an expected type of their value. 386 | 387 | This is done with a colon (`:`) after the `[identifier]` section 388 | followed by the expected type. 389 | 390 | For example: 391 | 392 | ```fsharp 393 | let [identifier]: [Type] = [value] 394 | ``` 395 | 396 | If `value` (or `expression`) does not evaluate to a value of type `Type`, 397 | a compiler error should be raised. 398 | 399 | ### 2.2 - Functions 400 | 401 | The syntax for defining functions is very similar to bindings, 402 | however function definitions also define the parameters of the function. 403 | 404 | ```fsharp 405 | let [identifier] [param1] [param2] [...] = [function_body] 406 | ``` 407 | 408 | where `param1`, `param2`, ... `paramN` are valid [Identifiers](#15---normal-identifiers) 409 | and `function_body` is any expression. 410 | 411 | For example: 412 | 413 | ```fsharp 414 | let f param1 param2 = param1 + param2 415 | ``` 416 | 417 | #### 2.2.1 - Multiple Expression Function Bodies 418 | 419 | Functions can also have multiple expressions in their body. 420 | This is denoted by a new line, and then indentation, so that every line of 421 | the body lines up with the function name. 422 | 423 | For example: 424 | 425 | ```fsharp 426 | let f param1 param2 = 427 | print param1 428 | print param2 429 | let double = param1 * 2 430 | double + param2 431 | ``` 432 | 433 | Every line in the body is evaluated, and the last line is returned. 434 | Therefore, a function body must end with an expression. 435 | 436 | ##### 2.2.1.1 - Semicolons in Multiple Expression Function Bodies 437 | 438 | Semicolons (`;`) can also be used as an alternative or supplement to line terminators: 439 | 440 | ```fsharp 441 | let f param1 param2 = 442 | print param1 ; print param2 443 | let double = param1 * 2 444 | double + param2 445 | ``` 446 | 447 | #### 2.2.2 - Def forms 448 | 449 | It is often useful to explicitly specify the type of a function, 450 | for drafting out implementations or for readability in future. 451 | 452 | Whilst the let binding colon syntax can be used for functions, it is often messy. 453 | For example: 454 | 455 | ```fsharp 456 | let add a b : Int -> Int -> Int = a + b 457 | ``` 458 | 459 | To avoid this issue, the type of the function can be declared on the line above 460 | its implementation. 461 | 462 | ```fsharp 463 | def [identifier] : [type] 464 | ``` 465 | 466 | For example: 467 | 468 | ```fsharp 469 | def add : Int -> Int -> Int 470 | let add a b = a + b 471 | ``` 472 | 473 | This form can also be used for standard bindings (i.e not functions): 474 | 475 | ```fsharp 476 | def pi : Double 477 | let pi = 3.141529 478 | ``` 479 | 480 | The def line must be directly above the let line 481 | 482 | #### 2.2.3 - Function Types 483 | 484 | In Elara, a function is a mapping from a single input value to a single output value. 485 | 486 | There are 2 types of functions, which are categorized based on their **purity** (for a function to be pure, it must have no _visible_ side effects) 487 | 488 | The purity of a function can always be inferred by the compiler based on its context. If a function _only_ calls other pure functions, it is itself pure. Otherwise, it is impure. 489 | 490 | ##### 2.2.3.1 - Pure Functions 491 | 492 | A pure function mapping a value of type `a` to a value of type `b` has the type `a -> b`. 493 | A pure function has no side effects, meaning a compiler is permitted to replace the implementation with an optimized version. For example the function may be automatically memoised or inlined for performance benefits. This functionality can be prevented with the annotation `@NoInline`. 494 | 495 | A pure function may only call other pure functions. Calling an impure function makes this function itself impure. 496 | 497 | ##### 2.2.3.2 - Impure Functions 498 | 499 | An impure function behaves similarly to a pure function, but is expected to perform side effects. An impure function mapping a value of type `a` to a value of type `b` has the type `a => b`. 500 | Because side effects are expected, compilers should be conservative when optimizing impure functions to avoid subtle changes in behavior. 501 | 502 | An impure function may call any other function, pure or impure. 503 | 504 | ### 2.2.4 - Function Application 505 | 506 | Calling functions is done with the form `function arg`, where `arg` is an expression and `function` is some pure or impure function 507 | 508 | This applies the function to the given argument and evaluates to some value whose type is the function's return type. 509 | 510 | Function application is left associative, so `f a b` is the same as `(f a) b`. 511 | 512 | #### 2.2.5 - The main function 513 | 514 | In order for an Elara program to compile to an executable, it must define a main function that serves as an entry point for the program. 515 | 516 | This function must have the type `() => ()` and be named `main`. 517 | 518 | If the main function is present in a project, the compiler must emit an executable. Similarly, if this function is not present, the compiler is not required to emit an executable (although it is not prohibited). 519 | 520 | If multiple files in a project have the main function, the ambiguity must be resolved by the user. The process of resolving this is undefined as it is left up to the compiler implementation. 521 | 522 | ### 2.4 - Lists 523 | 524 | Lists are written `[elem1, elem2, ..., elemN]`, where `N` is any integer `N >= 0`. 525 | 526 | Lists are homogeneous, meaning all elements in a list must share a single type `a`. 527 | The type of this list is written as `[a]` 528 | 529 | The **empty list** is written as `[]` and has type `[a]` where `a` is a generic type. 530 | This means that the empty list can be used anywhere irrespective of the type of list. 531 | 532 | The **cons operator** is written as `:` and is reserved for list construction. 533 | This operator takes an element `a` and a list `[a]` and prepends the element to the head of the list. 534 | For example, `3 : [4]` gives `[3, 4]`. 535 | 536 | The cons operator is right associative, so `3 : 4 : [5]` is the same as `3 : (4 : [5])` 537 | 538 | List literals are merely a syntax sugar for repeated application of the cons operator. 539 | For example, `[1, 2, 3]` is de-sugared to `1 : 2 : 3 : []`. 540 | 541 | ### 2.5 - Tuples 542 | 543 | Tuples are similar to lists, except heterogeneous and fixed-length. 544 | 545 | A tuple is written as `(elem1, elem2, ..., elemN)` where `N` is any integer `N >= 2`. 546 | 547 | The type of a tuple of length `N` is written as `(t1, t2, ..., tN)`. 548 | For example, the tuple `(3, "Hello")` has type `(Int, String)` 549 | 550 | The type constructor of a tuple of length `N` can be de-sugared to a parenthesized series of commas, where there are `N - 1` commas. 551 | For example, a tuple of length 2 has the type constructor `(,)`. Thus, `(Int, String)` is identical to `(,) Int String`. 552 | 553 | ### 2.6 - The Unit Value 554 | 555 | The unit value is written as `()` and has type `()`. 556 | It is the only value of type `()`. 557 | 558 | The unit value can be used to represent computations with no useful input or output, i.e side effects. 559 | 560 | A pure function `f` with the signature `a -> ()` must have by definition `let f _ = ()`, 561 | as no other pure implementations exist. 562 | 563 | *Note that this could be thought of as an empty tuple (length 0) if empty tuples were permitted* 564 | (see section 2.5). 565 | 566 | ### 2.7 - Generic Types 567 | 568 | Generic types are a type that can be any type. 569 | They are denoted with a lowercase identifier to distinguish them from normal types. 570 | All generic types are assumed to be universally quantified. 571 | For example, the type expression `a -> a` means `∀a. a -> a`, i.e it must hold for every possible type. A value of type `String -> String` is not sufficient. 572 | 573 | Once a generic type has been "realized", it must remain the same all throughout a type expression. 574 | For example, in the expression `a -> b -> (a, b)`, if `a` is inferred to `Int` then it must be `Int` everywhere in the expression. 575 | 576 | The realized type of a generic type can usually be inferred from usage by the compiler. 577 | If not, then they should be kept as generics. 578 | If the realized type can never be inferred, then a compiler error should be raised to prompt 579 | the user to resolve the ambiguity. 580 | 581 | ### 2.8 - Type Class Constraints 582 | 583 | Type classes are Elara's mechanism for polymorphism. 584 | Their semantics and functionality is described later in this specification (TODO link). 585 | 586 | A type expression can have place a **constraint** on some generic types to create a smaller set of possible types. 587 | This is written as `(Class1 i1, Class2 i2, ..., ClassN iN) := expr` 588 | where `N >= 1`, `iN` is a valid Generic Type identifier, `ClassN` is the identifier of a type class, 589 | and `expr` is some Type Expression. 590 | 591 | For example, to constrain a function `print : a => ()` to any type that is an instance of the `Show` class, we could write `(Show a) := a => ()`. 592 | 593 | ### 2.9 - Standard Types 594 | 595 | Standard types are the simplest form of type. 596 | These are simply some valid type identifier (see Section 1.5.1) 597 | that matches the name of any one of the following: 598 | 599 | - Alias Type 600 | - Algebraic Data Type 601 | - Record Type 602 | 603 | #### 2.9.1 - Alias Types 604 | 605 | Alias types are simply named aliases for another type. 606 | They can be used to add names to more complex type expressions. 607 | 608 | They are written as `type alias = subject` where `alias` is a valid Type Identifier, and 609 | `subject` is some valid Type Expression (see Section 2.10) 610 | 611 | #### 2.9.2 - Algebraic Data Type 612 | 613 | An algebraic data type represents a closed, distinct union of other types. 614 | 615 | ### 2.10 - Type Expressions 616 | 617 | A type expression is some combination of types and constraints that forms an expression that describes 618 | to a single type. 619 | 620 | Valid elements in a type expression include: 621 | 622 | - Lists (Section 2.4) 623 | - Tuples (Section 2.5) 624 | - The Unit Type (Section 2.6) 625 | - Generic Types (Section 2.7) 626 | - Type Class Constraints (Section 2.8) 627 | - Standard Types (Section 2.9) 628 | - Function Types (Section 2.2.3) 629 | 630 | A type expression must have at least 1 element in it, but can be arbitrarily sized otherwise. 631 | 632 | Examples of valid type expressions include: 633 | 634 | - `(Show a) := [a] => ()` 635 | - `(Int, String) -> (String, Int)` 636 | - `String` 637 | - `a -> a` 638 | - `a -> b -> Int -> [(a, b)]` 639 | - `a` 640 | -------------------------------------------------------------------------------- /elara.elr: -------------------------------------------------------------------------------- 1 | namespace test/lol 2 | import elara/std 3 | 4 | struct Person { 5 | String name 6 | Int age 7 | } 8 | 9 | let map = { 10 | Person("Dave", 16): 2, 11 | "Key 1": 19.4, 12 | [1, 2, 3]: 4 13 | } 14 | print(map) -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ElaraLang/elara 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20211011183812-e4d7f542a779 7 | github.com/dsnet/compress v0.0.1 // indirect 8 | github.com/frankban/quicktest v1.11.2 // indirect 9 | github.com/golang/snappy v0.0.2 // indirect 10 | github.com/mholt/archiver v3.1.1+incompatible 11 | github.com/nwaples/rardecode v1.1.0 // indirect 12 | github.com/pierrec/lz4 v2.6.0+incompatible // indirect 13 | github.com/ulikunitz/xz v0.5.8 // indirect 14 | github.com/urfave/cli/v2 v2.3.0 15 | github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /grammar/ElaraLexer.g4: -------------------------------------------------------------------------------- 1 | lexer grammar ElaraLexer; 2 | 3 | 4 | NewLine : ('\r' '\n' | '\n' | '\r') { 5 | this.processNewLine(); 6 | } ; 7 | 8 | Tab : [\t]+ { 9 | this.processTabToken(); 10 | } ; 11 | 12 | Semicolon : ';'; 13 | 14 | fragment Space: [ \t]; 15 | Whitespace : Space+ -> skip; 16 | 17 | InlineComment : '//' ~[\r\n]* -> skip; 18 | MultiComment : '/*' .+? '*/' -> skip; 19 | 20 | fragment NonZeroDigit : '1'..'9'; 21 | fragment Digit: '0' | NonZeroDigit; 22 | fragment HexDigit: [0-9A-F]; 23 | fragment ScientificNotation: 'E' [+-]; 24 | 25 | // Literals 26 | fragment AbsoluteIntegerLiteral: NonZeroDigit (Digit+ (ScientificNotation Digit+))?; 27 | IntegerLiteral : '-'? AbsoluteIntegerLiteral; 28 | FloatLiteral: IntegerLiteral '.' AbsoluteIntegerLiteral ; 29 | 30 | CharLiteral : '\'' (. | '\\' .) '\''; 31 | StringLiteral : '"' (~'"' | '\\"')+ '"'; 32 | 33 | // Keywords 34 | Let : 'let'; 35 | Def : 'def'; 36 | Mut: 'mut'; 37 | Type: 'type'; 38 | Class: 'class'; 39 | 40 | // Symbols 41 | Comma: ','; 42 | LParen: '('; 43 | RParen: ')'; 44 | LSquareParen: '['; 45 | RSquareParen: ']'; 46 | LBrace: '{'; 47 | RBrace: '}'; 48 | Colon: ':'; 49 | Dot: '.'; 50 | PureArrow: '->'; 51 | ImpureArrow: '=>'; 52 | Equals : '='; 53 | Bar : '|'; 54 | 55 | // Identifiers 56 | TypeIdentifier: [A-Z][a-zA-Z_0-9]*; 57 | VarIdentifier: [a-z][a-zA-Z_0-9]*; 58 | OperatorIdentifier: ('!' | '#' | '$' | '%' | '+' | '-' | '/' | '*' | '.' | '<' | '>' | '=' | '?' | '@' | '~' | '\\' | '^' | '|')+; 59 | 60 | -------------------------------------------------------------------------------- /grammar/ElaraParser.g4: -------------------------------------------------------------------------------- 1 | parser grammar ElaraParser; 2 | 3 | options { tokenVocab=ElaraLexer; } 4 | 5 | defClause : Def VarIdentifier Colon type; 6 | letClause : Let VarIdentifier Equals expression; 7 | variable : defClause? letClause; 8 | 9 | unit : LParen RParen; 10 | 11 | // Types 12 | type : unit #UnitType 13 | | VarIdentifier #GenericType 14 | | typeName typeName+ #RealizedGenericType 15 | | type PureArrow type # PureFunctionType 16 | | type ImpureArrow type #ImpureFunctionType 17 | | LParen type RParen #ParenthesizedType 18 | | LParen (type (Comma type)+) RParen #TupleType 19 | | LSquareParen type RSquareParen # ListType 20 | | TypeIdentifier #SimpleType 21 | | Mut type #MutType; 22 | 23 | typeName : 24 | TypeIdentifier #NormalTypeName 25 | | VarIdentifier #GenericTypeName; 26 | 27 | typeAlias : type; 28 | typeConstructor : 29 | TypeIdentifier type* #NormalTypeConstructor 30 | | TypeIdentifier recordType #RecordTypeConstructor; 31 | 32 | sumType : typeConstructor (Bar typeConstructor)*; 33 | recordTypeField : VarIdentifier Colon type; 34 | recordType : LBrace recordTypeField (Comma recordTypeField)* RBrace; 35 | 36 | typeDeclaration : Type TypeIdentifier VarIdentifier* Equals typeDeclarationValue; 37 | typeDeclarationValue : typeAlias | sumType | recordType; 38 | 39 | // Expressions 40 | 41 | expression : 42 | unit #UnitExpression 43 | | IntegerLiteral #IntExpression 44 | | FloatLiteral #FloatExpression 45 | | CharLiteral #CharExpression 46 | | StringLiteral #StringExpression 47 | | LSquareParen (expression (Comma expression)*)? RSquareParen #ListExpression 48 | | LParen (expression (Comma expression)+) RParen #TupleExpression 49 | ; -------------------------------------------------------------------------------- /interpreter/built-in-types.go: -------------------------------------------------------------------------------- 1 | package interpreter 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ElaraLang/elara/util" 6 | ) 7 | 8 | var AnyType = NewEmptyType("Any") 9 | var UnitType = NewEmptyType("Unit") 10 | 11 | var FloatType = NewEmptyType("Float") 12 | var BooleanType = NewEmptyType("Boolean") 13 | 14 | var CharType = NewEmptyType("Char") 15 | 16 | var StringType = NewCollectionTypeOf(CharType) 17 | var OutputType = NewEmptyType("Output") 18 | 19 | var types = []Type{ 20 | AnyType, 21 | UnitType, 22 | IntType, 23 | FloatType, 24 | BooleanType, 25 | StringType, 26 | CharType, 27 | OutputType, 28 | } 29 | 30 | func Init(context *Context) { 31 | for _, t := range types { 32 | context.types[t.Name()] = t 33 | } 34 | context.types["String"] = StringType 35 | 36 | InitInts(context) 37 | 38 | stringPlusName := "plus" 39 | stringPlus := &Function{ 40 | Signature: Signature{ 41 | Parameters: []Parameter{ 42 | { 43 | Name: "this", 44 | Type: StringType, 45 | Position: 0, 46 | }, 47 | { 48 | Name: "other", 49 | Type: AnyType, 50 | Position: 1, 51 | }}, 52 | ReturnType: StringType, 53 | }, 54 | Body: NewAbstractCommand(func(ctx *Context) *ReturnedValue { 55 | this := ctx.FindParameter(0) 56 | otherParam := ctx.FindParameter(1) 57 | concatenated := this.Value.(*Collection).elemsAsString() + util.Stringify(otherParam.Value) 58 | return NonReturningValue(&Value{ 59 | Type: StringType, 60 | Value: concatenated, 61 | }) 62 | }), 63 | name: &stringPlusName, 64 | } 65 | stringPlusType := NewFunctionType(stringPlus) 66 | context.DefineVariable(&Variable{ 67 | Name: stringPlusName, 68 | Mutable: false, 69 | Type: stringPlusType, 70 | Value: &Value{ 71 | Type: stringPlusType, 72 | Value: stringPlus, 73 | }, 74 | }) 75 | 76 | anyPlusName := "plus" 77 | anyPlus := &Function{ 78 | Signature: Signature{ 79 | Parameters: []Parameter{ 80 | { 81 | Name: "this", 82 | Type: AnyType, 83 | }, 84 | { 85 | Name: "other", 86 | Type: StringType, 87 | Position: 1, 88 | }}, 89 | ReturnType: StringType, 90 | }, 91 | Body: NewAbstractCommand(func(ctx *Context) *ReturnedValue { 92 | this := ctx.FindParameter(0) 93 | otherParam := ctx.FindParameter(1) 94 | 95 | concatenated := ctx.Stringify(this) + otherParam.Value.(*Collection).elemsAsString() 96 | return NonReturningValue(&Value{ 97 | Type: StringType, 98 | Value: concatenated, 99 | }) 100 | }), 101 | name: &anyPlusName, 102 | } 103 | anyPlusType := NewFunctionType(anyPlus) 104 | context.DefineVariable(&Variable{ 105 | Name: anyPlusName, 106 | Mutable: false, 107 | Type: anyPlusType, 108 | Value: &Value{ 109 | Type: anyPlusType, 110 | Value: anyPlus, 111 | }, 112 | }) 113 | 114 | define(context, "toString", &Function{ 115 | Signature: Signature{ 116 | Parameters: []Parameter{ 117 | { 118 | Name: "this", 119 | Type: AnyType, 120 | }, 121 | }, 122 | ReturnType: StringType, 123 | }, 124 | Body: NewAbstractCommand(func(ctx *Context) *ReturnedValue { 125 | this := ctx.FindParameter(0) 126 | return NonReturningValue(StringValue(ctx.Stringify(this))) 127 | }), 128 | }) 129 | 130 | colPlusName := "plus" 131 | colPlus := &Function{ 132 | Signature: Signature{ 133 | Parameters: []Parameter{ 134 | { 135 | Name: "this", 136 | Type: NewCollectionTypeOf(AnyType), 137 | }, 138 | { 139 | Name: "other", 140 | Type: NewCollectionTypeOf(AnyType), 141 | Position: 1, 142 | }, 143 | }, 144 | ReturnType: NewCollectionTypeOf(AnyType), 145 | }, 146 | Body: NewAbstractCommand(func(ctx *Context) *ReturnedValue { 147 | this := ctx.FindParameter(0).Value.(*Collection) 148 | other := ctx.FindParameter(1).Value.(*Collection) 149 | 150 | elems := make([]*Value, len(this.Elements)+len(other.Elements)) 151 | for i, element := range this.Elements { 152 | elems[i] = element 153 | } 154 | for i, element := range other.Elements { 155 | elems[i+len(this.Elements)] = element 156 | } 157 | 158 | newCol := &Collection{ 159 | ElementType: this.ElementType, 160 | Elements: elems, 161 | } 162 | return NonReturningValue(&Value{ 163 | Type: NewCollectionType(newCol), 164 | Value: newCol, 165 | }) 166 | }), 167 | name: &colPlusName, 168 | } 169 | colPlusType := NewFunctionType(colPlus) 170 | context.DefineVariable(&Variable{ 171 | Name: colPlusName, 172 | Mutable: false, 173 | Type: colPlusType, 174 | Value: &Value{ 175 | Type: colPlusType, 176 | Value: colPlus, 177 | }, 178 | }) 179 | 180 | define(context, "times", &Function{ 181 | Signature: Signature{ 182 | Parameters: []Parameter{ 183 | { 184 | Name: "this", 185 | Type: NewCollectionTypeOf(AnyType), 186 | }, 187 | { 188 | Name: "other", 189 | Type: IntType, 190 | Position: 1, 191 | }, 192 | }, 193 | ReturnType: NewCollectionTypeOf(AnyType), 194 | }, 195 | Body: NewAbstractCommand(func(ctx *Context) *ReturnedValue { 196 | thisParam := ctx.FindParameter(0) 197 | this := thisParam.Value.(*Collection) 198 | amount := ctx.FindParameter(1).Value.(int64) 199 | if amount == 1 { 200 | return NonReturningValue(thisParam) 201 | } 202 | 203 | currentElemLength := len(this.Elements) 204 | newSize := int64(currentElemLength) * amount 205 | newColl := make([]*Value, newSize) 206 | for i := int64(0); i < newSize; i++ { 207 | index := i % amount 208 | if currentElemLength == 1 { 209 | index = 0 210 | } 211 | newColl[i] = this.Elements[index] 212 | } 213 | 214 | collection := &Collection{ 215 | ElementType: this.ElementType, 216 | Elements: newColl, 217 | } 218 | return NonReturningValue(NewValue(NewCollectionType(collection), collection)) 219 | }), 220 | }) 221 | 222 | outputWriteName := "write" 223 | outputWrite := &Function{ 224 | Signature: Signature{ 225 | Parameters: []Parameter{ 226 | { 227 | Name: "this", 228 | Type: OutputType, 229 | }, 230 | { 231 | Name: "value", 232 | Type: AnyType, 233 | Position: 1, 234 | }, 235 | }, 236 | ReturnType: UnitType, 237 | }, 238 | Body: NewAbstractCommand(func(ctx *Context) *ReturnedValue { 239 | value := ctx.FindParameter(1) 240 | asString := ctx.Stringify(value) 241 | 242 | fmt.Printf("%s", asString) 243 | return NonReturningValue(UnitValue()) 244 | }), 245 | name: &outputWriteName, 246 | } 247 | outputWriteType := NewFunctionType(stringPlus) 248 | context.DefineVariable(&Variable{ 249 | Name: outputWriteName, 250 | Mutable: false, 251 | Type: outputWriteType, 252 | Value: &Value{ 253 | Type: outputWriteType, 254 | Value: outputWrite, 255 | }, 256 | }) 257 | 258 | anyEqualsName := "equals" 259 | anyEquals := &Function{ 260 | Signature: Signature{ 261 | Parameters: []Parameter{ 262 | { 263 | Name: "this", 264 | Type: AnyType, 265 | }, 266 | { 267 | Name: "other", 268 | Type: AnyType, 269 | Position: 1, 270 | }, 271 | }, 272 | ReturnType: BooleanType, 273 | }, 274 | name: &anyEqualsName, 275 | Body: NewAbstractCommand(func(c *Context) *ReturnedValue { 276 | this := c.FindParameter(0).Value 277 | other := c.FindParameter(1) 278 | value := false 279 | switch a := this.(type) { 280 | case *Collection: 281 | value = a.Equals(c, other) 282 | case *Instance: 283 | value = a.Equals(c, other) 284 | case int64: 285 | asI64, isI64 := other.Value.(int64) 286 | if isI64 && a == asI64 { 287 | value = true 288 | } 289 | case float64: 290 | asF64, isF64 := other.Value.(float64) 291 | if isF64 && a == asF64 { 292 | value = true 293 | } 294 | } 295 | if value == false { 296 | value = this == other 297 | } 298 | return NonReturningValue(BooleanValue(value)) 299 | }), 300 | } 301 | anyEqualsType := NewFunctionType(anyEquals) 302 | context.DefineVariable(&Variable{ 303 | Name: anyEqualsName, 304 | Mutable: false, 305 | Type: anyEqualsType, 306 | Value: &Value{ 307 | Type: anyEqualsType, 308 | Value: anyEquals, 309 | }, 310 | }) 311 | } 312 | 313 | //func intAdd(ctx *Context) *Value { 314 | // parameter := ctx.FindParameter("value") 315 | // asInt, isInt := parameter.Value.(int64) 316 | // if isInt { 317 | // result := ctx.receiver.Value.(int64) + asInt 318 | // return &Value{ 319 | // Type: IntType, 320 | // Value: result, 321 | // } 322 | // } else { 323 | // asFloat, isFloat := parameter.Value.(float64) 324 | // if isFloat { 325 | // result := float64(ctx.receiver.Value.(int64)) + asFloat 326 | // return &Value{ 327 | // Type: FloatType, 328 | // Value: result, 329 | // } 330 | // } else { 331 | // //TODO 332 | // //While this might work, it ignores the fact that values won't be "cast" if passed. An Int passed as Any will still try and use Int functions 333 | // result := util.Stringify(ctx.receiver.Value) + util.Stringify(parameter.Value) 334 | // return &Value{ 335 | // Type: StringType, 336 | // Value: result, 337 | // } 338 | // } 339 | // } 340 | //} 341 | -------------------------------------------------------------------------------- /interpreter/collections.go: -------------------------------------------------------------------------------- 1 | package interpreter 2 | 3 | import "strings" 4 | 5 | //In a proper implementation these would be persistent. But for now, they will do 6 | type Collection struct { 7 | ElementType Type 8 | Elements []*Value 9 | 10 | cachedAsString *string 11 | } 12 | 13 | type CollectionType struct { 14 | ElementType Type 15 | } 16 | 17 | func (t *CollectionType) Name() string { 18 | return "[" + t.ElementType.Name() + "]" //Eg [String] 19 | } 20 | 21 | func (t *CollectionType) Accepts(otherType Type, ctx *Context) bool { 22 | otherColl, ok := otherType.(*CollectionType) 23 | if !ok { 24 | return false 25 | } 26 | 27 | return t.ElementType.Accepts(otherColl.ElementType, ctx) 28 | } 29 | 30 | func NewCollectionType(collection *Collection) Type { 31 | return &CollectionType{ 32 | collection.ElementType, 33 | } 34 | } 35 | func NewCollectionTypeOf(elemType Type) Type { 36 | return &CollectionType{ 37 | elemType, 38 | } 39 | } 40 | 41 | func (t *Collection) String() string { 42 | if t.ElementType == CharType { 43 | return t.elemsAsString() 44 | } 45 | elemStrings := make([]string, len(t.Elements)) 46 | for i, element := range t.Elements { 47 | elemStrings[i] = element.String() 48 | } 49 | return "[" + strings.Join(elemStrings, ", ") + "]" 50 | } 51 | 52 | func (t *Collection) elemsAsString() string { 53 | if t.cachedAsString != nil { 54 | return *t.cachedAsString 55 | } 56 | 57 | if t.ElementType != CharType { 58 | panic("Cannot convert collection to string") 59 | } 60 | builder := strings.Builder{} 61 | for _, elem := range t.Elements { 62 | builder.WriteRune(elem.Value.(rune)) 63 | } 64 | 65 | asString := builder.String() 66 | t.cachedAsString = &asString 67 | return asString 68 | } 69 | 70 | func (t *Collection) Equals(ctx *Context, other *Value) bool { 71 | otherAsCol, otherIsCol := other.Value.(*Collection) 72 | if !otherIsCol { 73 | return false 74 | } 75 | if len(t.Elements) != len(otherAsCol.Elements) { 76 | return false 77 | } 78 | for i, element := range t.Elements { 79 | otherElem := otherAsCol.Elements[i] 80 | if !element.Equals(ctx, otherElem) { 81 | break 82 | } 83 | } 84 | return true 85 | } 86 | -------------------------------------------------------------------------------- /interpreter/command.go: -------------------------------------------------------------------------------- 1 | package interpreter 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ElaraLang/elara/lexer" 6 | "github.com/ElaraLang/elara/parser" 7 | "github.com/ElaraLang/elara/util" 8 | _ "github.com/ElaraLang/elara/util" 9 | "reflect" 10 | "strings" 11 | ) 12 | 13 | type Command interface { 14 | Exec(ctx *Context) *ReturnedValue 15 | } 16 | 17 | type DefineVarCommand struct { 18 | Name string 19 | Mutable bool 20 | Type parser.Type 21 | value Command 22 | runtimeType Type 23 | 24 | hashedName uint64 25 | } 26 | 27 | func (c *DefineVarCommand) getType(ctx *Context) Type { 28 | if c.runtimeType == nil { 29 | if c.Type == nil { 30 | return nil 31 | } 32 | c.runtimeType = FromASTType(c.Type, ctx) 33 | } 34 | return c.runtimeType 35 | } 36 | 37 | func (c *DefineVarCommand) Exec(ctx *Context) *ReturnedValue { 38 | if c.hashedName == 0 { 39 | c.hashedName = util.Hash(c.Name) 40 | } 41 | var value *Value 42 | foundVar, _ := ctx.FindVariableMaxDepth(c.hashedName, 1) 43 | if foundVar != nil { 44 | asFunction, isFunction := foundVar.Value.Value.(*Function) 45 | if isFunction { 46 | value = c.value.Exec(ctx).Unwrap() 47 | valueAsFunction, valueIsFunction := value.Value.(*Function) 48 | if valueIsFunction && !asFunction.Signature.Accepts(&valueAsFunction.Signature, ctx, false) { 49 | //We'll allow it because the functions have different arity 50 | } else { 51 | //panic("Variable named " + c.Name + " already exists with the current signature") //TODO this might need to come back, maybe. 52 | } 53 | } else { 54 | panic("Variable named " + c.Name + " already exists") 55 | } 56 | } 57 | if value == nil { 58 | value = c.value.Exec(ctx).Unwrap() 59 | } 60 | 61 | if value == nil { 62 | panic("Command " + reflect.TypeOf(c.value).String() + " returned nil") 63 | } 64 | 65 | variableType := c.getType(ctx) 66 | if variableType != nil { 67 | if !variableType.Accepts(value.Type, ctx) { 68 | panic("Cannot use value of type " + value.Type.Name() + " in place of " + variableType.Name() + " for variable " + c.Name) 69 | } 70 | } else { 71 | variableType = value.Type 72 | } 73 | variable := &Variable{ 74 | Name: c.Name, 75 | Mutable: c.Mutable, 76 | Type: variableType, 77 | Value: value, 78 | } 79 | 80 | ctx.DefineVariable(variable) 81 | return NilValue() 82 | } 83 | 84 | type AssignmentCommand struct { 85 | Name string 86 | value Command 87 | 88 | hashedName uint64 89 | } 90 | 91 | func (c *AssignmentCommand) Exec(ctx *Context) *ReturnedValue { 92 | if c.hashedName == 0 { 93 | c.hashedName = util.Hash(c.Name) 94 | } 95 | variable := ctx.FindVariable(c.hashedName) 96 | if variable == nil { 97 | panic("No such variable " + c.Name) 98 | } 99 | 100 | if !variable.Mutable { 101 | panic("Cannot reassign immutable variable " + c.Name) 102 | } 103 | 104 | value := c.value.Exec(ctx).Unwrap() 105 | 106 | if !variable.Type.Accepts(value.Type, ctx) { 107 | panic("Cannot reassign variable " + c.Name + " of type " + variable.Type.Name() + " to value " + value.String() + " of type " + value.Type.Name()) 108 | } 109 | 110 | variable.Value = value 111 | return NonReturningValue(value) 112 | } 113 | 114 | type VariableCommand struct { 115 | Variable string 116 | 117 | hash uint64 118 | cachedVar *Value 119 | } 120 | 121 | func (c *VariableCommand) findVariable(ctx *Context) *Variable { 122 | if c.hash == 0 { 123 | c.hash = util.Hash(c.Variable) 124 | } 125 | 126 | variable := ctx.FindVariable(c.hash) 127 | return variable 128 | } 129 | 130 | func (c *VariableCommand) Exec(ctx *Context) *ReturnedValue { 131 | if c.cachedVar != nil { 132 | return NonReturningValue(c.cachedVar) 133 | } 134 | paramIndex := -1 135 | fun := ctx.function 136 | if fun != nil { 137 | for i, parameter := range fun.Signature.Parameters { 138 | if parameter.Name == c.Variable { 139 | paramIndex = i 140 | break 141 | } 142 | } 143 | } 144 | if paramIndex != -1 { 145 | param := ctx.FindParameter(uint(paramIndex)) 146 | if param != nil { 147 | return NonReturningValue(param) 148 | } 149 | } 150 | variable := c.findVariable(ctx) 151 | if variable != nil { 152 | return NonReturningValue(variable.Value) 153 | } 154 | 155 | constructor := ctx.FindConstructor(c.Variable) 156 | if constructor == nil { 157 | if ctx.function != nil && ctx.function.context != nil { 158 | return c.Exec(ctx.function.context) 159 | } 160 | panic("No such variable or parameter or constructor " + c.Variable) 161 | } 162 | c.cachedVar = constructor 163 | return NonReturningValue(constructor) 164 | } 165 | 166 | type InvocationCommand struct { 167 | Invoking Command 168 | args []Command 169 | 170 | cachedFun *Function 171 | } 172 | 173 | func (c *InvocationCommand) findReceiverFunction(ctx *Context, receiver *Value, argValues []*Value, functionName string, nameHash uint64) *Function { 174 | receiverType := receiver.Type 175 | parameters := []Parameter{{ 176 | Name: "this", 177 | Type: receiverType, 178 | }} 179 | for i, value := range argValues { 180 | parameters = append(parameters, Parameter{ 181 | Name: fmt.Sprintf("", i), 182 | Type: value.Type, 183 | }) 184 | } 185 | receiverSignature := &Signature{ 186 | Parameters: parameters, 187 | ReturnType: AnyType, //can't infer this rn 188 | } 189 | 190 | receiverFunction := ctx.FindFunction(nameHash, receiverSignature) 191 | if receiverFunction == nil { 192 | paramTypes := make([]string, 0) 193 | for _, value := range argValues { 194 | paramTypes = append(paramTypes, value.Type.Name()) 195 | } 196 | panic("Unknown function " + receiverType.Name() + "::" + functionName + "(" + strings.Join(paramTypes, ",") + ")") 197 | } 198 | 199 | return receiverFunction 200 | } 201 | func (c *InvocationCommand) Exec(ctx *Context) *ReturnedValue { 202 | context, usingReceiver := c.Invoking.(*ContextCommand) 203 | 204 | argValues := make([]*Value, len(c.args)) 205 | for i, arg := range c.args { 206 | argValues[i] = arg.Exec(ctx).UnwrapNotNil() 207 | } 208 | 209 | if !usingReceiver { 210 | if c.cachedFun != nil { 211 | return NonReturningValue(c.cachedFun.Exec(ctx, argValues)) //Avoid unnecessary lookup 212 | } 213 | val := c.Invoking.Exec(ctx).Unwrap() 214 | fun, ok := val.Value.(*Function) 215 | if !ok { 216 | panic("Cannot invoke value that isn't a function ") 217 | } 218 | switch t := c.Invoking.(type) { 219 | case *VariableCommand: 220 | variable := t.findVariable(ctx) 221 | if variable != nil && !variable.Mutable { 222 | c.cachedFun = fun 223 | } 224 | } 225 | 226 | return NonReturningValue(fun.Exec(ctx, argValues)) 227 | } 228 | 229 | //ContextCommand seems to think it's a special case... because it is. 230 | var receiver *Value 231 | 232 | functionName := context.variable 233 | 234 | receiver = context.receiver.Exec(ctx).Unwrap() 235 | 236 | if c.cachedFun != nil { 237 | argValuesAndSelf := []*Value{receiver} 238 | argValuesAndSelf = append(argValuesAndSelf, argValues...) 239 | return NonReturningValue(c.cachedFun.Exec(ctx, argValuesAndSelf)) 240 | } 241 | 242 | structType, isStruct := receiver.Type.(*StructType) 243 | 244 | if isStruct { 245 | value, ok := structType.GetProperty(functionName) 246 | if ok { 247 | function, ok := value.DefaultValue.Value.(*Function) 248 | if !ok { 249 | panic("Cannot invoke non-function " + value.Name) 250 | } 251 | this := context.receiver.Exec(ctx).Unwrap() 252 | 253 | argValues := make([]*Value, len(c.args)) 254 | argValues = append(argValues, this) 255 | for i, arg := range c.args { 256 | argValues[i] = arg.Exec(ctx).Unwrap() 257 | } 258 | return NonReturningValue(function.Exec(ctx, argValues)) 259 | } 260 | } 261 | 262 | extension := ctx.FindExtension(receiver.Type, functionName) 263 | if extension != nil { 264 | fun := extension.Value.Value.Value.(*Function) 265 | c.cachedFun = fun 266 | argValuesAndSelf := []*Value{receiver} 267 | argValuesAndSelf = append(argValuesAndSelf, argValues...) 268 | return NonReturningValue(fun.Exec(ctx, argValuesAndSelf)) 269 | } 270 | 271 | //Look for a receiver 272 | receiverFunction := c.findReceiverFunction(ctx, receiver, argValues, functionName, context.hash()) 273 | argValuesAndSelf := []*Value{receiver} 274 | argValuesAndSelf = append(argValuesAndSelf, argValues...) 275 | return NonReturningValue(receiverFunction.Exec(ctx, argValuesAndSelf)) 276 | } 277 | 278 | type AbstractCommand struct { 279 | content func(ctx *Context) *ReturnedValue 280 | } 281 | 282 | func (c *AbstractCommand) Exec(ctx *Context) *ReturnedValue { 283 | return c.content(ctx) 284 | } 285 | 286 | func NewAbstractCommand(content func(ctx *Context) *ReturnedValue) *AbstractCommand { 287 | return &AbstractCommand{ 288 | content: content, 289 | } 290 | } 291 | 292 | type LiteralCommand struct { 293 | value *Value 294 | } 295 | 296 | func (c *LiteralCommand) Exec(_ *Context) *ReturnedValue { 297 | return NonReturningValue(c.value) 298 | } 299 | 300 | type FunctionLiteralCommand struct { 301 | name *string 302 | parameters []parser.FunctionArgument 303 | returnType parser.Type //Can be nil - infer return type 304 | body Command 305 | 306 | currentContext *Context 307 | } 308 | 309 | func (c *FunctionLiteralCommand) Exec(ctx *Context) *ReturnedValue { 310 | if c.currentContext == nil { 311 | c.currentContext = ctx.Clone() 312 | //Function literals take a snapshot of their current context to avoid scoping issues 313 | //This one will be cached forever, so we don't need to cleanup 314 | } 315 | params := make([]Parameter, len(c.parameters)) 316 | 317 | for i, parameter := range c.parameters { 318 | paramType := FromASTType(parameter.Type, c.currentContext) 319 | params[i] = Parameter{ 320 | Type: paramType, 321 | Name: parameter.Name, 322 | Position: uint(i), 323 | } 324 | } 325 | 326 | astReturnType := c.returnType 327 | var returnType Type 328 | if astReturnType == nil { 329 | returnType = AnyType 330 | } else { 331 | returnType = FromASTType(c.returnType, c.currentContext) 332 | } 333 | 334 | fun := &Function{ 335 | name: c.name, 336 | Signature: Signature{ 337 | Parameters: params, 338 | ReturnType: returnType, 339 | }, 340 | Body: c.body, 341 | context: c.currentContext, 342 | } 343 | 344 | functionType := NewFunctionType(fun) 345 | 346 | return NonReturningValue(&Value{ 347 | Type: functionType, 348 | Value: fun, 349 | }) 350 | } 351 | 352 | type BinaryOperatorCommand struct { 353 | lhs Command 354 | op func(ctx *Context, lhs *Value, rhs *Value) *ReturnedValue 355 | rhs Command 356 | } 357 | 358 | func (c *BinaryOperatorCommand) Exec(ctx *Context) *ReturnedValue { 359 | lhs := c.lhs.Exec(ctx).Unwrap() 360 | rhs := c.rhs.Exec(ctx).Unwrap() 361 | 362 | return c.op(ctx, lhs, rhs) 363 | } 364 | 365 | type BlockCommand struct { 366 | lines []*Command 367 | } 368 | 369 | func (c *BlockCommand) Exec(ctx *Context) *ReturnedValue { 370 | var last = NonReturningValue(UnitValue()) 371 | for _, lineRef := range c.lines { 372 | line := *lineRef 373 | val := line.Exec(ctx) 374 | if val.IsReturning { 375 | return val 376 | } 377 | last = val 378 | } 379 | return last 380 | } 381 | 382 | type ContextCommand struct { 383 | receiver Command 384 | variable string 385 | hashedVariable uint64 386 | } 387 | 388 | func (c *ContextCommand) hash() uint64 { 389 | if c.hashedVariable == 0 { 390 | c.hashedVariable = util.Hash(c.variable) 391 | } 392 | return c.hashedVariable 393 | } 394 | func (c *ContextCommand) Exec(ctx *Context) *ReturnedValue { 395 | if c.hashedVariable == 0 { 396 | c.hashedVariable = util.Hash(c.variable) 397 | } 398 | receiver := c.receiver.Exec(ctx).Unwrap() 399 | 400 | var value *ReturnedValue 401 | switch val := receiver.Value.(type) { 402 | case *Collection: 403 | switch c.variable { 404 | case "size": 405 | value = NonReturningValue(IntValue(int64(len(val.Elements)))) 406 | } 407 | case *Map: 408 | switch c.variable { 409 | case "keys": 410 | keySet := make([]*Value, len(val.Elements)) 411 | for i, element := range val.Elements { 412 | keySet[i] = element.Key 413 | } 414 | collection := &Collection{ 415 | ElementType: val.MapType.KeyType, 416 | Elements: keySet, 417 | } 418 | collectionType := NewCollectionType(collection) 419 | 420 | value = NonReturningValue(&Value{ 421 | Type: collectionType, 422 | Value: collection, 423 | }) 424 | case "values": 425 | valueSet := make([]*Value, len(val.Elements)) 426 | for i, element := range val.Elements { 427 | valueSet[i] = element.Value 428 | } 429 | collection := &Collection{ 430 | ElementType: val.MapType.ValueType, 431 | Elements: valueSet, 432 | } 433 | collectionType := NewCollectionType(collection) 434 | 435 | value = NonReturningValue(&Value{ 436 | Type: collectionType, 437 | Value: collection, 438 | }) 439 | } 440 | case *Instance: 441 | { 442 | value = NonReturningValue(val.Values[c.variable]) 443 | } 444 | default: 445 | panic("Unsupported receiver " + util.Stringify(receiver)) 446 | } 447 | if value != nil && value.Value != nil { 448 | return value 449 | } 450 | 451 | //Search for an extension 452 | extension := ctx.FindExtension(receiver.Type, c.variable) 453 | if extension == nil { 454 | panic("Unknown property or extension for " + receiver.String() + " with name " + c.variable) 455 | } 456 | return NonReturningValue(extension.Value.Value) 457 | } 458 | 459 | type IfElseCommand struct { 460 | condition Command 461 | ifBranch Command 462 | elseBranch Command 463 | } 464 | 465 | func (c *IfElseCommand) Exec(ctx *Context) *ReturnedValue { 466 | condition := c.condition.Exec(ctx) 467 | value, ok := condition.Unwrap().Value.(bool) 468 | if !ok { 469 | panic("If statements requires boolean value") 470 | } 471 | 472 | if value { 473 | return c.ifBranch.Exec(ctx) 474 | } else if c.elseBranch != nil { 475 | return c.elseBranch.Exec(ctx) 476 | } else { 477 | return NilValue() 478 | } 479 | } 480 | 481 | type IfElseExpressionCommand struct { 482 | condition Command 483 | ifBranch []Command 484 | ifResult Command 485 | elseBranch []Command 486 | elseResult Command 487 | } 488 | 489 | func (c *IfElseExpressionCommand) Exec(ctx *Context) *ReturnedValue { 490 | condition := c.condition.Exec(ctx) 491 | value, ok := condition.Unwrap().Value.(bool) 492 | if !ok { 493 | panic("If statements requires boolean value") 494 | } 495 | 496 | if value { 497 | if c.ifBranch != nil { 498 | for _, cmd := range c.ifBranch { 499 | cmd.Exec(ctx) 500 | } 501 | } 502 | return c.ifResult.Exec(ctx) 503 | } else { 504 | if c.elseBranch != nil { 505 | for _, cmd := range c.elseBranch { 506 | cmd.Exec(ctx) 507 | } 508 | } 509 | return c.elseResult.Exec(ctx) 510 | } 511 | } 512 | 513 | type ReturnCommand struct { 514 | returning Command 515 | } 516 | 517 | func (c *ReturnCommand) Exec(ctx *Context) *ReturnedValue { 518 | if c.returning == nil { 519 | return ReturningValue(UnitValue()) 520 | } 521 | return ReturningValue(c.returning.Exec(ctx).Unwrap()) 522 | } 523 | 524 | type NamespaceCommand struct { 525 | namespace string 526 | } 527 | 528 | func (c *NamespaceCommand) Exec(ctx *Context) *ReturnedValue { 529 | ctx.Init(c.namespace) 530 | return NilValue() 531 | } 532 | 533 | type ImportCommand struct { 534 | imports []string 535 | } 536 | 537 | func (c *ImportCommand) Exec(ctx *Context) *ReturnedValue { 538 | for _, s := range c.imports { 539 | ctx.Import(s) 540 | } 541 | return NilValue() 542 | } 543 | 544 | type StructDefCommand struct { 545 | name string 546 | fields []parser.StructField 547 | } 548 | 549 | func (c *StructDefCommand) Exec(ctx *Context) *ReturnedValue { 550 | 551 | properties := make([]Property, len(c.fields)) 552 | propertyPositions := map[string]int{} 553 | 554 | for i, field := range c.fields { 555 | var Type Type 556 | if field.FieldType == nil { 557 | Type = AnyType 558 | } else { 559 | Type = FromASTType(*field.FieldType, ctx) 560 | } 561 | 562 | var defaultValue *Value 563 | if field.Default != nil { 564 | defaultValue = ExpressionToCommand(field.Default).Exec(ctx).Unwrap() 565 | } 566 | 567 | modifiers := uint(0) 568 | if field.Mutable { 569 | modifiers |= Mut 570 | } 571 | properties[i] = Property{ 572 | Name: field.Identifier, 573 | Modifiers: modifiers, 574 | Type: Type, 575 | DefaultValue: defaultValue, 576 | } 577 | propertyPositions[field.Identifier] = i 578 | } 579 | 580 | ctx.types[c.name] = &StructType{ 581 | TypeName: c.name, 582 | Properties: properties, 583 | propertyPositions: propertyPositions, 584 | } 585 | 586 | return NilValue() 587 | } 588 | 589 | type ExtendCommand struct { 590 | Type string 591 | statements []Command 592 | alias string 593 | } 594 | 595 | func (c *ExtendCommand) Exec(ctx *Context) *ReturnedValue { 596 | extending := ctx.FindType(c.Type) 597 | if extending == nil { 598 | panic("No such type " + c.Type) 599 | } 600 | 601 | for _, statement := range c.statements { 602 | switch statement := statement.(type) { 603 | case *DefineVarCommand: 604 | 605 | value := statement.value.Exec(ctx).Unwrap() 606 | 607 | asFunction, isFunction := value.Value.(*Function) 608 | if isFunction { 609 | //Need to append the receiver to the function's signature 610 | receiverParameter := Parameter{ 611 | Name: c.alias, 612 | Position: 0, 613 | Type: extending, 614 | } 615 | signature := asFunction.Signature 616 | params := make([]Parameter, len(signature.Parameters)+1) 617 | params[0] = receiverParameter 618 | for i := range signature.Parameters { 619 | p := signature.Parameters[i] 620 | p.Position++ 621 | params[i] = p 622 | } 623 | signature.Parameters = params 624 | asFunction.Signature = signature 625 | } 626 | 627 | variable := &Variable{ 628 | Name: statement.Name, 629 | Mutable: statement.Mutable, 630 | Type: statement.getType(ctx), 631 | Value: value, 632 | } 633 | 634 | extension := &Extension{ 635 | ReceiverName: c.alias, 636 | Value: variable, 637 | } 638 | 639 | ctx.DefineExtension(extending, statement.Name, extension) 640 | } 641 | } 642 | return NilValue() 643 | } 644 | 645 | type TypeCheckCommand struct { 646 | expression Command 647 | checkType parser.Type 648 | } 649 | 650 | func (c *TypeCheckCommand) Exec(ctx *Context) *ReturnedValue { 651 | checkAgainst := FromASTType(c.checkType, ctx) 652 | if checkAgainst == nil { 653 | panic("No such type " + util.Stringify(c.checkType)) 654 | } 655 | res := c.expression.Exec(ctx).Unwrap() 656 | is := checkAgainst.Accepts(res.Type, ctx) 657 | return NonReturningValue(BooleanValue(is)) 658 | } 659 | 660 | type WhileCommand struct { 661 | condition Command 662 | body Command 663 | } 664 | 665 | func (c *WhileCommand) Exec(ctx *Context) *ReturnedValue { 666 | for { 667 | val := c.condition.Exec(ctx) 668 | condition, ok := val.Unwrap().Value.(bool) 669 | if !ok { 670 | panic("If statements requires boolean condition") 671 | } 672 | if !condition { 673 | break 674 | } 675 | c.body.Exec(ctx) 676 | } 677 | return NilValue() 678 | } 679 | 680 | type CollectionCommand struct { 681 | Elements []Command 682 | } 683 | 684 | func (c *CollectionCommand) Exec(ctx *Context) *ReturnedValue { 685 | elements := make([]*Value, len(c.Elements)) 686 | for i, element := range c.Elements { 687 | elements[i] = element.Exec(ctx).Unwrap() 688 | } 689 | //In a proper type system we might try and find a union of all elements, but this is dynamic, and I'm lazy 690 | var collType Type 691 | if len(elements) == 0 { 692 | collType = AnyType 693 | } else { 694 | collType = elements[0].Type 695 | } 696 | collection := &Collection{ 697 | ElementType: collType, 698 | Elements: elements, 699 | } 700 | collectionType := NewCollectionType(collection) 701 | 702 | return NonReturningValue(&Value{ 703 | Type: collectionType, 704 | Value: collection, 705 | }) 706 | } 707 | 708 | type AccessCommand struct { 709 | checking Command 710 | index Command 711 | } 712 | 713 | func (c *AccessCommand) Exec(ctx *Context) *ReturnedValue { 714 | checking := c.checking.Exec(ctx).Unwrap().Value 715 | switch accessingType := checking.(type) { 716 | case *Collection: 717 | index, isInt := c.index.Exec(ctx).Unwrap().Value.(int64) 718 | if !isInt { 719 | panic("Index was not an integer") 720 | } 721 | return NonReturningValue(accessingType.Elements[index]) 722 | 723 | case *Map: 724 | index := c.index.Exec(ctx).Unwrap() 725 | return NonReturningValue(accessingType.Get(ctx, index)) 726 | } 727 | panic("Indexed access not supported for non-collection type") 728 | } 729 | 730 | type TypeCommand struct { 731 | name string 732 | value parser.Type 733 | } 734 | 735 | func (c *TypeCommand) Exec(ctx *Context) *ReturnedValue { 736 | runtimeType := FromASTType(c.value, ctx) 737 | existing := ctx.FindType(c.name) 738 | if existing != nil { 739 | panic("Type with name " + c.name + " already exists in current scope") 740 | } 741 | ctx.types[c.name] = runtimeType 742 | return NilValue() 743 | } 744 | 745 | type MapCommand struct { 746 | entries []MapEntry 747 | } 748 | type MapEntry struct { 749 | key Command 750 | value Command 751 | } 752 | 753 | func (c *MapCommand) Exec(ctx *Context) *ReturnedValue { 754 | elements := make([]*Entry, 0) 755 | for _, entry := range c.entries { 756 | key := entry.key.Exec(ctx).Unwrap() 757 | value := entry.value.Exec(ctx).Unwrap() 758 | entry := &Entry{ 759 | Key: key, 760 | Value: value, 761 | } 762 | elements = append(elements, entry) 763 | } 764 | 765 | mapValue := MapOf(elements) 766 | mapType := mapValue.MapType 767 | value := NewValue(mapType, mapValue) 768 | return NonReturningValue(value) 769 | } 770 | 771 | func ToCommand(statement parser.Stmt) Command { 772 | switch t := statement.(type) { 773 | case parser.VarDefStmt: 774 | valueExpr := NamedExpressionToCommand(t.Value, &t.Identifier) 775 | return &DefineVarCommand{ 776 | Name: t.Identifier, 777 | Mutable: t.Mutable, 778 | Type: t.Type, 779 | value: valueExpr, 780 | } 781 | 782 | case parser.ExpressionStmt: 783 | return ExpressionToCommand(t.Expr) 784 | 785 | case parser.BlockStmt: 786 | commands := make([]*Command, len(t.Stmts)) 787 | for i, stmt := range t.Stmts { 788 | cmd := ToCommand(stmt) 789 | commands[i] = &cmd 790 | _, isReturn := cmd.(*ReturnCommand) 791 | //Small optimisation, it's not worth transforming anything that won't ever be reached 792 | if isReturn { 793 | return &BlockCommand{lines: commands[:i+1]} 794 | } 795 | } 796 | 797 | return &BlockCommand{lines: commands} 798 | 799 | case parser.IfElseStmt: 800 | condition := ExpressionToCommand(t.Condition) 801 | ifBranch := ToCommand(t.MainBranch) 802 | var elseBranch Command 803 | if t.ElseBranch != nil { 804 | elseBranch = ToCommand(t.ElseBranch) 805 | } 806 | 807 | return &IfElseCommand{ 808 | condition: condition, 809 | ifBranch: ifBranch, 810 | elseBranch: elseBranch, 811 | } 812 | 813 | case parser.ReturnStmt: 814 | if t.Returning != nil { 815 | return &ReturnCommand{ 816 | ExpressionToCommand(t.Returning), 817 | } 818 | } 819 | return &ReturnCommand{ 820 | nil, 821 | } 822 | case parser.NamespaceStmt: 823 | return &NamespaceCommand{ 824 | namespace: t.Namespace, 825 | } 826 | case parser.ImportStmt: 827 | return &ImportCommand{ 828 | imports: t.Imports, 829 | } 830 | case parser.StructDefStmt: 831 | name := t.Identifier 832 | return &StructDefCommand{ 833 | name: name, 834 | fields: t.StructFields, 835 | } 836 | 837 | case parser.ExtendStmt: 838 | commands := make([]Command, len(t.Body.Stmts)) 839 | for i, stmt := range t.Body.Stmts { 840 | commands[i] = ToCommand(stmt) 841 | } 842 | return &ExtendCommand{ 843 | Type: t.Identifier, 844 | statements: commands, 845 | alias: t.Alias, 846 | } 847 | 848 | case parser.WhileStmt: 849 | return &WhileCommand{ 850 | condition: ExpressionToCommand(t.Condition), 851 | body: ToCommand(t.Body), 852 | } 853 | case parser.TypeStmt: 854 | return &TypeCommand{ 855 | name: t.Identifier, 856 | value: t.Contract, 857 | } 858 | } 859 | 860 | panic("Could not handle " + reflect.TypeOf(statement).Name()) 861 | } 862 | 863 | func ExpressionToCommand(expr parser.Expr) Command { 864 | return NamedExpressionToCommand(expr, nil) 865 | } 866 | 867 | func NamedExpressionToCommand(expr parser.Expr, name *string) Command { 868 | 869 | switch t := expr.(type) { 870 | case parser.VariableExpr: 871 | return &VariableCommand{Variable: t.Identifier} 872 | 873 | case parser.InvocationExpr: 874 | fun := ExpressionToCommand(t.Invoker) 875 | args := make([]Command, 0) 876 | for _, arg := range t.Args { 877 | command := ExpressionToCommand(arg) 878 | if command == nil { 879 | panic("Could not convert expression " + reflect.TypeOf(arg).Name() + " to condition") 880 | } 881 | args = append(args, command) 882 | } 883 | 884 | return &InvocationCommand{ 885 | Invoking: fun, 886 | args: args, 887 | } 888 | 889 | case parser.StringLiteralExpr: 890 | str := t.Value 891 | value := StringValue(str) 892 | return &LiteralCommand{value: value} 893 | 894 | case parser.IntegerLiteralExpr: 895 | integer := t.Value 896 | value := IntValue(integer) 897 | return &LiteralCommand{value: value} 898 | case parser.FloatLiteralExpr: 899 | float := t.Value 900 | value := FloatValue(float) 901 | return &LiteralCommand{value: value} 902 | case parser.BooleanLiteralExpr: 903 | boolean := t.Value 904 | value := BooleanValue(boolean) 905 | return &LiteralCommand{value: value} 906 | case parser.CharLiteralExpr: 907 | char := t.Value 908 | value := CharValue(char) 909 | return &LiteralCommand{value: value} 910 | case parser.BinaryExpr: 911 | lhs := t.Lhs 912 | lhsCmd := ExpressionToCommand(lhs) 913 | op := t.Op 914 | rhs := t.Rhs 915 | rhsCmd := ExpressionToCommand(rhs) 916 | 917 | switch op { 918 | case lexer.Add: 919 | return &InvocationCommand{ 920 | Invoking: &ContextCommand{receiver: lhsCmd, variable: "plus"}, 921 | args: []Command{rhsCmd}, 922 | } 923 | case lexer.Subtract: 924 | return &InvocationCommand{ 925 | Invoking: &ContextCommand{receiver: lhsCmd, variable: "minus"}, 926 | args: []Command{rhsCmd}, 927 | } 928 | case lexer.Multiply: 929 | return &InvocationCommand{ 930 | Invoking: &ContextCommand{receiver: lhsCmd, variable: "times"}, 931 | args: []Command{rhsCmd}, 932 | } 933 | case lexer.Slash: 934 | return &InvocationCommand{ 935 | Invoking: &ContextCommand{receiver: lhsCmd, variable: "divide"}, 936 | args: []Command{rhsCmd}, 937 | } 938 | case lexer.Equals: 939 | return &InvocationCommand{Invoking: &ContextCommand{receiver: lhsCmd, variable: "equals"}, 940 | args: []Command{rhsCmd}, 941 | } 942 | case lexer.NotEquals: 943 | return NewAbstractCommand(func(ctx *Context) *ReturnedValue { 944 | command := &InvocationCommand{Invoking: &ContextCommand{receiver: lhsCmd, variable: "equals"}, 945 | args: []Command{rhsCmd}, 946 | } 947 | 948 | val := command.Exec(ctx).Unwrap() 949 | 950 | asBool, ok := (*val).Value.(bool) 951 | if !ok { 952 | panic("equals function did not return bool") 953 | } 954 | return NonReturningValue(BooleanValue(!asBool)) 955 | }) 956 | 957 | case lexer.Mod: 958 | return &InvocationCommand{Invoking: &ContextCommand{receiver: lhsCmd, variable: "mod"}, 959 | args: []Command{rhsCmd}, 960 | } 961 | } 962 | case parser.FuncDefExpr: 963 | return &FunctionLiteralCommand{ 964 | name: name, 965 | parameters: t.Arguments, 966 | returnType: t.ReturnType, 967 | body: ToCommand(t.Statement), 968 | } 969 | 970 | case parser.ContextExpr: 971 | contextCmd := ExpressionToCommand(t.Context) 972 | varName := t.Variable.Identifier 973 | return &ContextCommand{ 974 | contextCmd, 975 | varName, 976 | util.Hash(varName), 977 | } 978 | 979 | case parser.AssignmentExpr: 980 | //TODO contexts 981 | name := t.Identifier 982 | valueCmd := NamedExpressionToCommand(t.Value, &name) 983 | return &AssignmentCommand{ 984 | Name: name, 985 | value: valueCmd, 986 | } 987 | 988 | case parser.IfElseExpr: 989 | condition := ExpressionToCommand(t.Condition) 990 | var ifBranch []Command 991 | if t.IfBranch != nil { 992 | ifBranch = make([]Command, len(t.IfBranch)) 993 | for i, stmt := range t.IfBranch { 994 | ifBranch[i] = ToCommand(stmt) 995 | } 996 | } 997 | ifResult := ExpressionToCommand(t.IfResult) 998 | 999 | var elseBranch []Command 1000 | if t.ElseBranch != nil { 1001 | elseBranch = make([]Command, len(t.ElseBranch)) 1002 | for i, stmt := range t.ElseBranch { 1003 | elseBranch[i] = ToCommand(stmt) 1004 | } 1005 | } 1006 | elseResult := ExpressionToCommand(t.ElseResult) 1007 | 1008 | return &IfElseExpressionCommand{ 1009 | condition: condition, 1010 | ifBranch: ifBranch, 1011 | ifResult: ifResult, 1012 | elseBranch: elseBranch, 1013 | elseResult: elseResult, 1014 | } 1015 | 1016 | case parser.TypeCheckExpr: 1017 | return &TypeCheckCommand{ 1018 | expression: ExpressionToCommand(t.Expr), 1019 | checkType: t.Type, 1020 | } 1021 | case parser.GroupExpr: 1022 | return ExpressionToCommand(t.Group) 1023 | 1024 | case parser.CollectionExpr: 1025 | elements := make([]Command, len(t.Elements)) 1026 | for i, element := range t.Elements { 1027 | elements[i] = ExpressionToCommand(element) 1028 | } 1029 | return &CollectionCommand{Elements: elements} 1030 | 1031 | case parser.AccessExpr: 1032 | return &AccessCommand{ 1033 | checking: ExpressionToCommand(t.Expr), 1034 | index: ExpressionToCommand(t.Index), 1035 | } 1036 | case parser.MapExpr: 1037 | entries := make([]MapEntry, len(t.Entries)) 1038 | for i, entry := range t.Entries { 1039 | mapEntry := MapEntry{ 1040 | key: ExpressionToCommand(entry.Key), 1041 | value: ExpressionToCommand(entry.Value), 1042 | } 1043 | entries[i] = mapEntry 1044 | } 1045 | return &MapCommand{ 1046 | entries: entries, 1047 | } 1048 | } 1049 | 1050 | panic("Could not handle " + reflect.TypeOf(expr).Name()) 1051 | } 1052 | -------------------------------------------------------------------------------- /interpreter/context.go: -------------------------------------------------------------------------------- 1 | package interpreter 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ElaraLang/elara/util" 6 | "math" 7 | ) 8 | 9 | type Context struct { 10 | variables map[uint64][]*Variable 11 | parameters []*Value 12 | namespace string 13 | name string //The optional name of the context - may be empty 14 | 15 | //A map from namespace -> context slice 16 | contextPath map[string][]*Context 17 | 18 | extensions map[Type]map[string]*Extension 19 | types map[string]Type 20 | parent *Context 21 | function *Function //Will only be nil if this is a Function scope 22 | } 23 | 24 | var globalContext = &Context{ 25 | namespace: "__global__", 26 | name: "__global__", 27 | variables: map[uint64][]*Variable{}, 28 | extensions: map[Type]map[string]*Extension{}, 29 | parameters: []*Value{}, 30 | contextPath: map[string][]*Context{}, 31 | 32 | types: map[string]Type{}, 33 | function: nil, 34 | } 35 | 36 | func (c *Context) Init(namespace string) { 37 | if c.namespace != "" { 38 | panic("Context has already been initialized!") 39 | } 40 | c.namespace = namespace 41 | globalContext.contextPath[c.namespace] = append(globalContext.contextPath[c.namespace], c) 42 | } 43 | 44 | func (c *Context) DefineVariableWithHash(hash uint64, value *Variable) { 45 | vars := c.variables[hash] 46 | vars = append(vars, value) 47 | c.variables[hash] = vars 48 | } 49 | func (c *Context) DefineVariable(value *Variable) { 50 | hash := util.Hash(value.Name) 51 | c.DefineVariableWithHash(hash, value) 52 | } 53 | 54 | func (c *Context) FindFunction(hash uint64, signature *Signature) *Function { 55 | vars := c.variables[hash] 56 | if vars != nil { 57 | matching := make([]*Variable, 0) 58 | for _, variable := range vars { 59 | asFunction, isFunction := variable.Value.Value.(*Function) 60 | if isFunction { 61 | if asFunction.Signature.Accepts(signature, c, false) { 62 | matching = append(matching, variable) 63 | } 64 | } 65 | } 66 | if len(matching) > 1 { 67 | _ = fmt.Errorf("multiple matching functions with name %s and signature %s", matching[0].Name, signature.String()) 68 | } 69 | if len(matching) != 0 { 70 | return matching[0].Value.Value.(*Function) 71 | } 72 | } 73 | 74 | if c.parent != nil { 75 | parFound := c.parent.FindFunction(hash, signature) 76 | if parFound != nil { 77 | return parFound 78 | } 79 | } 80 | for _, contexts := range c.contextPath { 81 | for _, context := range contexts { 82 | v := context.FindFunction(hash, signature) 83 | if v != nil { 84 | return v 85 | } 86 | } 87 | } 88 | return nil 89 | } 90 | func (c *Context) FindVariable(hash uint64) *Variable { 91 | variable, _ := c.FindVariableMaxDepth(hash, -1) 92 | return variable 93 | } 94 | 95 | //TODO this needs optimising, it's a MASSIVE hotspot 96 | func (c *Context) FindVariableMaxDepth(hash uint64, maxDepth int) (*Variable, int) { 97 | vars := c.variables[hash] 98 | if vars != nil { 99 | return vars[len(vars)-1], 0 100 | } 101 | 102 | i := 0 103 | for { 104 | if c.parent == nil { 105 | break 106 | } 107 | parentVar, depth := c.parent.FindVariableMaxDepth(hash, int(math.Max(float64(maxDepth-i), -1))) 108 | if parentVar != nil { 109 | if maxDepth < 0 || c.parent.function == nil && i+depth <= maxDepth { 110 | return parentVar, i + depth 111 | } else { 112 | return nil, i + depth 113 | } 114 | } 115 | i++ 116 | if i >= maxDepth { 117 | break 118 | } 119 | } 120 | 121 | for _, contexts := range c.contextPath { 122 | for _, context := range contexts { 123 | v, _ := context.FindVariableMaxDepth(hash, maxDepth-i) 124 | if v != nil { 125 | return v, i //0 for a variable from an import? 126 | } 127 | } 128 | } 129 | 130 | return nil, -1 131 | } 132 | 133 | func (c *Context) DefineParameter(pos uint, value *Value) { 134 | c.parameters[pos] = value 135 | } 136 | 137 | func (c *Context) FindParameter(pos uint) *Value { 138 | par := c.parameters[pos] 139 | if par != nil { 140 | return par 141 | } 142 | return nil 143 | } 144 | 145 | func (c *Context) FindType(name string) Type { 146 | t, ok := c.types[name] 147 | if ok { 148 | return t 149 | } 150 | for _, contexts := range c.contextPath { 151 | for _, context := range contexts { 152 | t := context.FindType(name) 153 | if t != nil { 154 | return t 155 | } 156 | } 157 | } 158 | return nil 159 | } 160 | 161 | func (c *Context) EnterScope(name string, function *Function, paramLength uint) *Context { 162 | scope := NewContext(false) 163 | scope.parent = c 164 | scope.namespace = c.name 165 | scope.name = name 166 | scope.contextPath = c.contextPath 167 | scope.function = function 168 | scope.parameters = make([]*Value, paramLength) 169 | scope.extensions = c.extensions 170 | return scope 171 | } 172 | 173 | func (c *Context) FindConstructor(name string) *Value { 174 | 175 | t := c.FindType(name) 176 | if t == nil { 177 | return nil 178 | } 179 | asStruct, isStruct := t.(*StructType) 180 | if !isStruct { 181 | panic("Cannot construct non struct type") 182 | } 183 | if asStruct.constructor != nil { 184 | return asStruct.constructor 185 | } 186 | 187 | constructorParams := make([]Parameter, 0) 188 | i := uint(0) 189 | for _, v := range asStruct.Properties { 190 | if v.DefaultValue == nil { 191 | constructorParams = append(constructorParams, Parameter{ 192 | Position: i, 193 | Name: v.Name, 194 | Type: v.Type, 195 | }) 196 | } 197 | i++ 198 | } 199 | 200 | constructor := &Function{ 201 | Signature: Signature{ 202 | Parameters: constructorParams, 203 | ReturnType: t, 204 | }, 205 | Body: NewAbstractCommand(func(ctx *Context) *ReturnedValue { 206 | values := make(map[string]*Value, len(constructorParams)) 207 | for _, param := range constructorParams { 208 | values[param.Name] = ctx.FindParameter(param.Position) 209 | } 210 | return NonReturningValue(&Value{ 211 | Type: t, 212 | Value: &Instance{ 213 | Type: asStruct, 214 | Values: values, 215 | }, 216 | }) 217 | }), 218 | name: &name, 219 | } 220 | 221 | constructorVal := &Value{ 222 | Type: NewFunctionType(constructor), 223 | Value: constructor, 224 | } 225 | asStruct.constructor = constructorVal 226 | return constructorVal 227 | } 228 | 229 | func (c *Context) Import(namespace string) { 230 | contexts := globalContext.contextPath[namespace] 231 | if contexts == nil { 232 | panic("Nothing found in namespace " + namespace) 233 | } 234 | ns := c.contextPath[namespace] 235 | if ns == nil { 236 | c.contextPath[namespace] = contexts 237 | return 238 | } 239 | ns = append(ns, contexts...) 240 | c.contextPath[namespace] = ns 241 | } 242 | 243 | func (c *Context) string() string { 244 | s := "" 245 | for key, values := range c.variables { 246 | s += fmt.Sprintf("%s = [\n", key) 247 | for _, val := range values { 248 | s += fmt.Sprintf("%s \n", val.String()) 249 | } 250 | s += "]\n" 251 | } 252 | 253 | return s 254 | } 255 | 256 | func (c *Context) Stringify(value *Value) string { 257 | if value == nil { 258 | return "" 259 | } 260 | 261 | return util.Stringify(value.Value) 262 | 263 | } 264 | 265 | func (c *Context) DefineExtension(receiverType Type, name string, value *Extension) { 266 | extensions, present := c.extensions[receiverType] 267 | if !present { 268 | extensions = map[string]*Extension{} 269 | } 270 | _, exists := extensions[name] 271 | if exists { 272 | panic("Extension on " + receiverType.Name() + " with name " + name + " already exists.") 273 | } 274 | extensions[name] = value 275 | c.extensions[receiverType] = extensions 276 | } 277 | 278 | func (c *Context) FindExtension(receiverType Type, name string) *Extension { 279 | extensions, present := c.extensions[receiverType] 280 | if !present { 281 | extensions = map[string]*Extension{} 282 | } 283 | return extensions[name] 284 | } 285 | -------------------------------------------------------------------------------- /interpreter/contexts.go: -------------------------------------------------------------------------------- 1 | package interpreter 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | "net/http" 8 | "os" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | var contextPool = sync.Pool{ 14 | New: func() interface{} { 15 | return &Context{ 16 | variables: map[uint64][]*Variable{}, 17 | parameters: []*Value{}, 18 | namespace: "", 19 | name: "", 20 | contextPath: map[string][]*Context{}, 21 | types: map[string]Type{}, 22 | parent: nil, 23 | function: nil, 24 | extensions: map[Type]map[string]*Extension{}, 25 | } 26 | }, 27 | } 28 | 29 | func (c *Context) Clone() *Context { 30 | var parentClone *Context = nil 31 | if c.parent != nil { 32 | parentClone = c.parent.Clone() 33 | } 34 | fromPool := contextPool.Get().(*Context) 35 | fromPool.variables = c.variables 36 | fromPool.parameters = c.parameters 37 | fromPool.namespace = c.namespace 38 | fromPool.name = c.name 39 | fromPool.contextPath = c.contextPath 40 | fromPool.types = c.types 41 | fromPool.parent = parentClone 42 | fromPool.function = c.function 43 | fromPool.extensions = c.extensions 44 | return fromPool 45 | } 46 | 47 | func (c *Context) Cleanup() { 48 | c.function = nil 49 | 50 | for s := range c.variables { 51 | delete(c.variables, s) 52 | } 53 | c.variables = map[uint64][]*Variable{} 54 | c.parameters = []*Value{} 55 | 56 | c.namespace = "" 57 | c.name = "" 58 | c.contextPath = map[string][]*Context{} 59 | c.types = map[string]Type{} 60 | c.extensions = map[Type]map[string]*Extension{} 61 | c.parent = nil 62 | contextPool.Put(c) 63 | } 64 | 65 | func NewContext(init bool) *Context { 66 | c := contextPool.Get().(*Context) 67 | if !init { 68 | return c 69 | } 70 | c.DefineVariable(&Variable{ 71 | Name: "stdout", 72 | Mutable: false, 73 | Type: OutputType, 74 | Value: &Value{ 75 | Type: OutputType, 76 | Value: nil, 77 | }, 78 | }) 79 | inputFunctionName := "input" 80 | inputFunction := Function{ 81 | name: &inputFunctionName, 82 | Signature: Signature{ 83 | Parameters: []Parameter{}, 84 | ReturnType: StringType, 85 | }, 86 | Body: NewAbstractCommand(func(ctx *Context) *ReturnedValue { 87 | var input string 88 | _, err := fmt.Scanln(&input) 89 | if err != nil { 90 | panic(err) 91 | } 92 | 93 | return NonReturningValue(&Value{Value: input, Type: StringType}) 94 | }), 95 | } 96 | 97 | inputContract := NewFunctionType(&inputFunction) 98 | 99 | c.DefineVariable(&Variable{ 100 | Name: inputFunctionName, 101 | Mutable: false, 102 | Type: inputContract, 103 | Value: &Value{ 104 | Type: inputContract, 105 | Value: inputFunction, 106 | }, 107 | }) 108 | 109 | // fetch(baseURL) 110 | 111 | fetchFunctionName := "fetch" 112 | fetchFunction := &Function{ 113 | name: &fetchFunctionName, 114 | Signature: Signature{ 115 | Parameters: []Parameter{ 116 | { 117 | Name: "this", 118 | Type: StringType, 119 | Position: 0, 120 | }, 121 | }, 122 | ReturnType: StringType, 123 | }, 124 | Body: NewAbstractCommand(func(ctx *Context) *ReturnedValue { 125 | 126 | var requestURL string = ctx.FindParameter(0).Value.(string) 127 | 128 | response, err := http.Get(requestURL) 129 | 130 | if err != nil { 131 | fmt.Print(err.Error()) 132 | os.Exit(1) 133 | } 134 | 135 | responseData, err := ioutil.ReadAll(response.Body) 136 | if err != nil { 137 | log.Fatal(err) 138 | } 139 | 140 | return NonReturningValue(&Value{Value: string(responseData), Type: StringType}) 141 | // return NonReturningValue(&Value{Value: fetch, Type: StringType}) 142 | }), 143 | } 144 | 145 | fetchContract := NewFunctionType(fetchFunction) 146 | 147 | c.DefineVariable(&Variable{ 148 | Name: fetchFunctionName, 149 | Mutable: false, 150 | Type: fetchContract, 151 | Value: &Value{ 152 | Type: fetchContract, 153 | Value: fetchFunction, 154 | }, 155 | }) 156 | 157 | // end of fetch() 158 | 159 | // setTimeout(function, ms) 160 | 161 | setTimeoutFunctionName := "setTimeout" 162 | setTimeoutFunction := &Function{ 163 | name: &setTimeoutFunctionName, 164 | Signature: Signature{ 165 | Parameters: []Parameter{ 166 | { 167 | Name: "fx", 168 | Type: NewSignatureFunctionType ( Signature{ 169 | Parameters: []Parameter{}, 170 | ReturnType: UnitType, 171 | }), 172 | Position: 0, 173 | }, 174 | { 175 | Name: "ms", 176 | Type: IntType, 177 | Position: 1, 178 | }, 179 | }, 180 | ReturnType: AnyType, 181 | }, 182 | Body: NewAbstractCommand(func(ctx *Context) *ReturnedValue { 183 | 184 | fxRun := ctx.FindParameter(0).Value.(*Function) 185 | msRun := ctx.FindParameter(1).Value.(int64) 186 | 187 | time.Sleep(time.Duration(msRun * 1000000)) 188 | 189 | fxRun.Exec(ctx, []*Value{}) 190 | 191 | return NonReturningValue(UnitValue()) 192 | 193 | }), 194 | } 195 | 196 | setTimeoutContract := NewFunctionType(setTimeoutFunction) 197 | 198 | c.DefineVariable(&Variable{ 199 | Name: setTimeoutFunctionName, 200 | Mutable: false, 201 | Type: setTimeoutContract, 202 | Value: &Value{ 203 | Type: setTimeoutContract, 204 | Value: setTimeoutFunction, 205 | }, 206 | }) 207 | 208 | // end of setTimeout() 209 | 210 | emptyName := "empty" 211 | emptyFun := &Function{ 212 | name: &emptyName, 213 | Signature: Signature{ 214 | Parameters: []Parameter{}, 215 | ReturnType: NewCollectionTypeOf(AnyType), 216 | }, 217 | Body: NewAbstractCommand(func(ctx *Context) *ReturnedValue { 218 | return NonReturningValue(&Value{ 219 | Type: NewCollectionTypeOf(AnyType), 220 | Value: &Collection{ 221 | ElementType: AnyType, 222 | Elements: []*Value{}, 223 | }, 224 | }) 225 | }), 226 | } 227 | emptyContract := NewFunctionType(emptyFun) 228 | 229 | c.DefineVariable(&Variable{ 230 | Name: emptyName, 231 | Mutable: false, 232 | Type: emptyContract, 233 | Value: &Value{ 234 | Type: emptyContract, 235 | Value: emptyFun, 236 | }, 237 | }) 238 | 239 | Init(c) 240 | return c 241 | } 242 | -------------------------------------------------------------------------------- /interpreter/extension.go: -------------------------------------------------------------------------------- 1 | package interpreter 2 | 3 | type Extension struct { 4 | ReceiverName string 5 | Value *Variable 6 | } 7 | -------------------------------------------------------------------------------- /interpreter/function.go: -------------------------------------------------------------------------------- 1 | package interpreter 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ElaraLang/elara/util" 6 | "strings" 7 | ) 8 | 9 | type Function struct { 10 | Signature Signature 11 | Body Command 12 | name *string 13 | context *Context 14 | } 15 | 16 | func (f *Function) String() string { 17 | name := "Function" 18 | if f.name != nil { 19 | name = *f.name 20 | } 21 | return name + f.Signature.String() 22 | } 23 | 24 | func (f *Function) Exec(ctx *Context, parameters []*Value) (val *Value) { 25 | context := ctx 26 | if f.context != nil { 27 | //The cached context has highest priority for things like variables, but we set the parent to ensure that we can correctly inherit things like imports 28 | context = f.context.Clone() 29 | context.parent = ctx 30 | } 31 | if len(parameters) != len(f.Signature.Parameters) { 32 | panic(fmt.Sprintf("Illegal number of arguments for function %s. Expected %d, received %d", util.NillableStringify(f.name, ""), len(f.Signature.Parameters), len(parameters))) 33 | } 34 | 35 | var name string 36 | if f.name == nil { 37 | name = f.String() 38 | } else { 39 | name = *f.name 40 | } 41 | scope := context.EnterScope(name, f, uint(len(f.Signature.Parameters))) 42 | 43 | for i, paramValue := range parameters { 44 | expectedParameter := f.Signature.Parameters[i] 45 | 46 | if !expectedParameter.Type.Accepts(paramValue.Type, ctx) { 47 | panic(fmt.Sprintf("Expected %s for parameter %s and got %s (%s)", expectedParameter.Type.Name(), expectedParameter.Name, paramValue.String(), paramValue.Type.Name())) 48 | } 49 | // 50 | //if paramValue.Value == nil { 51 | // panic("Value must not be nil") 52 | //} 53 | scope.DefineParameter(expectedParameter.Position, paramValue.Copy()) //Passing by value 54 | } 55 | 56 | value := f.Body.Exec(scope).Value //Can't unwrap because it might have returned from the function 57 | scope.Cleanup() //Exit out of the scope 58 | if value == nil { 59 | value = UnitValue() 60 | } 61 | if !f.Signature.ReturnType.Accepts(value.Type, ctx) { 62 | name := "" 63 | if f.name != nil { 64 | name = *f.name 65 | } 66 | panic(fmt.Sprintf("Function '%s' did not return value of type %s, instead was %s", name, f.Signature.ReturnType.Name(), value.Type.Name())) 67 | } 68 | return value 69 | } 70 | 71 | type Signature struct { 72 | Parameters []Parameter 73 | ReturnType Type 74 | } 75 | 76 | func (s *Signature) String() string { 77 | paramNames := make([]string, len(s.Parameters)) 78 | for i := range s.Parameters { 79 | paramNames[i] = s.Parameters[i].Type.Name() 80 | } 81 | return fmt.Sprintf("(%s) => %s", strings.Join(paramNames, ", "), s.ReturnType.Name()) 82 | } 83 | 84 | func (s *Signature) Accepts(other *Signature, ctx *Context, compareReturnTypes bool) bool { 85 | if len(s.Parameters) != len(other.Parameters) { 86 | return false 87 | } 88 | for i, parameter := range s.Parameters { 89 | otherParam := other.Parameters[i] 90 | if !parameter.Type.Accepts(otherParam.Type, ctx) { 91 | return false 92 | } 93 | } 94 | if compareReturnTypes { 95 | return s.ReturnType.Accepts(other.ReturnType, ctx) 96 | } 97 | return true 98 | } 99 | 100 | type Parameter struct { 101 | Name string 102 | Position uint 103 | Type Type 104 | } 105 | -------------------------------------------------------------------------------- /interpreter/instance.go: -------------------------------------------------------------------------------- 1 | package interpreter 2 | 3 | type Instance struct { 4 | Type *StructType 5 | Values map[string]*Value 6 | } 7 | 8 | func (i *Instance) String() string { 9 | base := i.Type.Name() + " {" 10 | for _, v := range i.Type.Properties { 11 | value := i.Values[v.Name] 12 | base += "\n " + v.Name + ": " 13 | base += value.String() 14 | } 15 | base += "\n}" 16 | return base 17 | } 18 | 19 | func (i *Instance) Equals(ctx *Context, other *Value) bool { 20 | otherAsInstance, otherIsInstance := other.Value.(*Instance) 21 | if !otherIsInstance { 22 | return false 23 | } 24 | if !i.Type.Accepts(otherAsInstance.Type, ctx) { 25 | return false 26 | } 27 | for key, value := range i.Values { 28 | otherVal, present := otherAsInstance.Values[key] 29 | if !present { 30 | return false 31 | } 32 | if !value.Equals(ctx, otherVal) { 33 | return false 34 | } 35 | } 36 | return true 37 | } 38 | -------------------------------------------------------------------------------- /interpreter/interpreter.go: -------------------------------------------------------------------------------- 1 | package interpreter 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ElaraLang/elara/parser" 6 | "reflect" 7 | ) 8 | 9 | type Interpreter struct { 10 | lines []parser.Stmt 11 | context *Context 12 | } 13 | 14 | func NewInterpreter(code []parser.Stmt) *Interpreter { 15 | return &Interpreter{ 16 | lines: code, 17 | context: NewContext(true), 18 | } 19 | } 20 | func NewEmptyInterpreter() *Interpreter { 21 | return NewInterpreter([]parser.Stmt{}) 22 | } 23 | 24 | func (s *Interpreter) ResetLines(lines *[]parser.Stmt) { 25 | s.lines = *lines 26 | } 27 | 28 | func (s *Interpreter) Exec(scriptMode bool) []*Value { 29 | values := make([]*Value, len(s.lines)) 30 | 31 | for i := 0; i < len(s.lines); i++ { 32 | line := s.lines[i] 33 | command := ToCommand(line) 34 | res := command.Exec(s.context).Unwrap() 35 | values[i] = res 36 | if scriptMode { 37 | formatted := s.context.Stringify(res) + " " + reflect.TypeOf(res).String() 38 | fmt.Println(formatted) 39 | } 40 | } 41 | return values 42 | } 43 | -------------------------------------------------------------------------------- /interpreter/ints.go: -------------------------------------------------------------------------------- 1 | package interpreter 2 | 3 | var IntType = NewEmptyType("Int") 4 | 5 | func InitInts(ctx *Context) { 6 | define(ctx, "plus", &Function{ 7 | Signature: Signature{ 8 | Parameters: []Parameter{ 9 | { 10 | Name: "this", 11 | Type: IntType, 12 | }, 13 | { 14 | Name: "value", 15 | Type: IntType, 16 | Position: 1, 17 | }, 18 | }, 19 | ReturnType: IntType, 20 | }, 21 | Body: NewAbstractCommand(func(ctx *Context) *ReturnedValue { 22 | this := ctx.FindParameter(0).Value.(int64) 23 | value := ctx.FindParameter(1).Value.(int64) 24 | 25 | returningValue := NonReturningValue(IntValue(this + value)) 26 | return returningValue 27 | }), 28 | }) 29 | 30 | define(ctx, "minus", &Function{ 31 | Signature: Signature{ 32 | Parameters: []Parameter{ 33 | { 34 | Name: "this", 35 | Type: IntType, 36 | }, 37 | { 38 | Name: "value", 39 | Type: IntType, 40 | Position: 1, 41 | }, 42 | }, 43 | ReturnType: IntType, 44 | }, 45 | Body: NewAbstractCommand(func(ctx *Context) *ReturnedValue { 46 | this := ctx.FindParameter(0).Value.(int64) 47 | value := ctx.FindParameter(1).Value.(int64) 48 | 49 | return NonReturningValue(IntValue(this - value)) 50 | }), 51 | }) 52 | 53 | define(ctx, "times", &Function{ 54 | Signature: Signature{ 55 | Parameters: []Parameter{ 56 | { 57 | Name: "this", 58 | Type: IntType, 59 | }, 60 | { 61 | Name: "value", 62 | Type: IntType, 63 | Position: 1, 64 | }, 65 | }, 66 | ReturnType: IntType, 67 | }, 68 | Body: NewAbstractCommand(func(ctx *Context) *ReturnedValue { 69 | this := ctx.FindParameter(0).Value.(int64) 70 | value := ctx.FindParameter(1).Value.(int64) 71 | 72 | return NonReturningValue(IntValue(this * value)) 73 | }), 74 | }) 75 | 76 | define(ctx, "divide", &Function{ 77 | Signature: Signature{ 78 | Parameters: []Parameter{ 79 | { 80 | Name: "this", 81 | Type: IntType, 82 | }, 83 | { 84 | Name: "value", 85 | Type: IntType, 86 | Position: 1, 87 | }, 88 | }, 89 | ReturnType: IntType, 90 | }, 91 | Body: NewAbstractCommand(func(ctx *Context) *ReturnedValue { 92 | this := ctx.FindParameter(0).Value.(int64) 93 | value := ctx.FindParameter(1).Value.(int64) 94 | 95 | return NonReturningValue(IntValue(this / value)) 96 | }), 97 | }) 98 | 99 | define(ctx, "mod", &Function{ 100 | Signature: Signature{ 101 | Parameters: []Parameter{ 102 | { 103 | Name: "this", 104 | Type: IntType, 105 | }, 106 | { 107 | Name: "value", 108 | Type: IntType, 109 | Position: 1, 110 | }, 111 | }, 112 | ReturnType: IntType, 113 | }, 114 | Body: NewAbstractCommand(func(ctx *Context) *ReturnedValue { 115 | this := ctx.FindParameter(0).Value.(int64) 116 | value := ctx.FindParameter(1).Value.(int64) 117 | 118 | return NonReturningValue(IntValue(this % value)) 119 | }), 120 | }) 121 | } 122 | 123 | func define(ctx *Context, name string, function *Function) { 124 | function.name = &name 125 | funcType := NewFunctionType(function) 126 | ctx.DefineVariable(&Variable{ 127 | Name: name, 128 | Mutable: false, 129 | Type: funcType, 130 | Value: &Value{ 131 | Type: funcType, 132 | Value: function, 133 | }, 134 | }) 135 | } 136 | -------------------------------------------------------------------------------- /interpreter/maps.go: -------------------------------------------------------------------------------- 1 | package interpreter 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | type MapType struct { 9 | KeyType Type 10 | ValueType Type 11 | } 12 | 13 | type Map struct { 14 | MapType *MapType 15 | Elements []*Entry 16 | } 17 | 18 | type Entry struct { 19 | Key *Value 20 | Value *Value 21 | } 22 | 23 | func (m *Map) Get(ctx *Context, key *Value) *Value { 24 | for _, element := range m.Elements { 25 | if element.Key.Equals(ctx, key) { 26 | return element.Value 27 | } 28 | } 29 | return nil 30 | } 31 | func MapOf(elements []*Entry) *Map { 32 | mapType := &MapType{ 33 | KeyType: AnyType, 34 | ValueType: AnyType, //tfw type inference 35 | } 36 | return &Map{ 37 | MapType: mapType, 38 | Elements: elements, 39 | } 40 | } 41 | 42 | func (t *MapType) Name() string { 43 | return fmt.Sprintf("{ %s : %s }", t.KeyType.Name(), t.ValueType.Name()) 44 | } 45 | 46 | func (t *MapType) Accepts(otherType Type, ctx *Context) bool { 47 | asMap, isMap := otherType.(*MapType) 48 | if !isMap { 49 | return false 50 | } 51 | return t.KeyType.Accepts(asMap.KeyType, ctx) && t.ValueType.Accepts(asMap.ValueType, ctx) 52 | } 53 | 54 | func (m *Map) String() string { 55 | builder := strings.Builder{} 56 | builder.WriteRune('{') 57 | for i, elem := range m.Elements { 58 | builder.WriteRune('\n') 59 | builder.WriteString(elem.Key.String()) 60 | builder.WriteString(": ") 61 | builder.WriteString(elem.Value.String()) 62 | if i != len(m.Elements)-1 { 63 | builder.WriteRune(',') 64 | } 65 | } 66 | builder.WriteRune('\n') 67 | builder.WriteRune('}') 68 | return builder.String() 69 | } 70 | -------------------------------------------------------------------------------- /interpreter/modifiers.go: -------------------------------------------------------------------------------- 1 | package interpreter 2 | 3 | const ( 4 | Mut = 1 5 | Lazy = 2 6 | Observable = 4 7 | ) 8 | -------------------------------------------------------------------------------- /interpreter/types.go: -------------------------------------------------------------------------------- 1 | package interpreter 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ElaraLang/elara/lexer" 6 | "github.com/ElaraLang/elara/parser" 7 | "reflect" 8 | ) 9 | 10 | type Type interface { 11 | Name() string 12 | //returns if *this* type accepts the other type 13 | Accepts(otherType Type, ctx *Context) bool 14 | } 15 | 16 | type StructType struct { 17 | TypeName string 18 | Properties []Property //This preserves ordering of properties 19 | propertyPositions map[string]int //And this guarantees constant lookup still 20 | constructor *Value //*Function of the constructor 21 | } 22 | 23 | func (t *StructType) Name() string { 24 | return t.TypeName 25 | } 26 | func (t *StructType) Accepts(otherType Type, ctx *Context) bool { 27 | otherStruct, ok := otherType.(*StructType) 28 | if !ok { 29 | return false 30 | } 31 | for _, property := range t.Properties { 32 | byName, exists := otherStruct.GetProperty(property.Name) 33 | if !exists { 34 | return false //Must have all of the properties 35 | } 36 | if !property.Type.Accepts(byName.Type, ctx) { 37 | return false //And the types must be acceptable 38 | } 39 | } 40 | return true 41 | } 42 | func (t *StructType) GetProperty(identifier string) (Property, bool) { 43 | i, present := t.propertyPositions[identifier] 44 | if !present { 45 | return Property{}, false 46 | } 47 | return t.Properties[i], true 48 | } 49 | 50 | type Property struct { 51 | Name string 52 | Type Type 53 | //bitmask (base/modifiers.go) 54 | Modifiers uint 55 | DefaultValue *Value 56 | } 57 | 58 | type FunctionType struct { 59 | Signature Signature 60 | } 61 | 62 | func NewFunctionType(function *Function) *FunctionType { 63 | return &FunctionType{Signature: function.Signature} 64 | } 65 | func NewSignatureFunctionType(signature Signature) *FunctionType { 66 | return &FunctionType{Signature: signature} 67 | } 68 | func (t *FunctionType) Name() string { 69 | return t.Signature.String() 70 | } 71 | 72 | /* 73 | Function acceptance is defined by having the same number of parameters, 74 | with all of A's parameters accepting the corresponding parameters for B 75 | and A's return type accepting B's return type 76 | */ 77 | func (t *FunctionType) Accepts(otherType Type, ctx *Context) bool { 78 | otherFunc, ok := otherType.(*FunctionType) 79 | if !ok { 80 | return false 81 | } 82 | return t.Signature.Accepts(&otherFunc.Signature, ctx, false) 83 | } 84 | 85 | type EmptyType struct { 86 | name string 87 | } 88 | 89 | func (t *EmptyType) Name() string { 90 | return t.name 91 | } 92 | func (t *EmptyType) Accepts(otherType Type, _ *Context) bool { 93 | if *t == *(AnyType.(*EmptyType)) { //ew 94 | return true 95 | } 96 | //This is really trying to patch a deeper problem - this function relies on there only ever being 1 pointer to a type. 97 | asEmpty, isEmpty := otherType.(*EmptyType) 98 | 99 | if isEmpty { 100 | return t.name == asEmpty.name 101 | } 102 | return t == otherType 103 | } 104 | func NewEmptyType(name string) Type { 105 | return &EmptyType{name: name} 106 | } 107 | 108 | type UnionType struct { 109 | a Type 110 | b Type 111 | } 112 | 113 | func (t *UnionType) Name() string { 114 | return t.a.Name() + " | " + t.b.Name() 115 | } 116 | func (t *UnionType) Accepts(otherType Type, ctx *Context) bool { 117 | return t.a.Accepts(otherType, ctx) || t.b.Accepts(otherType, ctx) 118 | } 119 | 120 | type IntersectionType struct { 121 | a Type 122 | b Type 123 | } 124 | 125 | func (t *IntersectionType) Name() string { 126 | return t.a.Name() + " & " + t.b.Name() 127 | } 128 | func (t *IntersectionType) Accepts(otherType Type, ctx *Context) bool { 129 | return t.a.Accepts(otherType, ctx) && t.b.Accepts(otherType, ctx) 130 | } 131 | 132 | type DefinedType struct { 133 | name string 134 | parts map[string]Type 135 | } 136 | 137 | func (t *DefinedType) Name() string { 138 | return t.name 139 | } 140 | func (t *DefinedType) Accepts(other Type, ctx *Context) bool { 141 | asStruct, isStruct := other.(*StructType) 142 | for s, t2 := range t.parts { 143 | if isStruct { 144 | property, present := asStruct.GetProperty(s) 145 | if present && t2.Accepts(property.Type, ctx) { 146 | continue 147 | } 148 | } 149 | extension := ctx.FindExtension(other, s) 150 | if extension != nil && t2.Accepts(extension.Value.Type, ctx) { 151 | continue 152 | } 153 | return false 154 | } 155 | 156 | return true 157 | } 158 | 159 | func FromASTType(astType parser.Type, ctx *Context) Type { 160 | switch t := astType.(type) { 161 | case parser.ElementaryTypeContract: 162 | found := ctx.FindType(t.Identifier) 163 | if found != nil { 164 | return found 165 | } 166 | return NewEmptyType(t.Identifier) 167 | 168 | case parser.InvocableTypeContract: 169 | returned := FromASTType(t.ReturnType, ctx) 170 | args := make([]Parameter, len(t.Args)) 171 | for i, arg := range t.Args { 172 | argType := FromASTType(arg, ctx) 173 | args[i] = Parameter{ 174 | Name: fmt.Sprintf("arg%d", i), 175 | Type: argType, 176 | } 177 | } 178 | 179 | signature := Signature{ 180 | Parameters: args, 181 | ReturnType: returned, 182 | } 183 | return NewSignatureFunctionType(signature) 184 | 185 | case parser.CollectionTypeContract: 186 | elemType := FromASTType(t.ElemType, ctx) 187 | return &CollectionType{ 188 | ElementType: elemType, 189 | } 190 | 191 | case parser.BinaryTypeContract: 192 | switch t.TypeOp { 193 | case lexer.TypeAnd: 194 | return &IntersectionType{ 195 | a: FromASTType(t.Lhs, ctx), 196 | b: FromASTType(t.Rhs, ctx), 197 | } 198 | case lexer.TypeOr: 199 | return &UnionType{ 200 | a: FromASTType(t.Lhs, ctx), 201 | b: FromASTType(t.Rhs, ctx), 202 | } 203 | } 204 | case parser.DefinedTypeContract: 205 | parts := make(map[string]Type, len(t.DefType)) 206 | for _, definedType := range t.DefType { 207 | parts[definedType.Identifier] = FromASTType(definedType.DefType, ctx) 208 | } 209 | return &DefinedType{ 210 | name: t.Name, 211 | parts: parts, 212 | } 213 | case parser.MapTypeContract: 214 | keyType := FromASTType(t.KeyType, ctx) 215 | valueType := FromASTType(t.ValueType, ctx) 216 | return &MapType{ 217 | KeyType: keyType, ValueType: valueType, 218 | } 219 | } 220 | panic("Cannot handle " + reflect.TypeOf(astType).Name()) 221 | return nil 222 | } 223 | -------------------------------------------------------------------------------- /interpreter/value.go: -------------------------------------------------------------------------------- 1 | package interpreter 2 | 3 | import ( 4 | "github.com/ElaraLang/elara/util" 5 | "sync" 6 | ) 7 | 8 | type Value struct { 9 | Type Type 10 | Value interface{} 11 | } 12 | 13 | func (v *Value) String() string { 14 | if v == nil { 15 | return "" 16 | } 17 | return util.Stringify(v.Value) 18 | } 19 | 20 | func (v *Value) Copy() *Value { 21 | if v == nil { 22 | return nil 23 | } 24 | return NewValue(v.Type, v.Value) 25 | } 26 | 27 | var equalsNameHash = util.Hash("equals") 28 | 29 | func (v *Value) Equals(ctx *Context, b *Value) bool { 30 | eqFunction := ctx.FindFunction(equalsNameHash, &Signature{ 31 | Parameters: []Parameter{ 32 | {Name: "this", Position: 0, Type: v.Type}, 33 | {Name: "other", Position: 1, Type: b.Type}, 34 | }, 35 | ReturnType: BooleanType, 36 | }) 37 | if eqFunction != nil { 38 | res := eqFunction.Exec(ctx, []*Value{v, b}).Value.(bool) 39 | return res 40 | } 41 | return v.Value == b.Value 42 | } 43 | 44 | var unitValue = NewValue(UnitType, nil) 45 | 46 | func UnitValue() *Value { 47 | return unitValue 48 | } 49 | 50 | var returnedValues = sync.Pool{ 51 | New: func() interface{} { 52 | return &ReturnedValue{ 53 | Value: nil, 54 | IsReturning: false, 55 | } 56 | }, 57 | } 58 | 59 | type ReturnedValue struct { 60 | Value *Value 61 | IsReturning bool 62 | } 63 | 64 | func NewReturningValue(value *Value, returning bool) *ReturnedValue { 65 | r := returnedValues.Get().(*ReturnedValue) 66 | r.Value = value 67 | r.IsReturning = returning 68 | return r 69 | } 70 | func NonReturningValue(value *Value) *ReturnedValue { 71 | return NewReturningValue(value, false) 72 | } 73 | 74 | func ReturningValue(value *Value) *ReturnedValue { 75 | return NewReturningValue(value, true) 76 | } 77 | 78 | func NilValue() *ReturnedValue { 79 | return NewReturningValue(nil, false) 80 | } 81 | 82 | func (r *ReturnedValue) clean() { 83 | r.Value = nil 84 | r.IsReturning = false 85 | returnedValues.Put(r) 86 | } 87 | 88 | func (r ReturnedValue) Unwrap() *Value { 89 | if r.IsReturning { 90 | panic("Value should return") 91 | } 92 | val := r.Value 93 | r.clean() 94 | return val 95 | } 96 | 97 | func (r ReturnedValue) UnwrapNotNil() *Value { 98 | if r.IsReturning { 99 | panic("Value should return") 100 | } 101 | if r.Value == nil { 102 | panic("Value must not be nil") 103 | } 104 | val := r.Value 105 | r.clean() 106 | return val 107 | } 108 | -------------------------------------------------------------------------------- /interpreter/values.go: -------------------------------------------------------------------------------- 1 | package interpreter 2 | 3 | func NewValue(valueType Type, value interface{}) *Value { 4 | return &Value{ 5 | Type: valueType, 6 | Value: value, 7 | } 8 | } 9 | 10 | func IntValue(int int64) *Value { 11 | return NewValue(IntType, int) 12 | } 13 | 14 | func FloatValue(num float64) *Value { 15 | return NewValue(FloatType, num) 16 | } 17 | 18 | func BooleanValue(value bool) *Value { 19 | return NewValue(BooleanType, value) 20 | } 21 | 22 | func CharValue(value rune) *Value { 23 | return NewValue(CharType, value) 24 | } 25 | 26 | func StringValue(value string) *Value { 27 | chars := make([]*Value, len(value)) 28 | for i, c := range value { 29 | chars[i] = CharValue(c) 30 | } 31 | val := &Collection{Elements: chars, ElementType: CharType} 32 | return NewValue(NewCollectionTypeOf(CharType), val) 33 | } 34 | -------------------------------------------------------------------------------- /interpreter/variables.go: -------------------------------------------------------------------------------- 1 | package interpreter 2 | 3 | import "fmt" 4 | 5 | type Variable struct { 6 | Name string 7 | Mutable bool 8 | Type Type 9 | Value *Value 10 | } 11 | 12 | func (v Variable) String() string { 13 | return fmt.Sprintf("Variable { Name: %s, mutable: %T, type: %s, Value: %s", v.Name, v.Mutable, v.Type, v.Value) 14 | } 15 | 16 | //func (v *Variable) Equals(other Variable) bool { 17 | // if v.Name != other.Name { 18 | // return false 19 | // } 20 | // if v.Mutable != other.Mutable { 21 | // return false 22 | // } 23 | // if !v.Type.Accepts(other.Type, ) { 24 | // //TODO exact equality necessary? 25 | // return false 26 | // } 27 | // if v.Value != other.Value { 28 | // return false 29 | // } 30 | // return true 31 | //} 32 | -------------------------------------------------------------------------------- /lexer/chars.go: -------------------------------------------------------------------------------- 1 | package lexer 2 | 3 | import "unicode" 4 | 5 | func IsWhitespace(ch rune) bool { 6 | return whitespace[ch] 7 | } 8 | 9 | var whitespace = [255]bool{ 10 | ' ': true, 11 | '\n': true, 12 | '\t': true, 13 | } 14 | 15 | func isBracket(ch rune) bool { 16 | return ch == '(' || ch == ')' || ch == '{' || ch == '}' || ch == '[' || ch == ']' || isAngleBracket(ch) 17 | } 18 | 19 | func isStartOfSymbol(ch rune) bool { 20 | return ch == '.' || ch == '=' 21 | } 22 | 23 | func isAngleBracket(ch rune) bool { 24 | return ch == '<' || ch == '>' 25 | } 26 | 27 | //This function is a bit of a hotspot, mostly due to how often it's called. Not much to be done here though - map access is pretty fast :/ 28 | func isValidIdentifier(ch rune) bool { 29 | return !IllegalIdentifierChars[ch] 30 | } 31 | 32 | func isNumerical(ch rune) bool { 33 | return unicode.IsNumber(ch) 34 | } 35 | 36 | func isOperatorSymbol(ch rune) bool { 37 | return ch == '=' || ch == '+' || ch == '-' || ch == '*' || ch == '/' || ch == '%' || ch == '&' || ch == '|' || ch == '^' || ch == '!' || ch == '>' || ch == '<' 38 | } 39 | 40 | var eof = rune(-1) 41 | -------------------------------------------------------------------------------- /lexer/lexer.go: -------------------------------------------------------------------------------- 1 | package lexer 2 | 3 | func Lex(code string) []Token { 4 | chars := []rune(code) 5 | scanner := NewTokenReader(chars) 6 | 7 | //Note: in our big benchmark, the token:chars ratio seems to be about 1:1.2 (5:6). Could be worth doing len(code) / 1.2 and rounding? 8 | estimateLength := len(code) 9 | if estimateLength > 10 { 10 | estimateLength /= 2 11 | } 12 | tokens := make([]Token, estimateLength) //pre-sizing our slice avoids having to copy to append a lot 13 | i := 0 14 | 15 | for { 16 | tok, runes, line, col := scanner.Read() 17 | if tok == EOF { 18 | tokens = tokens[:i] 19 | break 20 | } 21 | 22 | token := &Token{ 23 | TokenType: tok, 24 | Text: runes, 25 | Position: CreatePosition(line, col), 26 | } 27 | 28 | if i <= len(tokens)-1 { 29 | tokens[i] = *token 30 | i++ 31 | } else { 32 | tokens = append(tokens, *token) 33 | i = len(tokens) 34 | } 35 | } 36 | 37 | return tokens 38 | } 39 | -------------------------------------------------------------------------------- /lexer/lexer_benchmark_test.go: -------------------------------------------------------------------------------- 1 | package lexer 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | var code = strings.Repeat(` 9 | let a = 30 10 | let a = 3.5 11 | let a = "Hello" 12 | let a = true 13 | let a = () => {} 14 | let hello-world => print "Hello World" 15 | hello-world() 16 | ()[]{}<> 17 | + - * / % && || ^ == != > >= < <= ! 18 | , : _ 19 | let mut struct if else match while 20 | `, 10_000) 21 | 22 | func BenchmarkEverySymbol(b *testing.B) { 23 | for n := 0; n < b.N; n++ { 24 | _ = Lex(code) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lexer/lexer_test.go: -------------------------------------------------------------------------------- 1 | package lexer 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestIntAssignmentLexing(t *testing.T) { 9 | code := "let a = 30" 10 | tokens := Lex(code) 11 | 12 | expectedTokens := []Token{ 13 | CreateToken(Let, "let", CreatePosition(0, 0)), 14 | CreateToken(Identifier, "a", CreatePosition(0, 4)), 15 | CreateToken(Equal, "=", CreatePosition(0, 6)), 16 | CreateToken(Int, "30", CreatePosition(0, 8)), 17 | } 18 | 19 | if !reflect.DeepEqual(tokens, expectedTokens) { 20 | t.Errorf("Incorrect lexing output, got %v but expected %v", tokens, expectedTokens) 21 | } 22 | } 23 | 24 | func TestFloatAssignmentLexing(t *testing.T) { 25 | code := "let a = 3.5" 26 | tokens := Lex(code) 27 | 28 | expectedTokens := []Token{ 29 | CreateToken(Let, "let", CreatePosition(0, 0)), 30 | CreateToken(Identifier, "a", CreatePosition(0, 4)), 31 | CreateToken(Equal, "=", CreatePosition(0, 6)), 32 | CreateToken(Float, "3.5", CreatePosition(0, 8)), 33 | } 34 | 35 | if !reflect.DeepEqual(tokens, expectedTokens) { 36 | t.Errorf("Incorrect lexing output, got %v but expected %v", tokens, expectedTokens) 37 | } 38 | } 39 | 40 | func TestStringAssignmentLexing(t *testing.T) { 41 | code := `let a = "Hello"` 42 | tokens := Lex(code) 43 | 44 | expectedTokens := []Token{ 45 | CreateToken(Let, "let", CreatePosition(0, 0)), 46 | CreateToken(Identifier, "a", CreatePosition(0, 4)), 47 | CreateToken(Equal, "=", CreatePosition(0, 6)), 48 | CreateToken(String, "Hello", CreatePosition(0, 8)), 49 | } 50 | 51 | if !reflect.DeepEqual(tokens, expectedTokens) { 52 | t.Errorf("Incorrect lexing output, got %v but expected %v", tokens, expectedTokens) 53 | } 54 | } 55 | 56 | func TestBooleanAssignmentLexing(t *testing.T) { 57 | code := `let a = true` 58 | tokens := Lex(code) 59 | 60 | expectedTokens := []Token{ 61 | CreateToken(Let, "let", CreatePosition(0, 0)), 62 | CreateToken(Identifier, "a", CreatePosition(0, 4)), 63 | CreateToken(Equal, "=", CreatePosition(0, 6)), 64 | CreateToken(BooleanTrue, "true", CreatePosition(0, 8)), 65 | } 66 | 67 | if !reflect.DeepEqual(tokens, expectedTokens) { 68 | t.Errorf("Incorrect lexing output, got %v but expected %v", tokens, expectedTokens) 69 | } 70 | } 71 | 72 | func TestSimpleFunctionLexing(t *testing.T) { 73 | code := `let a = () => {}` 74 | tokens := Lex(code) 75 | 76 | expectedTokens := []Token{ 77 | CreateToken(Let, "let", CreatePosition(0, 0)), 78 | CreateToken(Identifier, "a", CreatePosition(0, 4)), 79 | CreateToken(Equal, "=", CreatePosition(0, 6)), 80 | CreateToken(LParen, "(", CreatePosition(0, 8)), 81 | CreateToken(RParen, ")", CreatePosition(0, 9)), 82 | CreateToken(Arrow, "=>", CreatePosition(0, 11)), 83 | CreateToken(LBrace, "{", CreatePosition(0, 14)), 84 | CreateToken(RBrace, "}", CreatePosition(0, 15)), 85 | } 86 | 87 | if !reflect.DeepEqual(tokens, expectedTokens) { 88 | t.Errorf("Incorrect lexing output, got %v but expected %v", tokens, expectedTokens) 89 | } 90 | } 91 | 92 | func TestHelloWorldLexing(t *testing.T) { 93 | code := `let hello-world => print "Hello World" 94 | hello-world()` 95 | tokens := Lex(code) 96 | 97 | expectedTokens := []Token{ 98 | CreateToken(Let, "let", CreatePosition(0, 0)), 99 | CreateToken(Identifier, "hello-world", CreatePosition(0, 4)), 100 | CreateToken(Arrow, "=>", CreatePosition(0, 16)), 101 | CreateToken(Identifier, "print", CreatePosition(0, 19)), 102 | CreateToken(String, "Hello World", CreatePosition(0, 25)), 103 | CreateToken(NEWLINE, "\n", CreatePosition(0, 36)), 104 | //Note: These add 13 because there are 13 spaces in the raw string before the call 105 | CreateToken(Identifier, "hello-world", CreatePosition(1, 13+0)), 106 | CreateToken(LParen, "(", CreatePosition(1, 13+11)), 107 | CreateToken(RParen, ")", CreatePosition(1, 13+12)), 108 | } 109 | 110 | if !reflect.DeepEqual(tokens, expectedTokens) { 111 | t.Errorf("Incorrect lexing output, got %v but expected %v", tokens, expectedTokens) 112 | } 113 | } 114 | 115 | func TestBracketLexing(t *testing.T) { 116 | code := `()[]{}<>` 117 | tokens := Lex(code) 118 | 119 | expectedTokens := []Token{ 120 | CreateToken(LParen, "(", CreatePosition(0, 0)), 121 | CreateToken(RParen, ")", CreatePosition(0, 1)), 122 | CreateToken(LSquare, "[", CreatePosition(0, 2)), 123 | CreateToken(RSquare, "]", CreatePosition(0, 3)), 124 | CreateToken(LBrace, "{", CreatePosition(0, 4)), 125 | CreateToken(RBrace, "}", CreatePosition(0, 5)), 126 | CreateToken(LAngle, "<", CreatePosition(0, 6)), 127 | CreateToken(RAngle, ">", CreatePosition(0, 7)), 128 | } 129 | 130 | if !reflect.DeepEqual(tokens, expectedTokens) { 131 | t.Errorf("Incorrect lexing output, got %v but expected %v", tokens, expectedTokens) 132 | } 133 | } 134 | 135 | func TestOperatorLexing(t *testing.T) { 136 | code := `+ - * / % && || ^ == != > >= < <= !` 137 | tokens := Lex(code) 138 | 139 | expectedTokens := []Token{ 140 | CreateToken(Add, "+", CreatePosition(0, 0)), 141 | CreateToken(Subtract, "-", CreatePosition(0, 2)), 142 | CreateToken(Multiply, "*", CreatePosition(0, 4)), 143 | CreateToken(Slash, "/", CreatePosition(0, 6)), 144 | CreateToken(Mod, "%", CreatePosition(0, 8)), 145 | CreateToken(And, "&&", CreatePosition(0, 10)), 146 | CreateToken(Or, "||", CreatePosition(0, 13)), 147 | CreateToken(Xor, "^", CreatePosition(0, 16)), 148 | CreateToken(Equals, "==", CreatePosition(0, 18)), 149 | CreateToken(NotEquals, "!=", CreatePosition(0, 21)), 150 | CreateToken(RAngle, ">", CreatePosition(0, 24)), 151 | CreateToken(GreaterEqual, ">=", CreatePosition(0, 26)), 152 | CreateToken(LAngle, "<", CreatePosition(0, 29)), 153 | CreateToken(LesserEqual, "<=", CreatePosition(0, 31)), 154 | CreateToken(Not, "!", CreatePosition(0, 34)), 155 | } 156 | 157 | if !reflect.DeepEqual(tokens, expectedTokens) { 158 | t.Errorf("Incorrect lexing output %v but expected %v", tokens, expectedTokens) 159 | } 160 | } 161 | 162 | func TestUnderscoreLexing(t *testing.T) { 163 | code := `_` 164 | tokens := Lex(code) 165 | 166 | expectedTokens := []Token{ 167 | CreateToken(Underscore, "_", CreatePosition(0, 0)), 168 | } 169 | 170 | if !reflect.DeepEqual(tokens, expectedTokens) { 171 | t.Errorf("Incorrect lexing output, got %v but expected %v", tokens, expectedTokens) 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /lexer/scanner.go: -------------------------------------------------------------------------------- 1 | package lexer 2 | 3 | import ( 4 | "unicode" 5 | ) 6 | 7 | //TODO remove the amount of `defer` declarations as they seem to have a cost, and more optimisations of readIdentifier 8 | type TokenReader struct { 9 | runes []rune 10 | cursor int 11 | line int 12 | col int 13 | } 14 | 15 | func NewTokenReader(runes []rune) *TokenReader { 16 | return &TokenReader{ 17 | runes: runes, 18 | cursor: 0, 19 | line: 0, 20 | col: 0, 21 | } 22 | } 23 | 24 | //Reads the current rune and moves the cursor to the next rune 25 | func (s *TokenReader) Advance() rune { 26 | if s.cursor >= len(s.runes) { 27 | return eof 28 | } 29 | r := s.runes[s.cursor] 30 | s.cursor++ 31 | return r 32 | } 33 | 34 | //Goes back to reading the previous rune 35 | func (s *TokenReader) unread() { 36 | s.cursor-- 37 | } 38 | 39 | func (s *TokenReader) peek() rune { 40 | if s.cursor >= len(s.runes)-1 { 41 | return eof 42 | } 43 | return s.runes[s.cursor] 44 | } 45 | 46 | //TODO this is pretty gross, could use a cleanup 47 | func (s *TokenReader) Read() (tok TokenType, text []rune, line int, col int) { 48 | ch := s.Advance() 49 | 50 | if ch == eof { 51 | return EOF, []rune{ch}, s.line, s.col 52 | } 53 | 54 | if ch == '\r' { 55 | return s.Read() 56 | } 57 | 58 | if ch == '\n' { 59 | oldCol := s.col 60 | s.col = 0 61 | s.line++ 62 | return NEWLINE, []rune{ch}, s.line - 1, oldCol 63 | } 64 | 65 | if IsWhitespace(ch) { 66 | s.consumeWhitespace() 67 | return s.Read() 68 | } 69 | 70 | if ch == ',' { 71 | defer func() { 72 | s.col++ 73 | }() 74 | return Comma, []rune{ch}, s.line, s.col 75 | } 76 | if ch == ':' { 77 | defer func() { 78 | s.col++ 79 | }() 80 | return Colon, []rune{ch}, s.line, s.col 81 | } 82 | 83 | if isAngleBracket(ch) { 84 | s.unread() 85 | bracket, t := s.readAngleBracket() 86 | defer func() { 87 | s.col += len(t) 88 | }() 89 | return bracket, t, s.line, s.col 90 | } 91 | 92 | if isStartOfSymbol(ch) { 93 | s.unread() 94 | symbol, t := s.readSymbol() 95 | defer func() { 96 | s.col += len(t) 97 | }() 98 | return symbol, t, s.line, s.col 99 | } 100 | 101 | if isOperatorSymbol(ch) { 102 | s.unread() 103 | op, t := s.readOperator() 104 | defer func() { 105 | s.col += len(t) 106 | }() 107 | if op != Illegal && op != EOF { 108 | return op, t, s.line, s.col 109 | } 110 | } 111 | 112 | if isBracket(ch) { 113 | s.unread() 114 | bracket, t := s.readBracket() 115 | defer func() { 116 | s.col += len(t) 117 | }() 118 | return bracket, t, s.line, s.col 119 | } 120 | 121 | if isNumerical(ch) { 122 | s.unread() 123 | number, t := s.readNumber() 124 | defer func() { 125 | s.col += len(t) 126 | }() 127 | return number, t, s.line, s.col 128 | } 129 | 130 | if ch == '"' { 131 | str, t := s.readString() 132 | defer func() { 133 | s.col += len(t) 134 | }() 135 | return str, t, s.line, s.col 136 | } 137 | 138 | if ch == '\'' { 139 | char, t := s.readChar() 140 | defer func() { 141 | s.col++ 142 | }() 143 | return char, []rune{t}, s.line, s.col 144 | } 145 | 146 | if isValidIdentifier(ch) { 147 | s.unread() 148 | identifier, t := s.readIdentifier() 149 | defer func() { 150 | s.col += len(t) 151 | }() 152 | return identifier, t, s.line, s.col 153 | } 154 | 155 | return Illegal, []rune{ch}, s.line, s.col 156 | } 157 | 158 | //Consume all whitespace until we reach an eof or a non-whitespace character 159 | func (s *TokenReader) consumeWhitespace() int { 160 | count := 1 //We know this function will be called when the lexer has already encountered at least 1 whitespace 161 | for { 162 | ch := s.Advance() 163 | if ch == eof { 164 | break 165 | } 166 | if ch == '\n' { 167 | s.line++ 168 | s.col = 0 169 | } else if ch == '\t' || ch == ' ' { 170 | count++ 171 | } else { 172 | s.unread() 173 | break 174 | } 175 | } 176 | s.col += count 177 | return count 178 | } 179 | 180 | func (s *TokenReader) readIdentifier() (tok TokenType, text []rune) { 181 | i := s.cursor 182 | end := i 183 | for { 184 | r := s.runes[end] 185 | if r == eof || !isValidIdentifier(r) { 186 | break 187 | } 188 | end++ 189 | if end >= len(s.runes) { 190 | break 191 | } 192 | } 193 | s.cursor = end 194 | 195 | str := s.runes[i:end] 196 | length := end - i //possibly slightly faster than len() 197 | 198 | switch str[0] { 199 | case 'l': 200 | { 201 | if length == 3 && str[1] == 'e' && str[2] == 't' { 202 | return Let, str 203 | } 204 | if length == 4 && str[1] == 'a' && str[2] == 'z' && str[3] == 'y' { 205 | return Lazy, str 206 | } 207 | return Identifier, str 208 | } 209 | case 't': 210 | { 211 | if length == 4 { 212 | if str[1] == 'y' && str[2] == 'p' && str[3] == 'e' { 213 | return Type, str 214 | } 215 | if str[1] == 'r' && str[2] == 'u' && str[3] == 'e' { 216 | return BooleanTrue, str 217 | } 218 | } 219 | return Identifier, str 220 | } 221 | case 'i': 222 | { 223 | if length == 2 { 224 | if str[1] == 'f' { 225 | return If, str 226 | } 227 | if str[1] == 's' { 228 | return Is, str 229 | } 230 | } 231 | if length == 6 && str[1] == 'm' && str[2] == 'p' && str[3] == 'o' && str[4] == 'r' && str[5] == 't' { 232 | return Import, str 233 | } 234 | return Identifier, str 235 | } 236 | } 237 | //TODO optimise other comparisons 238 | if runeSliceEq(str, []rune("mut")) { 239 | return Mut, str 240 | } 241 | if runeSliceEq(str, []rune("restricted")) { 242 | return Restricted, str 243 | } 244 | if runeSliceEq(str, []rune("extend")) { 245 | return Extend, str 246 | } 247 | if runeSliceEq(str, []rune("return")) { 248 | return Return, str 249 | } 250 | if runeSliceEq(str, []rune("while")) { 251 | return While, str 252 | } 253 | if runeSliceEq(str, []rune("struct")) { 254 | return Struct, str 255 | } 256 | if runeSliceEq(str, []rune("namespace")) { 257 | return Namespace, str 258 | } 259 | if runeSliceEq(str, []rune("else")) { 260 | return Else, str 261 | } 262 | if runeSliceEq(str, []rune("match")) { 263 | return Match, str 264 | } 265 | if runeSliceEq(str, []rune("as")) { 266 | return As, str 267 | } 268 | if runeSliceEq(str, []rune("false")) { 269 | return BooleanFalse, str 270 | } 271 | 272 | return Identifier, str 273 | } 274 | 275 | func (s *TokenReader) readBracket() (tok TokenType, text []rune) { 276 | str := s.Advance() 277 | switch str { 278 | case '(': 279 | return LParen, []rune{str} 280 | case ')': 281 | return RParen, []rune{str} 282 | case '{': 283 | return LBrace, []rune{str} 284 | case '}': 285 | return RBrace, []rune{str} 286 | case '<': 287 | return LAngle, []rune{str} 288 | case '>': 289 | return RAngle, []rune{str} 290 | case '[': 291 | return LSquare, []rune{str} 292 | case ']': 293 | return RSquare, []rune{str} 294 | } 295 | return Illegal, []rune{str} 296 | } 297 | 298 | func (s *TokenReader) readSymbol() (tok TokenType, text []rune) { 299 | ch := s.Advance() 300 | 301 | switch ch { 302 | case '.': 303 | return Dot, []rune{ch} 304 | case '=': 305 | peeked := s.peek() 306 | if peeked == '>' { 307 | s.Advance() 308 | return Arrow, []rune{ch, peeked} 309 | } 310 | if peeked == '=' { 311 | s.Advance() 312 | return Equals, []rune{ch, peeked} 313 | } 314 | return Equal, []rune{ch} 315 | } 316 | 317 | return Illegal, []rune{ch} 318 | } 319 | func (s *TokenReader) readAngleBracket() (tok TokenType, text []rune) { 320 | ch1 := s.Advance() 321 | ch := s.peek() 322 | if ch1 == '<' { 323 | switch ch { 324 | case '=': 325 | s.Advance() 326 | return LesserEqual, []rune{ch1, ch} 327 | } 328 | return LAngle, []rune{ch1} 329 | } 330 | if ch1 == '>' { 331 | switch ch { 332 | case '=': 333 | s.Advance() 334 | return GreaterEqual, []rune{ch1, ch} 335 | } 336 | return RAngle, []rune{ch1} 337 | } 338 | 339 | return Illegal, []rune{ch1} 340 | } 341 | 342 | func (s *TokenReader) readOperator() (tok TokenType, text []rune) { 343 | start := s.cursor 344 | end := start 345 | for { 346 | r := s.runes[end] 347 | if r == eof { 348 | break 349 | } 350 | if !isOperatorSymbol(r) { 351 | s.unread() 352 | break 353 | } 354 | end++ 355 | if end >= len(s.runes) { 356 | break 357 | } 358 | } 359 | s.cursor = end 360 | 361 | str := s.runes[start:end] 362 | switch str[0] { 363 | case '+': 364 | return Add, str 365 | case '-': 366 | return Subtract, str 367 | case '*': 368 | return Multiply, str 369 | case '/': 370 | return Slash, str 371 | case '%': 372 | return Mod, str 373 | case '^': 374 | return Xor, str 375 | case '|': 376 | return TypeOr, str 377 | case '&': 378 | return TypeAnd, str 379 | 380 | case '>': 381 | { 382 | l := len(str) 383 | if l == 1 { 384 | return LAngle, str 385 | } 386 | n := str[1] 387 | if l > 2 || n != '=' { 388 | panic("Unknown operator " + string(str)) 389 | } 390 | return GreaterEqual, str 391 | } 392 | case '<': 393 | { 394 | l := len(str) 395 | if l == 1 { 396 | return RAngle, str 397 | } 398 | n := str[1] 399 | if l > 2 || n != '=' { 400 | panic("Unknown operator " + string(str)) 401 | } 402 | return LesserEqual, str 403 | } 404 | case '!': 405 | { 406 | l := len(str) 407 | if l == 1 { 408 | return Not, str 409 | } 410 | n := str[1] 411 | if l > 2 || n != '=' { 412 | panic("Unknown operator " + string(str)) 413 | } 414 | return NotEquals, str 415 | } 416 | } 417 | if len(str) != 2 { 418 | panic("Unknown operator " + string(str)) 419 | } 420 | if runeSliceEq(str, []rune("&&")) { 421 | return And, str 422 | } 423 | if runeSliceEq(str, []rune("||")) { 424 | return Or, str 425 | } 426 | if runeSliceEq(str, []rune("==")) { 427 | return Equals, str 428 | } 429 | return Illegal, str 430 | } 431 | 432 | //This function is called with the assumption that the beginning " has ALREADY been Advance. 433 | func (s *TokenReader) readString() (tok TokenType, text []rune) { 434 | start := s.cursor 435 | end := start 436 | 437 | for { 438 | r := s.runes[end] 439 | if r == eof { 440 | break 441 | } 442 | end++ 443 | if r == '"' { 444 | break 445 | } 446 | if end >= len(s.runes) { 447 | break 448 | } 449 | } 450 | s.cursor = end 451 | return String, s.runes[start : end-1] 452 | } 453 | 454 | //This function is called with the assumption that the beginning ' has ALREADY been Advance. 455 | func (s *TokenReader) readChar() (tok TokenType, char rune) { 456 | start := s.cursor 457 | char = s.runes[start] 458 | s.cursor++ 459 | if s.runes[s.cursor-1] == '\\' { 460 | switch s.runes[s.cursor] { 461 | case 'n': 462 | char = '\n' 463 | case 'r': 464 | char = '\r' 465 | case 't': 466 | char = '\t' 467 | case '\'': 468 | char = '\'' 469 | case '\\': 470 | char = '\\' 471 | case 'b': 472 | char = '\b' 473 | default: 474 | panic("Invalid escape sequence in char literal") 475 | } 476 | s.cursor++ 477 | } 478 | if s.runes[s.cursor] != '\'' { 479 | panic("Char literal must have only 1 symbol") 480 | } 481 | s.cursor++ 482 | return Char, char 483 | } 484 | 485 | func (s *TokenReader) readNumber() (tok TokenType, text []rune) { 486 | start := s.cursor 487 | end := start 488 | numType := Int 489 | 490 | for { 491 | r := s.runes[end] 492 | if r == eof { 493 | break 494 | } 495 | if r == '\n' { 496 | s.unread() 497 | break 498 | } 499 | if r == '.' { 500 | if numType == Float { 501 | break 502 | } 503 | numType = Float 504 | } else if !unicode.IsNumber(r) { 505 | s.unread() 506 | break 507 | } 508 | end++ 509 | if end >= len(s.runes) { 510 | break 511 | } 512 | } 513 | s.cursor = end 514 | 515 | return numType, s.runes[start:end] 516 | } 517 | 518 | func runeSliceEq(a []rune, b []rune) bool { 519 | if len(a) != len(b) { 520 | return false 521 | } 522 | for i := range a { 523 | if a[i] != b[i] { 524 | return false 525 | } 526 | } 527 | return true 528 | } 529 | -------------------------------------------------------------------------------- /lexer/token.go: -------------------------------------------------------------------------------- 1 | package lexer 2 | 3 | import "fmt" 4 | 5 | type Token struct { 6 | TokenType TokenType 7 | Text []rune 8 | Position Position 9 | } 10 | 11 | func (t *Token) Equals(other *Token) bool { 12 | if t.TokenType != other.TokenType { 13 | return false 14 | } 15 | if t.Position != other.Position { 16 | return false 17 | } 18 | if !runeSliceEq(t.Text, other.Text) { 19 | return false 20 | } 21 | return true 22 | } 23 | 24 | type Position struct { 25 | line int 26 | column int 27 | } 28 | 29 | func CreateToken(tokenType TokenType, text string, position Position) Token { 30 | return Token{ 31 | TokenType: tokenType, 32 | Text: []rune(text), 33 | Position: position, 34 | } 35 | } 36 | 37 | func CreatePosition(line int, column int) Position { 38 | return Position{ 39 | line: line, 40 | column: column, 41 | } 42 | } 43 | 44 | func (t *Token) String() string { 45 | return fmt.Sprintf("%s '%s' at %s", t.TokenType.String(), string(t.Text), t.Position.String()) 46 | } 47 | 48 | func (p *Position) String() string { 49 | return fmt.Sprintf("%d:%d", p.line, p.column) 50 | } 51 | -------------------------------------------------------------------------------- /lexer/token_type.go: -------------------------------------------------------------------------------- 1 | package lexer 2 | 3 | type TokenType int 4 | 5 | const ( 6 | //special tokens 7 | Illegal TokenType = iota 8 | EOF 9 | NEWLINE 10 | 11 | //Brackets 12 | LParen 13 | RParen 14 | LBrace 15 | RBrace 16 | LAngle //< 17 | RAngle //> 18 | LSquare 19 | RSquare 20 | 21 | //Keywords 22 | Let 23 | Extend 24 | Return 25 | While 26 | Mut 27 | Lazy 28 | Restricted 29 | Struct 30 | Namespace 31 | Import 32 | Type 33 | If 34 | Else 35 | Match 36 | As 37 | Is 38 | 39 | //Operators 40 | Add 41 | Subtract 42 | Multiply 43 | Slash 44 | Mod 45 | And 46 | Or 47 | Xor 48 | Equals 49 | NotEquals 50 | GreaterEqual 51 | LesserEqual 52 | Not 53 | 54 | TypeOr // | 55 | TypeAnd // & 56 | 57 | //Symbol 58 | Equal 59 | Arrow 60 | Dot 61 | 62 | //Literals 63 | BooleanTrue 64 | BooleanFalse 65 | String 66 | Char 67 | Int 68 | Float 69 | 70 | Comma 71 | Colon 72 | 73 | Identifier 74 | Underscore 75 | ) 76 | 77 | func (token *TokenType) String() string { 78 | return tokenNames[*token] 79 | } 80 | 81 | var tokenNames = map[TokenType]string{ 82 | Illegal: "Illegal", 83 | EOF: "EOF", 84 | NEWLINE: "\\n", 85 | 86 | LParen: "LParen", 87 | RParen: "RParen", 88 | LBrace: "LBrace", 89 | RBrace: "RBrace", 90 | LAngle: "LAngle", 91 | RAngle: "RAngle", 92 | LSquare: "LSquare", 93 | RSquare: "RSquare", 94 | Type: "Type", 95 | Let: "Let", 96 | Extend: "Extend", 97 | Return: "Return", 98 | While: "While", 99 | Mut: "Mut", 100 | Lazy: "Lazy", 101 | Restricted: "Restricted", 102 | Struct: "Struct", 103 | Namespace: "Namespace", 104 | Import: "Import", 105 | If: "If", 106 | Else: "Else", 107 | Match: "Match", 108 | As: "As", 109 | Is: "Is", 110 | Add: "Add", 111 | Subtract: "Subtract", 112 | Multiply: "Multiply", 113 | Slash: "Slash", 114 | Mod: "Mod", 115 | And: "And", 116 | Or: "Or", 117 | Xor: "Xor", 118 | Equals: "Equals", 119 | NotEquals: "NotEquals", 120 | GreaterEqual: "GreaterEqual", 121 | LesserEqual: "LesserEqual", 122 | Not: "Not", 123 | Equal: "Equal", 124 | Arrow: "Arrow", 125 | Dot: "Dot", 126 | BooleanTrue: "True", 127 | BooleanFalse: "False", 128 | String: "String", 129 | Char: "Char", 130 | Int: "Int", 131 | Float: "Float", 132 | 133 | Comma: "Comma", 134 | Colon: "Colon", 135 | 136 | Identifier: "Identifier", 137 | Underscore: "Underscore", 138 | } 139 | var IllegalIdentifierChars = []bool{ 140 | ',': true, 141 | '.': true, 142 | ':': true, 143 | '#': true, 144 | '[': true, 145 | ']': true, 146 | '(': true, 147 | ')': true, 148 | '{': true, 149 | '}': true, 150 | '"': true, 151 | '>': true, 152 | '<': true, 153 | ' ': true, 154 | '\n': true, 155 | '\r': true, 156 | '\t': true, 157 | } 158 | -------------------------------------------------------------------------------- /parser/defined_type.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import "github.com/ElaraLang/elara/lexer" 4 | 5 | type DefinedType struct { 6 | Identifier string 7 | DefType Type 8 | } 9 | 10 | func (p *Parser) definedTypes() (types []DefinedType) { 11 | types = make([]DefinedType, 0) 12 | p.consume(lexer.LBrace, "Expected '{' where defined type starts") 13 | p.cleanNewLines() 14 | for !p.check(lexer.RBrace) { 15 | typ := p.primaryContract(true) 16 | id := p.consume(lexer.Identifier, "Expected identifier for type in defined type contract") 17 | dTyp := DefinedType{ 18 | Identifier: string(id.Text), 19 | DefType: typ, 20 | } 21 | types = append(types, dTyp) 22 | if !p.match(lexer.Comma) { 23 | break 24 | } 25 | p.cleanNewLines() 26 | } 27 | p.cleanNewLines() 28 | p.consume(lexer.RBrace, "Expected '}' where defined type ends") 29 | return types 30 | } 31 | -------------------------------------------------------------------------------- /parser/expressions.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "github.com/ElaraLang/elara/lexer" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | type Expr interface{ exprNode() } 10 | 11 | type BinaryExpr struct { 12 | Lhs Expr 13 | Op TokenType 14 | Rhs Expr 15 | } 16 | 17 | type UnaryExpr struct { 18 | Op TokenType 19 | Rhs Expr 20 | } 21 | 22 | type GroupExpr struct { 23 | Group Expr 24 | } 25 | 26 | type VariableExpr struct { 27 | Identifier string 28 | } 29 | 30 | type AssignmentExpr struct { 31 | Context Expr 32 | Identifier string 33 | Value Expr 34 | } 35 | 36 | type InvocationExpr struct { 37 | Invoker Expr 38 | Args []Expr 39 | } 40 | 41 | type ContextExpr struct { 42 | Context Expr 43 | Variable VariableExpr 44 | } 45 | 46 | type TypeCastExpr struct { 47 | Expr Expr 48 | Type Type 49 | } 50 | 51 | type TypeCheckExpr struct { 52 | Expr Expr 53 | Type Type 54 | } 55 | 56 | type IfElseExpr struct { 57 | Condition Expr 58 | IfBranch []Stmt 59 | IfResult Expr 60 | ElseBranch []Stmt 61 | ElseResult Expr 62 | } 63 | 64 | type FuncDefExpr struct { 65 | Arguments []FunctionArgument 66 | ReturnType Type 67 | Statement Stmt 68 | } 69 | 70 | type AccessExpr struct { 71 | Expr Expr 72 | Index Expr 73 | } 74 | 75 | type CollectionExpr struct { 76 | Elements []Expr 77 | } 78 | 79 | type MapExpr struct { 80 | Entries []MapEntry 81 | } 82 | 83 | type MapEntry struct { 84 | Key Expr 85 | Value Expr 86 | } 87 | 88 | type StringLiteralExpr struct { 89 | Value string 90 | } 91 | type CharLiteralExpr struct { 92 | Value rune 93 | } 94 | 95 | type IntegerLiteralExpr struct { 96 | Value int64 97 | } 98 | 99 | type FloatLiteralExpr struct { 100 | Value float64 101 | } 102 | 103 | type BooleanLiteralExpr struct { 104 | Value bool 105 | } 106 | 107 | func (FuncDefExpr) exprNode() {} 108 | func (AccessExpr) exprNode() {} 109 | func (CollectionExpr) exprNode() {} 110 | func (MapExpr) exprNode() {} 111 | func (StringLiteralExpr) exprNode() {} 112 | func (CharLiteralExpr) exprNode() {} 113 | func (IntegerLiteralExpr) exprNode() {} 114 | func (FloatLiteralExpr) exprNode() {} 115 | func (BooleanLiteralExpr) exprNode() {} 116 | func (UnaryExpr) exprNode() {} 117 | func (BinaryExpr) exprNode() {} 118 | func (GroupExpr) exprNode() {} 119 | func (ContextExpr) exprNode() {} 120 | func (IfElseExpr) exprNode() {} 121 | func (InvocationExpr) exprNode() {} 122 | func (AssignmentExpr) exprNode() {} 123 | func (VariableExpr) exprNode() {} 124 | func (TypeCastExpr) exprNode() {} 125 | func (TypeCheckExpr) exprNode() {} 126 | 127 | func (p *Parser) expression() Expr { 128 | if p.peek().TokenType == lexer.If { 129 | return p.ifElseExpression() 130 | } 131 | return p.assignment() 132 | } 133 | 134 | func (p *Parser) assignment() (expr Expr) { 135 | expr = p.typeCast() 136 | 137 | if p.check(lexer.Equal) { 138 | eqlTok := p.advance() 139 | rhs := p.typeCast() 140 | 141 | switch v := expr.(type) { 142 | case VariableExpr: 143 | expr = AssignmentExpr{ 144 | Identifier: v.Identifier, 145 | Value: rhs, 146 | } 147 | break 148 | case ContextExpr: 149 | expr = AssignmentExpr{ 150 | Context: v.Context, 151 | Identifier: v.Variable.Identifier, 152 | Value: rhs, 153 | } 154 | break 155 | default: 156 | panic(ParseError{ 157 | token: eqlTok, 158 | message: "Invalid type found behind assignment", 159 | }) 160 | } 161 | } 162 | return 163 | } 164 | 165 | func (p *Parser) typeCast() Expr { 166 | expr := p.typeCheck() 167 | for p.match(lexer.As) { 168 | expr = TypeCastExpr{ 169 | Expr: expr, 170 | Type: p.typeContractDefinable(), 171 | } 172 | } 173 | return expr 174 | } 175 | 176 | func (p *Parser) typeCheck() Expr { 177 | expr := p.logicalOr() 178 | if p.match(lexer.Is) { 179 | expr = TypeCheckExpr{ 180 | Expr: expr, 181 | Type: p.typeContractDefinable(), 182 | } 183 | } 184 | return expr 185 | } 186 | 187 | func (p *Parser) logicalOr() (expr Expr) { 188 | expr = p.logicalAnd() 189 | 190 | for p.match(lexer.Or) { 191 | op := p.previous() 192 | rhs := p.logicalAnd() 193 | expr = BinaryExpr{ 194 | Lhs: expr, 195 | Op: op.TokenType, 196 | Rhs: rhs, 197 | } 198 | } 199 | return 200 | } 201 | 202 | func (p *Parser) logicalAnd() Expr { 203 | expr := p.referenceEquality() 204 | 205 | for p.match(lexer.And) { 206 | op := p.previous() 207 | rhs := p.referenceEquality() 208 | 209 | expr = BinaryExpr{ 210 | Lhs: expr, 211 | Op: op.TokenType, 212 | Rhs: rhs, 213 | } 214 | } 215 | return expr 216 | } 217 | 218 | func (p *Parser) referenceEquality() (expr Expr) { 219 | expr = p.comparison() 220 | 221 | for p.match(lexer.Equals, lexer.NotEquals) { 222 | op := p.previous() 223 | rhs := p.comparison() 224 | 225 | expr = BinaryExpr{ 226 | Lhs: expr, 227 | Op: op.TokenType, 228 | Rhs: rhs, 229 | } 230 | } 231 | return 232 | } 233 | 234 | func (p *Parser) comparison() (expr Expr) { 235 | expr = p.addition() 236 | 237 | for p.match(lexer.GreaterEqual, lexer.RAngle, lexer.LesserEqual, lexer.LAngle) { 238 | op := p.previous() 239 | rhs := p.addition() 240 | 241 | expr = BinaryExpr{ 242 | Lhs: expr, 243 | Op: op.TokenType, 244 | Rhs: rhs, 245 | } 246 | } 247 | return 248 | } 249 | 250 | func (p *Parser) addition() (expr Expr) { 251 | expr = p.multiplication() 252 | 253 | for p.match(lexer.Add, lexer.Subtract) { 254 | op := p.previous() 255 | rhs := p.multiplication() 256 | expr = BinaryExpr{ 257 | Lhs: expr, 258 | Op: op.TokenType, 259 | Rhs: rhs, 260 | } 261 | } 262 | return 263 | } 264 | 265 | func (p *Parser) multiplication() (expr Expr) { 266 | expr = p.unary() 267 | 268 | for p.match(lexer.Multiply, lexer.Slash, lexer.Mod) { 269 | op := p.previous() 270 | rhs := p.unary() 271 | expr = BinaryExpr{ 272 | Lhs: expr, 273 | Op: op.TokenType, 274 | Rhs: rhs, 275 | } 276 | } 277 | return 278 | } 279 | 280 | func (p *Parser) unary() (expr Expr) { 281 | if p.match(lexer.Subtract, lexer.Not, lexer.Add) { 282 | op := p.previous() 283 | rhs := p.unary() 284 | expr = UnaryExpr{ 285 | Op: op.TokenType, 286 | Rhs: rhs, 287 | } 288 | return 289 | } 290 | expr = p.invoke() 291 | return 292 | } 293 | 294 | func (p *Parser) invoke() (expr Expr) { 295 | expr = p.funDef() 296 | 297 | for p.match(lexer.LParen, lexer.Dot, lexer.LSquare) { 298 | switch p.previous().TokenType { 299 | case lexer.LParen: 300 | separator := lexer.Comma 301 | args := p.invocationParameters(&separator) 302 | 303 | expr = InvocationExpr{ 304 | Invoker: expr, 305 | Args: args, 306 | } 307 | case lexer.Dot: 308 | id := p.consumeValidIdentifier("Expected identifier inside context getter/setter") 309 | 310 | expr = ContextExpr{ 311 | Context: expr, 312 | Variable: VariableExpr{Identifier: string(id.Text)}, 313 | } 314 | case lexer.LSquare: 315 | expr = AccessExpr{ 316 | Expr: expr, 317 | Index: p.expression(), 318 | } 319 | p.consume(lexer.RSquare, "Expected ']' after access index") 320 | } 321 | } 322 | return 323 | } 324 | 325 | func (p *Parser) funDef() Expr { 326 | tok := p.peek() 327 | switch tok.TokenType { 328 | case lexer.LParen: 329 | args := p.functionArguments() 330 | var typ Type 331 | p.consume(lexer.Arrow, "Expected arrow at function definition") 332 | 333 | if p.check(lexer.Identifier) && p.isBlockPresent() { 334 | typ = p.typeContract() 335 | } 336 | return FuncDefExpr{ 337 | Arguments: args, 338 | ReturnType: typ, 339 | Statement: p.statement(), 340 | } 341 | case lexer.LBrace: 342 | mapExpr := p.tryParseMapLiteral() 343 | if mapExpr != nil { 344 | return mapExpr 345 | } 346 | if p.previous().TokenType == lexer.Arrow { 347 | panic(ParseError{ 348 | token: tok, 349 | message: "Single line function expected, found block function", 350 | }) 351 | } 352 | return FuncDefExpr{ 353 | Arguments: make([]FunctionArgument, 0), 354 | ReturnType: nil, 355 | Statement: p.blockStatement(), 356 | } 357 | case lexer.Arrow: 358 | p.advance() 359 | return FuncDefExpr{ 360 | Arguments: make([]FunctionArgument, 0), 361 | ReturnType: nil, 362 | Statement: p.exprStatement(), 363 | } 364 | default: 365 | return p.collection() 366 | } 367 | } 368 | 369 | func (p *Parser) tryParseMapLiteral() Expr { 370 | p.advance() 371 | //Peek until reaching a closing brace 372 | count := 0 373 | seenColon := false 374 | for { 375 | count++ 376 | next := p.advance().TokenType 377 | if next == lexer.Colon { 378 | seenColon = true 379 | break 380 | } 381 | if next == lexer.RBrace { 382 | break 383 | } 384 | } 385 | for i := 0; i < count; i++ { 386 | p.reverse() 387 | } 388 | p.reverse() // Undo the lbrace read 389 | if !seenColon { //it's not a map literal 390 | return nil 391 | } 392 | return p.mapLiteral() 393 | } 394 | 395 | func (p *Parser) mapLiteral() Expr { 396 | p.consume(lexer.LBrace, "Expected { in map literal") 397 | p.cleanNewLines() 398 | consumeEntry := func() (Expr, Expr) { 399 | key := p.expression() 400 | p.consume(lexer.Colon, "Expected colon between map literal key and value") 401 | val := p.expression() 402 | return key, val 403 | } 404 | entries := make([]MapEntry, 0) 405 | 406 | for { 407 | if p.peek().TokenType == lexer.RBrace { 408 | break 409 | } 410 | key, val := consumeEntry() 411 | entries = append(entries, MapEntry{key, val}) 412 | if p.peek().TokenType == lexer.Comma { 413 | p.advance() 414 | } 415 | //p.consume(lexer.Comma, "Expected comma after map literal entry") 416 | p.cleanNewLines() 417 | } 418 | p.consume(lexer.RBrace, "Expected } to close map literal") 419 | return MapExpr{Entries: entries} 420 | } 421 | 422 | func (p *Parser) collection() (expr Expr) { 423 | if p.match(lexer.LSquare) { 424 | col := make([]Expr, 0) 425 | for { 426 | col = append(col, p.expression()) 427 | p.cleanNewLines() 428 | if !p.match(lexer.Comma) { 429 | break 430 | } 431 | } 432 | p.consume(lexer.RSquare, "Expected ']' at end of collection literal") 433 | return CollectionExpr{ 434 | Elements: col, 435 | } 436 | } 437 | expr = p.primary() 438 | return 439 | } 440 | 441 | func (p *Parser) primary() (expr Expr) { 442 | var err error 443 | switch p.peek().TokenType { 444 | case lexer.String: 445 | str := p.consume(lexer.String, "Expected string") 446 | text := string(str.Text) 447 | text = strings.ReplaceAll(text, "\\n", "\n") 448 | //TODO other special characters 449 | 450 | expr = StringLiteralExpr{Value: text} 451 | break 452 | case lexer.Char: 453 | charTok := p.consume(lexer.Char, "Expected char") 454 | char := charTok.Text[0] 455 | expr = CharLiteralExpr{Value: char} 456 | case lexer.BooleanTrue: 457 | p.consume(lexer.BooleanTrue, "Expected BooleanTrue") 458 | expr = BooleanLiteralExpr{Value: true} 459 | break 460 | case lexer.BooleanFalse: 461 | p.consume(lexer.BooleanFalse, "Expected BooleanFalse") 462 | expr = BooleanLiteralExpr{Value: false} 463 | break 464 | case lexer.Int: 465 | str := p.consume(lexer.Int, "Expected integer") 466 | var integer int64 467 | integer, err = strconv.ParseInt(string(str.Text), 10, 64) 468 | expr = IntegerLiteralExpr{Value: integer} 469 | break 470 | case lexer.Float: 471 | str := p.consume(lexer.Float, "Expected float") 472 | var float float64 473 | float, err = strconv.ParseFloat(string(str.Text), 64) 474 | expr = FloatLiteralExpr{Value: float} 475 | break 476 | case lexer.Identifier: 477 | str := p.consume(lexer.Identifier, "Expected identifier") 478 | expr = VariableExpr{Identifier: string(str.Text)} 479 | break 480 | 481 | case lexer.If: 482 | return p.ifElseExpression() 483 | case lexer.LParen: 484 | p.advance() 485 | expr = GroupExpr{Group: p.expression()} 486 | p.consume(lexer.RParen, "Expected ')' after grouped expression") 487 | } 488 | 489 | if err != nil { 490 | panic(ParseError{ 491 | token: p.previous(), 492 | message: "Expected literal", 493 | }) 494 | } 495 | 496 | if expr == nil { 497 | panic(ParseError{ 498 | token: p.peek(), 499 | message: "Invalid expression", 500 | }) 501 | } 502 | return 503 | } 504 | 505 | func (p *Parser) ifElseExpression() Expr { 506 | p.consume(lexer.If, "Expected if at beginning of if expression") 507 | condition := p.logicalOr() 508 | if p.peek().TokenType == lexer.Arrow { 509 | p.consume(lexer.Arrow, "") 510 | mainResult := p.expression() 511 | 512 | elseBranch, elseResult := p.elseExpression() 513 | 514 | return IfElseExpr{ 515 | Condition: condition, 516 | IfBranch: nil, 517 | IfResult: mainResult, 518 | ElseBranch: elseBranch, 519 | ElseResult: elseResult, 520 | } 521 | } 522 | 523 | mainBranch := p.blockStatement() 524 | mainResult := mainBranch.Stmts[len(mainBranch.Stmts)-1] 525 | _, isExpr := mainResult.(ExpressionStmt) 526 | if !isExpr { 527 | panic(ParseError{message: "Last line in an `if` block must be an expression"}) 528 | } 529 | 530 | elseBranch, elseResult := p.elseExpression() 531 | 532 | return IfElseExpr{ 533 | Condition: condition, 534 | IfBranch: mainBranch.Stmts[:len(mainBranch.Stmts)-1], //drop the last result 535 | IfResult: mainResult.(ExpressionStmt).Expr, 536 | ElseBranch: elseBranch, 537 | ElseResult: elseResult, 538 | } 539 | } 540 | 541 | func (p *Parser) elseExpression() ([]Stmt, Expr) { 542 | p.cleanNewLines() 543 | p.consume(lexer.Else, "if expression must follow with else expression") 544 | if p.peek().TokenType == lexer.Arrow { 545 | p.advance() 546 | return nil, p.expression() 547 | } else if p.peek().TokenType == lexer.If { 548 | return nil, p.ifElseExpression() 549 | } else { 550 | elseBranch := p.blockStatement() 551 | elseResult := elseBranch.Stmts[len(elseBranch.Stmts)-1] 552 | 553 | _, isExpr := elseResult.(ExpressionStmt) 554 | if !isExpr { 555 | panic(ParseError{message: "Last line in an `else` expression block must be an expression"}) 556 | } 557 | 558 | return elseBranch.Stmts[:len(elseBranch.Stmts)-1], elseResult.(ExpressionStmt).Expr 559 | } 560 | } 561 | -------------------------------------------------------------------------------- /parser/function.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "github.com/ElaraLang/elara/lexer" 5 | ) 6 | 7 | type FunctionArgument struct { 8 | Lazy bool 9 | Type Type 10 | Name string 11 | Default Expr 12 | } 13 | 14 | func (p *Parser) invocationParameters(separator *TokenType) (expr []Expr) { 15 | params := make([]Expr, 0) 16 | for !p.match(lexer.RParen) { 17 | param := p.expression() 18 | params = append(params, param) 19 | if p.peek().TokenType == lexer.RParen { 20 | p.advance() 21 | break 22 | } 23 | if separator != nil { 24 | p.consume(*separator, "Expected separator "+separator.String()+" in function parameters") 25 | } 26 | } 27 | expr = params 28 | return 29 | } 30 | 31 | func (p *Parser) functionArguments() (args []FunctionArgument) { 32 | args = make([]FunctionArgument, 0) 33 | p.consume(lexer.LParen, "Expected left paren before starting function definition") 34 | 35 | for !p.match(lexer.RParen) { 36 | arg := p.functionArgument() 37 | args = append(args, arg) 38 | p.cleanNewLines() 39 | if !p.check(lexer.RParen) { 40 | p.consume(lexer.Comma, "Expected comma to separate function arguments") 41 | } 42 | } 43 | return 44 | } 45 | 46 | func (p *Parser) functionArgument() FunctionArgument { 47 | lazy := p.parseProperties(lexer.Lazy)[0] 48 | checkIndex := p.current + 1 49 | var typ Type 50 | if len(p.tokens) > checkIndex && p.tokens[checkIndex].TokenType != lexer.Equal { 51 | typ = p.typeContractDefinable() 52 | } 53 | id := p.consume(lexer.Identifier, "Invalid argument in function def") 54 | var def Expr 55 | if p.match(lexer.Equal) { 56 | def = p.expression() 57 | } 58 | return FunctionArgument{ 59 | Lazy: lazy, 60 | Type: typ, 61 | Name: string(id.Text), 62 | Default: def, 63 | } 64 | } 65 | 66 | func (p *Parser) isFuncDef() (result bool) { 67 | closing := p.findParenClosingPoint(p.current) 68 | return p.tokens[closing+1].TokenType == lexer.Arrow || 69 | (p.tokens[closing+1].TokenType == lexer.Identifier && p.tokens[closing+2].TokenType == lexer.Arrow) 70 | } 71 | 72 | func (p *Parser) findParenClosingPoint(start int) (index int) { 73 | if p.tokens[start].TokenType != lexer.LParen { 74 | return -1 75 | } 76 | cur := start + 1 77 | for p.tokens[cur].TokenType != lexer.RParen { 78 | if p.tokens[cur].TokenType == lexer.LParen { 79 | cur = p.findParenClosingPoint(cur) 80 | } 81 | cur++ 82 | if cur > len(p.tokens) { 83 | panic(ParseError{ 84 | token: p.previous(), 85 | message: "Unexpected end before closing parenthesis", 86 | }) 87 | } 88 | } 89 | return cur 90 | } 91 | 92 | func (p *Parser) isBlockPresent() bool { 93 | curIdx := p.current 94 | 95 | for curIdx < len(p.tokens) { 96 | switch p.tokens[curIdx].TokenType { 97 | case lexer.LBrace: 98 | return true 99 | case lexer.NEWLINE: 100 | return false 101 | } 102 | curIdx++ 103 | } 104 | return false 105 | } 106 | -------------------------------------------------------------------------------- /parser/generics.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "github.com/ElaraLang/elara/lexer" 5 | ) 6 | 7 | type GenericContract struct { 8 | Identifier string 9 | Contract Type 10 | } 11 | 12 | func (p *Parser) generic() (contracts []GenericContract) { 13 | p.consume(lexer.LAngle, "Expected generic declaration to start with `<`") 14 | contracts = make([]GenericContract, 0) 15 | for { 16 | contract := p.genericContract() 17 | contracts = append(contracts, contract) 18 | if !p.match(lexer.Comma) { 19 | break 20 | } 21 | } 22 | p.consume(lexer.RAngle, "Expected generic declaration to end with `>`") 23 | return 24 | } 25 | 26 | func (p *Parser) genericContract() (typContract GenericContract) { 27 | typID := p.consume(lexer.Identifier, "Expected identifier for generic type") 28 | p.consume(lexer.Colon, "Expected colon after generic type id") 29 | contract := p.typeContractDefinable() 30 | typContract = GenericContract{ 31 | Identifier: string(typID.Text), 32 | Contract: contract, 33 | } 34 | return 35 | } 36 | 37 | func (p *Parser) typeStatement() (typStmt Stmt) { 38 | p.consume(lexer.Type, "Expected 'type' at the start of type declaration") 39 | id := p.consume(lexer.Identifier, "Expected identifier for type") 40 | p.consume(lexer.Equal, "Expected equals after type identifier") 41 | contract := p.typeContractDefinable() 42 | typStmt = TypeStmt{ 43 | Identifier: string(id.Text), 44 | Contract: contract, 45 | } 46 | return 47 | } 48 | 49 | func (p *Parser) genericStatement() (genericStmt Stmt) { 50 | generic := p.generic() 51 | 52 | p.cleanNewLines() 53 | 54 | stmt := p.declaration() 55 | return GenerifiedStmt{ 56 | Contracts: generic, 57 | Statement: stmt, 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /parser/meta.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "github.com/ElaraLang/elara/lexer" 5 | "regexp" 6 | ) 7 | 8 | type NamespaceStmt struct { 9 | Namespace string 10 | } 11 | 12 | func (NamespaceStmt) stmtNode() {} 13 | 14 | type ImportStmt struct { 15 | Imports []string 16 | } 17 | 18 | func (ImportStmt) stmtNode() {} 19 | 20 | var namespaceRegex, _ = regexp.Compile(".+/.+") 21 | 22 | func (p *Parser) parseFileMeta() (NamespaceStmt, ImportStmt) { 23 | p.consume(lexer.Namespace, "Expected file namespace declaration!") 24 | nsToken := p.consume(lexer.Identifier, "Expected valid namespace!") 25 | ns := string(nsToken.Text) 26 | if !namespaceRegex.MatchString(ns) { 27 | panic(ParseError{ 28 | token: nsToken, 29 | message: "Invalid namespace format", 30 | }) 31 | } 32 | p.cleanNewLines() 33 | imports := make([]string, 0) 34 | var impNs string 35 | for p.match(lexer.Import) { 36 | importToken := p.consume(lexer.Identifier, "Expected valid namespace to import!") 37 | impNs = string(importToken.Text) 38 | if !namespaceRegex.MatchString(impNs) { 39 | panic(ParseError{ 40 | token: nsToken, 41 | message: "Invalid namespace format to import", 42 | }) 43 | } 44 | imports = append(imports, impNs) 45 | p.cleanNewLines() 46 | } 47 | return NamespaceStmt{ 48 | Namespace: ns, 49 | }, ImportStmt{ 50 | Imports: imports, 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /parser/parser.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ElaraLang/elara/lexer" 6 | ) 7 | 8 | type Scanner = lexer.TokenReader 9 | type Token = lexer.Token 10 | type TokenType = lexer.TokenType 11 | 12 | type ParseError struct { 13 | token Token 14 | message string 15 | } 16 | 17 | func (pe ParseError) Error() string { 18 | return fmt.Sprintf("Parse Error: %s at %s", pe.message, pe.token.String()) 19 | } 20 | 21 | type Parser struct { 22 | tokens []Token 23 | current int 24 | } 25 | 26 | func NewEmptyParser() *Parser { 27 | return &Parser{} 28 | } 29 | 30 | func NewParser(tokens []Token) *Parser { 31 | return &Parser{ 32 | tokens: tokens, 33 | } 34 | } 35 | 36 | func (p *Parser) Reset(tokens []Token) { 37 | p.tokens = tokens 38 | p.current = 0 39 | } 40 | 41 | func (p *Parser) Parse() (result []Stmt, error []ParseError) { 42 | p.current = 0 43 | result = make([]Stmt, 0) 44 | error = make([]ParseError, 0) 45 | 46 | for !p.isAtEnd() { 47 | p.parseLine(&result, &error) 48 | } 49 | return 50 | } 51 | 52 | func (p *Parser) parseLine(result *[]Stmt, error *[]ParseError) { 53 | defer p.handleError(error) 54 | if p.peek().TokenType == lexer.NEWLINE { 55 | p.advance() 56 | return 57 | } 58 | 59 | if p.current == 0 && p.check(lexer.Namespace) { 60 | ns, importStmt := p.parseFileMeta() 61 | *result = append(*result, ns, importStmt) 62 | return 63 | } 64 | 65 | stmt := p.declaration() 66 | *result = append(*result, stmt) 67 | if !(p.match(lexer.NEWLINE) || p.isAtEnd()) { 68 | panic(ParseError{ 69 | token: p.peek(), 70 | message: "Expected new line", 71 | }) 72 | } 73 | } 74 | 75 | func (p *Parser) handleError(errors *[]ParseError) { 76 | if r := recover(); r != nil { 77 | switch err := r.(type) { 78 | case ParseError: 79 | *errors = append(*errors, err) 80 | break 81 | case []ParseError: 82 | *errors = append(*errors, err...) 83 | case error: 84 | *errors = append(*errors, ParseError{ 85 | token: p.previous(), 86 | message: err.Error(), 87 | }) 88 | default: 89 | *errors = append(*errors, ParseError{ 90 | token: p.previous(), 91 | message: "Invalid errors thrown by Parser: ", 92 | }) 93 | break 94 | } 95 | p.syncError() 96 | } 97 | } 98 | 99 | func (p *Parser) peek() Token { 100 | if p.current >= len(p.tokens) { 101 | return Token{ 102 | TokenType: lexer.EOF, 103 | } 104 | } 105 | return p.tokens[p.current] 106 | } 107 | 108 | func (p *Parser) previous() Token { 109 | return p.tokens[p.current-1] 110 | } 111 | 112 | func (p *Parser) isAtEnd() bool { 113 | if p.current == len(p.tokens) { 114 | return true 115 | } 116 | return p.peek().TokenType == lexer.EOF 117 | } 118 | 119 | func (p *Parser) check(tokenType TokenType) bool { 120 | return !p.isAtEnd() && p.peek().TokenType == tokenType 121 | } 122 | 123 | func (p *Parser) advance() Token { 124 | if !p.isAtEnd() { 125 | p.current++ 126 | } 127 | return p.previous() 128 | } 129 | func (p *Parser) reverse() { 130 | if p.current == 0 { 131 | return 132 | } 133 | p.current-- 134 | } 135 | 136 | func (p *Parser) match(types ...TokenType) bool { 137 | for _, t := range types { 138 | if p.check(t) { 139 | p.advance() 140 | return true 141 | } 142 | } 143 | return false 144 | } 145 | 146 | func (p *Parser) consume(tokenType TokenType, msg string) (token Token) { 147 | if p.check(tokenType) { 148 | token = p.advance() 149 | return 150 | } 151 | panic(ParseError{token: p.peek(), message: msg}) 152 | } 153 | 154 | func (p *Parser) consumeValidIdentifier(msg string) (token Token) { 155 | next := p.peek() 156 | nextType := next.TokenType 157 | if nextType != lexer.Identifier && nextType != lexer.Add && nextType != lexer.Subtract && nextType != lexer.Slash && nextType != lexer.Multiply { 158 | panic(ParseError{token: p.peek(), message: msg}) 159 | } 160 | p.advance() 161 | return next 162 | } 163 | 164 | func (p *Parser) cleanNewLines() { 165 | for p.match(lexer.NEWLINE) { 166 | } 167 | } 168 | func (p *Parser) insert(index int, value ...Token) { 169 | if len(p.tokens) == index { 170 | p.tokens = append(p.tokens, value...) 171 | } 172 | p.tokens = append(p.tokens[:index+len(value)], p.tokens[index:]...) 173 | for i := 0; i < len(value); i++ { 174 | p.tokens[index+i] = value[i] 175 | } 176 | } 177 | 178 | func (p *Parser) insertBlankType(index int, value ...TokenType) { 179 | blankTokens := make([]Token, len(value)) 180 | for i := range value { 181 | blankTokens[i] = Token{ 182 | TokenType: value[i], 183 | Text: nil, 184 | Position: lexer.CreatePosition(-1, 1), 185 | } 186 | } 187 | p.insert(index, blankTokens...) 188 | } 189 | 190 | func (p *Parser) syncError() { 191 | for !p.isAtEnd() && !p.check(lexer.NEWLINE) && !p.check(lexer.EOF) { 192 | p.advance() 193 | } 194 | p.cleanNewLines() 195 | } 196 | 197 | func (p *Parser) parseProperties(propTypes ...lexer.TokenType) []bool { 198 | result := make([]bool, len(propTypes)) 199 | for contains(propTypes, p.peek().TokenType) { 200 | tokTyp := p.advance().TokenType 201 | for i := 0; i < len(propTypes); i++ { 202 | if propTypes[i] == tokTyp { 203 | if result[i] { 204 | panic(ParseError{ 205 | token: p.previous(), 206 | message: "Multiple variable properties of same type defined", 207 | }) 208 | } 209 | result[i] = true 210 | break 211 | } 212 | } 213 | } 214 | return result 215 | } 216 | 217 | func contains(s []lexer.TokenType, e lexer.TokenType) bool { 218 | for _, a := range s { 219 | if a == e { 220 | return true 221 | } 222 | } 223 | return false 224 | } 225 | -------------------------------------------------------------------------------- /parser/statements.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import "github.com/ElaraLang/elara/lexer" 4 | 5 | type Stmt interface { 6 | stmtNode() 7 | } 8 | 9 | type ExpressionStmt struct { 10 | Expr Expr 11 | } 12 | 13 | type BlockStmt struct { 14 | Stmts []Stmt 15 | } 16 | 17 | type VarDefStmt struct { 18 | Mutable bool 19 | Lazy bool 20 | Restricted bool 21 | Identifier string 22 | Type Type 23 | Value Expr 24 | } 25 | 26 | type StructDefStmt struct { 27 | Identifier string 28 | StructFields []StructField 29 | } 30 | 31 | type IfElseStmt struct { 32 | Condition Expr 33 | MainBranch Stmt 34 | ElseBranch Stmt 35 | } 36 | 37 | type WhileStmt struct { 38 | Condition Expr 39 | Body Stmt 40 | } 41 | 42 | type ExtendStmt struct { 43 | Identifier string 44 | Body BlockStmt 45 | Alias string 46 | } 47 | type TypeStmt struct { 48 | Identifier string 49 | Contract Type 50 | } 51 | type GenerifiedStmt struct { 52 | Contracts []GenericContract 53 | Statement Stmt 54 | } 55 | 56 | type ReturnStmt struct { 57 | Returning Expr 58 | } 59 | 60 | func (ExpressionStmt) stmtNode() {} 61 | func (BlockStmt) stmtNode() {} 62 | func (VarDefStmt) stmtNode() {} 63 | func (StructDefStmt) stmtNode() {} 64 | func (IfElseStmt) stmtNode() {} 65 | func (WhileStmt) stmtNode() {} 66 | func (ExtendStmt) stmtNode() {} 67 | func (GenerifiedStmt) stmtNode() {} 68 | func (TypeStmt) stmtNode() {} 69 | func (ReturnStmt) stmtNode() {} 70 | 71 | func (p *Parser) declaration() (stmt Stmt) { 72 | if p.check(lexer.Let) { 73 | return p.varDefStatement() 74 | } 75 | return p.statement() 76 | } 77 | 78 | func (p *Parser) statement() Stmt { 79 | switch p.peek().TokenType { 80 | case lexer.While: 81 | return p.whileStatement() 82 | case lexer.If: 83 | return p.ifStatement() 84 | case lexer.LBrace: 85 | return p.blockStatement() 86 | case lexer.Struct: 87 | return p.structStatement() 88 | case lexer.Type: 89 | return p.typeStatement() 90 | case lexer.LAngle: 91 | return p.genericStatement() 92 | case lexer.Return: 93 | return p.returnStatement() 94 | case lexer.Extend: 95 | return p.extendStatement() 96 | default: 97 | return p.exprStatement() 98 | } 99 | } 100 | 101 | func (p *Parser) varDefStatement() Stmt { 102 | p.consume(lexer.Let, "Expected variable declaration to start with let") 103 | 104 | properties := p.parseProperties(lexer.Mut, lexer.Lazy, lexer.Restricted) 105 | mut := properties[0] 106 | lazy := properties[1] 107 | restricted := properties[2] 108 | 109 | id := p.consume(lexer.Identifier, "Expected identifier for variable declaration") 110 | var typ Type 111 | if p.match(lexer.Colon) { 112 | typ = p.typeContract() 113 | } 114 | 115 | switch p.peek().TokenType { 116 | case lexer.LParen: 117 | p.insertBlankType(p.current, lexer.Equal) 118 | break 119 | case lexer.Arrow: 120 | p.insertBlankType(p.current, lexer.Equal, lexer.LParen, lexer.RParen) 121 | break 122 | } 123 | p.consume(lexer.Equal, "Expected Equal on variable declaration") 124 | expr := p.expression() 125 | 126 | return VarDefStmt{ 127 | Mutable: mut, 128 | Lazy: lazy, 129 | Restricted: restricted, 130 | Identifier: string(id.Text), 131 | Type: typ, 132 | Value: expr, 133 | } 134 | } 135 | 136 | func (p *Parser) whileStatement() Stmt { 137 | p.consume(lexer.While, "Expected while at beginning of while loop") 138 | expr := p.expression() 139 | body := p.blockStatement() 140 | return WhileStmt{ 141 | Condition: expr, 142 | Body: body, 143 | } 144 | } 145 | 146 | func (p *Parser) ifStatement() (stmt Stmt) { 147 | p.consume(lexer.If, "Expected if at beginning of if statement") 148 | condition := p.logicalOr() 149 | p.cleanNewLines() 150 | mainBranch := p.blockStatement() 151 | p.cleanNewLines() 152 | 153 | var elseBranch Stmt 154 | if p.match(lexer.Else) { 155 | if p.check(lexer.If) { 156 | elseBranch = p.ifStatement() 157 | } else { 158 | elseBranch = p.blockStatement() 159 | } 160 | } 161 | stmt = IfElseStmt{ 162 | Condition: condition, 163 | MainBranch: mainBranch, 164 | ElseBranch: elseBranch, 165 | } 166 | return 167 | } 168 | 169 | func (p *Parser) blockStatement() BlockStmt { 170 | result := make([]Stmt, 0) 171 | errors := make([]ParseError, 0) 172 | p.consume(lexer.LBrace, "Expected { at beginning of block") 173 | p.cleanNewLines() 174 | for !p.check(lexer.RBrace) { 175 | declaration := p.blockedDeclaration(&errors) 176 | result = append(result, declaration) 177 | } 178 | p.consume(lexer.RBrace, "Expected } at end of block") 179 | if len(errors) > 0 { 180 | panic(errors) 181 | } 182 | return BlockStmt{Stmts: result} 183 | } 184 | 185 | func (p *Parser) blockedDeclaration(errors *[]ParseError) (s Stmt) { 186 | defer p.handleError(errors) 187 | s = p.declaration() 188 | 189 | //This is no longer guaranteed as if statements clean any new lines while looking for an else branch 190 | //nxt := p.peek() 191 | //if nxt.TokenType != lexer.NEWLINE && nxt.TokenType != lexer.RBrace { 192 | // panic("Expected newline after declaration in block") 193 | //} 194 | p.cleanNewLines() 195 | 196 | return s 197 | } 198 | 199 | func (p *Parser) structStatement() Stmt { 200 | p.consume(lexer.Struct, "Expected struct start to begin with `struct` keyword") 201 | return StructDefStmt{ 202 | Identifier: string(p.consume(lexer.Identifier, "Expected identifier after `struct` keyword").Text), 203 | StructFields: p.structFields(), 204 | } 205 | } 206 | 207 | func (p *Parser) returnStatement() Stmt { 208 | p.consume(lexer.Return, "Expected return") 209 | var expr Expr 210 | if p.peek().TokenType != lexer.NEWLINE { 211 | expr = p.expression() 212 | } 213 | return ReturnStmt{Returning: expr} 214 | } 215 | 216 | func (p *Parser) exprStatement() Stmt { 217 | return ExpressionStmt{Expr: p.expression()} 218 | } 219 | 220 | func (p *Parser) extendStatement() Stmt { 221 | p.consume(lexer.Extend, "Expected 'extend'") 222 | id := p.consumeValidIdentifier("Expected struct name to extend") 223 | alias := "this" // 224 | next := p.peek() 225 | if next.TokenType == lexer.As { 226 | p.advance() 227 | alias = string(p.consume(lexer.Identifier, "Expected identifier for extend alias").Text) 228 | } 229 | return ExtendStmt{ 230 | Identifier: string(id.Text), 231 | Body: p.blockStatement(), 232 | Alias: alias, 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /parser/struct.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import "github.com/ElaraLang/elara/lexer" 4 | 5 | type StructField struct { 6 | Mutable bool 7 | Identifier string 8 | FieldType *Type 9 | Default Expr 10 | } 11 | 12 | func (p *Parser) structFields() (fields []StructField) { 13 | p.consume(lexer.LBrace, "Expected '{' at struct field start") 14 | 15 | fields = make([]StructField, 0) 16 | p.cleanNewLines() 17 | for !p.check(lexer.RBrace) { 18 | field := p.structField() 19 | fields = append(fields, *field) 20 | if !p.match(lexer.NEWLINE) && !p.check(lexer.RBrace) { 21 | panic(ParseError{ 22 | token: p.previous(), 23 | message: "Expected newline after struct field", 24 | }) 25 | } 26 | } 27 | p.consume(lexer.RBrace, "Expected '}' at struct def end") 28 | return 29 | } 30 | 31 | func (p *Parser) structField() (field *StructField) { 32 | mutable := p.match(lexer.Mut) 33 | t1 := p.advance() 34 | t2 := p.advance() 35 | var typ Type 36 | var identifier string 37 | var def Expr 38 | if t1.TokenType == lexer.Identifier { 39 | switch t2.TokenType { 40 | case lexer.Identifier: 41 | typ = ElementaryTypeContract{Identifier: string(t1.Text)} 42 | identifier = string(t2.Text) 43 | if p.match(lexer.Equal) { 44 | def = p.logicalOr() 45 | } 46 | break 47 | case lexer.Equal: 48 | identifier = string(t2.Text) 49 | def = p.logicalOr() 50 | break 51 | default: 52 | panic(ParseError{ 53 | token: t1, 54 | message: "Invalid struct field", 55 | }) 56 | } 57 | } else { 58 | panic(ParseError{ 59 | token: t1, 60 | message: "Invalid struct field", 61 | }) 62 | } 63 | return &StructField{ 64 | Mutable: mutable, 65 | Identifier: identifier, 66 | FieldType: &typ, 67 | Default: def, 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /parser/type.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import "github.com/ElaraLang/elara/lexer" 4 | 5 | type Type interface { 6 | typeOf() 7 | } 8 | 9 | type DefinedTypeContract struct { 10 | DefType []DefinedType 11 | Name string 12 | } 13 | type ElementaryTypeContract struct { 14 | Identifier string 15 | } 16 | type InvocableTypeContract struct { 17 | Args []Type 18 | ReturnType Type 19 | } 20 | 21 | type BinaryTypeContract struct { 22 | Lhs Type 23 | TypeOp TokenType 24 | Rhs Type 25 | } 26 | 27 | type CollectionTypeContract struct { 28 | ElemType Type 29 | } 30 | 31 | type MapTypeContract struct { 32 | KeyType Type 33 | ValueType Type 34 | } 35 | 36 | func (p *Parser) typeContract() (contract Type) { 37 | return p.contractualOr(false) 38 | } 39 | 40 | func (p *Parser) typeContractDefinable() (contract Type) { 41 | return p.contractualOr(true) 42 | } 43 | 44 | func (p *Parser) contractualOr(allowDef bool) (contract Type) { 45 | contract = p.contractualAnd(allowDef) 46 | for p.match(lexer.TypeOr) { 47 | op := p.previous() 48 | rhs := p.contractualAnd(allowDef) 49 | contract = BinaryTypeContract{ 50 | Lhs: contract, 51 | TypeOp: op.TokenType, 52 | Rhs: rhs, 53 | } 54 | } 55 | return 56 | } 57 | 58 | func (p *Parser) contractualAnd(allowDef bool) (contract Type) { 59 | contract = p.primaryContract(allowDef) 60 | 61 | for p.match(lexer.TypeAnd) { 62 | op := p.previous() 63 | rhs := p.primaryContract(allowDef) 64 | 65 | contract = BinaryTypeContract{ 66 | Lhs: contract, 67 | TypeOp: op.TokenType, 68 | Rhs: rhs, 69 | } 70 | } 71 | return 72 | } 73 | 74 | func (p *Parser) primaryContract(allowDef bool) (contract Type) { 75 | if p.peek().TokenType == lexer.LSquare { 76 | p.advance() 77 | collType := p.consume(lexer.Identifier, "Expected identifier after [ for collection type") 78 | p.consume(lexer.RSquare, "Expected ] after [ for collection type") 79 | return CollectionTypeContract{ElemType: ElementaryTypeContract{Identifier: string(collType.Text)}} 80 | } 81 | if p.peek().TokenType == lexer.Identifier { 82 | name := string(p.advance().Text) 83 | return ElementaryTypeContract{Identifier: name} 84 | } else if p.check(lexer.LParen) { 85 | isFunc := p.isFuncDef() 86 | if isFunc { 87 | args := make([]Type, 0) 88 | p.consume(lexer.LParen, "Function type args not started properly with '('") 89 | 90 | for !p.check(lexer.RParen) { 91 | argTyp := p.typeContract() 92 | args = append(args, argTyp) 93 | if !p.match(lexer.Comma) { 94 | break 95 | } 96 | } 97 | p.consume(lexer.RParen, "Function type args not ended properly with ')'") 98 | p.consume(lexer.Arrow, "Expected arrow after function type args") 99 | 100 | ret := p.typeContract() 101 | return InvocableTypeContract{ 102 | Args: args, 103 | ReturnType: ret, 104 | } 105 | } else { 106 | contract = p.contractualOr(allowDef) 107 | 108 | p.consume(lexer.LParen, "contract group not closed. Expected '}'") 109 | return 110 | } 111 | } 112 | if p.peek().TokenType == lexer.LBrace { 113 | p.advance() 114 | //Peek until reaching a closing brace 115 | count := 0 116 | seenColon := false 117 | for { 118 | count++ 119 | next := p.advance().TokenType 120 | if next == lexer.Colon { 121 | seenColon = true 122 | } 123 | if next == lexer.RBrace { 124 | break 125 | } 126 | } 127 | for i := 0; i < count; i++ { 128 | p.reverse() 129 | } 130 | if !seenColon { //it's not a map type 131 | p.reverse() // Undo the lbrace read 132 | return p.definedContract(allowDef) 133 | } 134 | 135 | keyType := p.typeContract() 136 | p.consume(lexer.Colon, "Expected colon in map type") 137 | valueType := p.typeContract() 138 | 139 | p.consume(lexer.RBrace, "Expected closing brace for map type contract") 140 | 141 | return MapTypeContract{ 142 | KeyType: keyType, 143 | ValueType: valueType, 144 | } 145 | } 146 | return p.definedContract(allowDef) 147 | } 148 | 149 | func (p *Parser) definedContract(allowDef bool) (contract Type) { 150 | if allowDef && p.check(lexer.LBrace) { 151 | defTyp := p.definedTypes() 152 | return DefinedTypeContract{DefType: defTyp} 153 | } 154 | panic(ParseError{ 155 | token: p.previous(), 156 | message: "Invalid type contract", 157 | }) 158 | } 159 | 160 | func (t ElementaryTypeContract) typeOf() {} 161 | func (t BinaryTypeContract) typeOf() {} 162 | func (t InvocableTypeContract) typeOf() {} 163 | func (t DefinedTypeContract) typeOf() {} 164 | func (t CollectionTypeContract) typeOf() {} 165 | func (t MapTypeContract) typeOf() {} 166 | -------------------------------------------------------------------------------- /samples/fizzbuzz.elr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElaraLang/elara-go/3a081e05d31225d45cece44b93282afe0d99c585/samples/fizzbuzz.elr -------------------------------------------------------------------------------- /tests/elara_benchmark_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "github.com/ElaraLang/elara/base" 5 | "testing" 6 | ) 7 | 8 | func BenchmarkElara(b *testing.B) { 9 | code := `namespace test/lol 10 | import elara/std 11 | 12 | struct Person { 13 | String name 14 | Int age 15 | } 16 | 17 | let daveFactory = () => { Person("Dave", 50) } 18 | 19 | let produceDaves(Int amount) => { 20 | let factories = [daveFactory] * amount 21 | factories.map(run) 22 | } 23 | 24 | let daves = produceDaves(2147483647) 25 | ` 26 | base.LoadStdLib() 27 | for i := 0; i < b.N; i++ { 28 | base.Execute(nil, code, false) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/execution_benchmark_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "github.com/ElaraLang/elara/base" 5 | "testing" 6 | ) 7 | 8 | var code = `let b = if true => "yes" else => "no" 9 | b` 10 | 11 | func BenchmarkSimpleExecution(b *testing.B) { 12 | 13 | for n := 0; n < b.N; n++ { 14 | res, _, _, _ := base.Execute(nil, code, false) 15 | if res[1].Value != "yes" { 16 | b.Fail() 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/variable_assignment_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "github.com/ElaraLang/elara/base" 5 | "github.com/ElaraLang/elara/interpreter" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | func TestSimpleVariableAssignment(t *testing.T) { 11 | code := `let a = 3 12 | a` 13 | results, _, _, _ := base.Execute(nil, code, false) 14 | expectedResults := []*interpreter.Value{ 15 | nil, 16 | interpreter.IntValue(3), 17 | } 18 | 19 | if !reflect.DeepEqual(results, expectedResults) { 20 | t.Errorf("Incorrect parsing output, got %v but expected %v", formatValues(results), formatValues(expectedResults)) 21 | } 22 | } 23 | 24 | func TestSimpleVariableAssignmentWithType(t *testing.T) { 25 | code := `let a: Int = 3 26 | a` 27 | results, _, _, _ := base.Execute(nil, code, false) 28 | expectedResults := []*interpreter.Value{ 29 | nil, 30 | interpreter.IntValue(3), 31 | } 32 | 33 | if !reflect.DeepEqual(results, expectedResults) { 34 | t.Errorf("Incorrect parsing output, got %v but expected %v", formatValues(results), formatValues(expectedResults)) 35 | } 36 | } 37 | 38 | func TestSimpleVariableAssignmentWithTypeAndInvalidValue(t *testing.T) { 39 | defer func() { 40 | if r := recover(); r == nil { 41 | t.Errorf("Intepreter allows assignment of incorrect types / values") 42 | } 43 | }() 44 | 45 | code := `let a: Int = 3.5 46 | a` 47 | base.Execute(nil, code, false) 48 | } 49 | 50 | func formatValues(values []*interpreter.Value) string { 51 | str := "[" 52 | for i, value := range values { 53 | formatted := value.String() 54 | if formatted != "" { 55 | str += formatted 56 | } else { 57 | str += "" 58 | } 59 | if i != len(values)-1 { 60 | str += ", " 61 | } 62 | } 63 | str += "]" 64 | 65 | return str 66 | } 67 | -------------------------------------------------------------------------------- /tests/variable_reassignment_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "github.com/ElaraLang/elara/base" 5 | "github.com/ElaraLang/elara/interpreter" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | func TestSimpleVariableReassignment(t *testing.T) { 11 | code := `let mut a = 3 12 | a = 4 13 | a` 14 | results, _, _, _ := base.Execute(nil, code, false) 15 | expectedResults := []*interpreter.Value{ 16 | nil, 17 | nil, 18 | interpreter.IntValue(4), 19 | } 20 | 21 | if !reflect.DeepEqual(results, expectedResults) { 22 | t.Errorf("Incorrect parsing output, got %v but expected %v", formatValues(results), formatValues(expectedResults)) 23 | } 24 | } 25 | 26 | func TestSimpleVariableReassignmentWithType(t *testing.T) { 27 | code := `let mut a: Int = 3 28 | a = 4 29 | a` 30 | results, _, _, _ := base.Execute(nil, code, false) 31 | expectedResults := []*interpreter.Value{ 32 | nil, 33 | nil, 34 | interpreter.IntValue(4), 35 | } 36 | 37 | if !reflect.DeepEqual(results, expectedResults) { 38 | t.Errorf("Incorrect parsing output, got %v but expected %v", formatValues(results), formatValues(expectedResults)) 39 | } 40 | } 41 | 42 | func TestSimpleVariableReassignmentWithTypeAndInvalidValue(t *testing.T) { 43 | defer func() { 44 | if r := recover(); r == nil { 45 | t.Errorf("Intepreter allows reassignment of incorrect types / values") 46 | } 47 | }() 48 | 49 | code := `let mut a: Int = 3 50 | a = 3.5` 51 | base.Execute(nil, code, false) 52 | } 53 | 54 | func TestSimpleVariableReassignmentWithImmutableVariable(t *testing.T) { 55 | defer func() { 56 | if r := recover(); r == nil { 57 | t.Errorf("Intepreter allows reassignment of immutable variable") 58 | } 59 | }() 60 | 61 | code := `let a = 3 62 | a = 4` 63 | results, _, _, _ := base.Execute(nil, code, false) 64 | 65 | expectedResults := []*interpreter.Value{ 66 | nil, 67 | interpreter.IntValue(4), 68 | } 69 | if !reflect.DeepEqual(results, expectedResults) { 70 | t.Errorf("Incorrect parsing output, got %v but expected %v", formatValues(results), formatValues(expectedResults)) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /typer/typer.go: -------------------------------------------------------------------------------- 1 | package typer 2 | 3 | import "github.com/ElaraLang/elara/parser" 4 | 5 | type Typer struct { 6 | Input []parser.Stmt 7 | } 8 | 9 | func (t *Typer) HandleTyping() { 10 | // Pass 1 - Scanning for types 11 | types := t.scanForUserDefinedTypes() 12 | // Pass 2 - Scanning for function return types 13 | functionReturns := t.scanForFunctionReturns() 14 | } 15 | 16 | func (t *Typer) scanForUserDefinedTypes() []parser.Type { 17 | types := make([]parser.Type, 0) 18 | var cur parser.Stmt 19 | for index := range t.Input { 20 | cur = t.Input[index] 21 | switch cur.(type) { 22 | case parser.StructDefStmt: 23 | structDef := cur.(parser.StructDefStmt) 24 | appendType(&types, &structDef) 25 | } 26 | } 27 | return types 28 | } 29 | 30 | func (t *Typer) scanForFunctionReturns() []parser.Type { 31 | 32 | } 33 | 34 | func appendType(types *[]parser.Type, p *parser.StructDefStmt) { 35 | fields := make([]parser.DefinedType, 0) 36 | for fieldIndex := range p.StructFields { 37 | curField := p.StructFields[fieldIndex] 38 | field := parser.DefinedType{ 39 | Identifier: curField.Identifier, 40 | DefType: *curField.FieldType, 41 | } 42 | fields = append(fields, field) 43 | } 44 | *types = append(*types, parser.DefinedTypeContract{DefType: fields, Name: p.Identifier}) 45 | } 46 | -------------------------------------------------------------------------------- /util/strings.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "hash/fnv" 6 | "reflect" 7 | "strconv" 8 | ) 9 | 10 | func NillableStringify(nillableStr *string, defaultStr string) string { 11 | if nillableStr == nil { 12 | return defaultStr 13 | } 14 | return *nillableStr 15 | } 16 | 17 | func Stringify(s interface{}) string { 18 | switch t := s.(type) { 19 | case Stringable: 20 | return t.String() 21 | case string: 22 | return t 23 | case rune: 24 | return string(t) 25 | case int: 26 | return strconv.Itoa(t) 27 | case int64: 28 | return strconv.FormatInt(t, 10) 29 | case uint: 30 | return strconv.FormatUint(uint64(t), 10) 31 | case float64: 32 | return strconv.FormatFloat(t, 'f', -1, 64) 33 | case bool: 34 | return strconv.FormatBool(t) 35 | } 36 | 37 | return fmt.Sprintf("%s: %s", reflect.TypeOf(s), s) 38 | } 39 | 40 | type Stringable interface { 41 | String() string 42 | } 43 | 44 | func Hash(s string) uint64 { 45 | h := fnv.New64a() 46 | _, _ = h.Write([]byte(s)) 47 | return h.Sum64() 48 | } 49 | --------------------------------------------------------------------------------