├── .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 |
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 |
--------------------------------------------------------------------------------