├── .gitattributes
├── .github
└── workflows
│ └── goreleaser.yml
├── .gitignore
├── .goreleaser.yaml
├── .vscode
└── launch.json
├── LICENSE
├── README.md
├── assets
└── WindLogo.png
├── ast
├── ast.go
├── expressions.go
└── statements.go
├── cmd
├── root.go
├── run.go
└── vm.go
├── compiler
└── compiler.go
├── evaluator
├── builtins.go
├── env.go
├── envManager.go
├── evaluator.go
├── evaluator_test.go
├── object.go
├── objectArray.go
├── objectString.go
├── stdLib.go
├── stdLibMath.go
└── stdLibRequest.go
├── go.mod
├── go.sum
├── lexer
├── lexer.go
└── lexer_test.go
├── main.go
├── opcode
└── opcode.go
├── parser
└── parser.go
├── token
├── token.go
└── tokenTypes.go
├── value
└── value.go
├── vm
├── env.go
├── stack.go
└── vm.go
└── vscode-extention
├── .vscode
└── launch.json
├── .vscodeignore
├── CHANGELOG.md
├── README.md
├── assets
└── WindLogo.png
├── language-configuration.json
├── package.json
├── syntaxes
└── windlang.tmLanguage.json
└── vsc-extension-quickstart.md
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.github/workflows/goreleaser.yml:
--------------------------------------------------------------------------------
1 | name: goreleaser
2 |
3 | on:
4 | push:
5 | branches:
6 | - "main"
7 |
8 | tags:
9 | - "*"
10 |
11 | pull_request:
12 |
13 | permissions:
14 | contents: write
15 |
16 | jobs:
17 | goreleaser:
18 | runs-on: ubuntu-latest
19 | steps:
20 | - name: Checkout
21 | uses: actions/checkout@v2
22 | with:
23 | fetch-depth: 0
24 |
25 | - name: Set up Go
26 | uses: actions/setup-go@v2
27 | with:
28 | go-version: "1.19"
29 |
30 | - name: Tests
31 | run: go test -v ./...
32 |
33 | - name: Run GoReleaser
34 | uses: goreleaser/goreleaser-action@v2
35 | with:
36 | distribution: goreleaser
37 | version: latest
38 | args: release --rm-dist
39 | env:
40 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
41 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | main.wind
2 | main.exe
3 | test.py
4 | node_modules
5 | *.vsix
6 | windlang
7 | main.py
8 |
--------------------------------------------------------------------------------
/.goreleaser.yaml:
--------------------------------------------------------------------------------
1 | build:
2 | goos:
3 | - windows
4 | - linux
5 | - darwin
6 |
--------------------------------------------------------------------------------
/.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": "main.go",
13 | "env": {},
14 | "args": ["vm", "main.wind"]
15 | }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 joetifa2003
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | # WindLang, A simple programming language built with golang 🍃
6 |
7 | [](https://goreportcard.com/report/github.com/joetifa2003/windlang)
8 |
9 | - [WindLang, A simple programming language built with golang 🍃](#windlang-a-simple-programming-language-built-with-golang-)
10 | - [What is wind?](#what-is-wind)
11 | - [Playground](#playground)
12 | - [Cool but why?](#cool-but-why)
13 | - [Why Golang, not Rust?](#why-golang-not-rust)
14 | - [How to use it?](#how-to-use-it)
15 | - [So what can it do?](#so-what-can-it-do)
16 | - [Hello world?](#hello-world)
17 | - [Variables](#variables)
18 | - [Data types](#data-types)
19 | - [Arrays](#arrays)
20 | - [Array.push(element) -> any[]](#arraypushelement---any)
21 | - [Array.pop() -> any](#arraypop---any)
22 | - [Array.removeAt(index) -> any](#arrayremoveatindex---any)
23 | - [Array.len() -> int](#arraylen---int)
24 | - [Array.join(separator) -> string](#arrayjoinseparator---string)
25 | - [Array.map(function) -> any[]](#arraymapfunction---any)
26 | - [Array.filter(function) -> any[]](#arrayfilterfunction---any)
27 | - [Array.reduce(fn(accumulator, element), initialValue) -> any](#arrayreducefnaccumulator-element-initialvalue---any)
28 | - [Array.contains(function) -> boolean](#arraycontainsfunction---boolean)
29 | - [Array.count(function) -> int](#arraycountfunction---int)
30 | - [Array.clone() -> any[]](#arrayclone---any)
31 | - [Strings](#strings)
32 | - [String.len(separator) -> int](#stringlenseparator---int)
33 | - [String.charAt(index) -> string](#stringcharatindex---string)
34 | - [String.contains(substr) -> boolean](#stringcontainssubstr---boolean)
35 | - [String.containsAny(substr) -> boolean](#stringcontainsanysubstr---boolean)
36 | - [String.count(substr) -> string](#stringcountsubstr---string)
37 | - [String.replace(old, new) -> string](#stringreplaceold-new---string)
38 | - [String.replaceAll(old, new) -> string](#stringreplaceallold-new---string)
39 | - [String.replaceN(old, new, n) -> string](#stringreplacenold-new-n---string)
40 | - [String.changeAt(index, new) -> string](#stringchangeatindex-new---string)
41 | - [String.indexOf(substr) -> int](#stringindexofsubstr---int)
42 | - [String.lastIndexOf(substr) -> int](#stringlastindexofsubstr---int)
43 | - [String.split(separator) -> string[]](#stringsplitseparator---string)
44 | - [String.trim() -> string](#stringtrim---string)
45 | - [String.toLowerCase() -> string](#stringtolowercase---string)
46 | - [String.toUpperCase() -> string](#stringtouppercase---string)
47 | - [Functions](#functions)
48 | - [Closures](#closures)
49 | - [If expressions](#if-expressions)
50 | - [Include statement](#include-statement)
51 | - [For loops](#for-loops)
52 | - [While loops](#while-loops)
53 | - [HashMaps](#hashmaps)
54 | - [Todos](#todos)
55 |
56 | ## What is wind?
57 |
58 | Wind is an interpreted language written in golang.
59 |
60 | ## Playground
61 |
62 | The easiest way to play with WindLang is to use the [playground](https://windlang.vercel.app/playground)
63 |
64 | ## Cool but why?
65 |
66 | I'm working on this as a side project and it's the 5th attempt at writing my own programming language, And it got me my first job too! it's awesome 💙
67 |
68 | ## Why Golang, not Rust?
69 |
70 | I already tried to build this with Rust, but I battled a lot with the Rust compiler and felt like golang is a better middle ground I got so much faster with golang, and it was surprising that the golang implementation was actually faster!
71 | Not because golang is faster than rust, it's my lack of knowledge, and the way I code is not "Rusty"
72 |
73 | ## How to use it?
74 |
75 | Head up to the [releases](https://github.com/joetifa2003/windlang/releases) page and download Wind for your operating system
76 |
77 | And execute windlang executable
78 |
79 | ```
80 | WindLang, A simple programming language written in golang
81 |
82 | Usage:
83 | windlang [command]
84 |
85 | Available Commands:
86 | completion Generate the autocompletion script for the specified shell
87 | help Help about any command
88 | run Run a Wind script
89 |
90 | Flags:
91 | -h, --help help for windlang
92 | -t, --toggle Help message for toggle
93 |
94 | Use "windlang [command] --help" for more information about a command.
95 | ```
96 |
97 | This is the Wind cli you can use the run command to run a Wind script file
98 | Install the vscode extension [here](https://marketplace.visualstudio.com/items?itemName=YoussefAhmed.windlang)!
99 |
100 | ## So what can it do?
101 |
102 | Let me demonstrate the features implemented as of now
103 |
104 | ### Hello world?
105 |
106 | ```swift
107 | println("Hello world");
108 | ```
109 |
110 | Yes, Wind can print Hello world, Surprising huh?
111 |
112 | ### Variables
113 |
114 | ```swift
115 | let x = 1;
116 | println(x); // 1
117 |
118 |
119 | x = "Hello world";
120 | println(x); // Hello World
121 | ```
122 |
123 | You can declare variables in Wind with the `let` keyword, Variables in wind are dynamically typed and you can reassign them to any type.
124 |
125 | ### Data types
126 |
127 | ```swift
128 | 1 // int
129 | 3.14 // float
130 | true, false // boolean
131 | nil // null
132 | "Hello World" // String
133 | [1, "2", true] // Arrays
134 | {
135 | "name": "Youssef",
136 | "age": 18
137 | } // HashMaps
138 | ```
139 |
140 | ### Arrays
141 |
142 | ```swift
143 | let arr = [1, "2", true];
144 |
145 | println(arr[0]); // 1
146 | println(arr[1]); // 2
147 | println(arr[2]); // true
148 |
149 | arr.push("3") // [1, "2", true, "3"]
150 | println(arr[3]); // 3
151 | arr.pop() // [1, "2", true]
152 | ```
153 |
154 | Arrays in Wind can take any type of data, and can be of any size
155 | You can append to an array by using the `append` function
156 | You can remove an element by index using the `remove` function.
157 |
158 | #### Array.push(element) -> any[]
159 |
160 | ```swift
161 | let x = [1, 2, 3];
162 | x.push(4) // [1, 2, 3, 4]
163 | ```
164 |
165 | Array push function adds an element to the end of the array
166 |
167 | #### Array.pop() -> any
168 |
169 | ```swift
170 | let x = [1, 2, 3, 4];
171 | x.push(4); // [1, 2, 3, 4]
172 |
173 | let y = x.pop(); // [1, 2, 3]
174 | println(y); // 4
175 | ```
176 |
177 | Array pop function removes the last element of the array and returns it
178 |
179 | #### Array.removeAt(index) -> any
180 |
181 | ```swift
182 | let x = [1, 2, 3, 4];
183 | x.push(4); // [1, 2, 3, 4]
184 |
185 | let y = x.removeAt(3); // [1, 2, 3]
186 | println(y); // 4
187 | ```
188 |
189 | Array removeAt function removes an element at the index of the array and returns it
190 |
191 | #### Array.len() -> int
192 |
193 | ```swift
194 | let x = [1, "hi", true];
195 | println(x.len()); // 3
196 | ```
197 |
198 | Array len function returns the length of the array
199 |
200 | #### Array.join(separator) -> string
201 |
202 | ```swift
203 | let x = [1, "hi", 3, 4];
204 |
205 | println(x.join("-")); // 1-hi-3-4
206 | ```
207 |
208 | Array join function returns a string with all the elements of the array separated by the separator
209 |
210 | #### Array.map(function) -> any[]
211 |
212 | ```swift
213 | let x = [1, 2, 3];
214 |
215 | println(x.map(fn(x) {x * 2})); // [2, 4, 6]
216 | ```
217 |
218 | Array map function applies the function to each element of the array and returns a new array with the results
219 |
220 | #### Array.filter(function) -> any[]
221 |
222 | ```swift
223 | let x = [1, 2, 3, 4];
224 | let even = x.filter(fn(x) { x % 2 == 0});
225 |
226 | println(even); // [2, 4]]
227 | ```
228 |
229 | Array filter function applies the function to each element of the array and if the function returns true, the element is added to the new array
230 |
231 | #### Array.reduce(fn(accumulator, element), initialValue) -> any
232 |
233 | ```swift
234 | let x = [1, 2, 3, 4, 5];
235 | let sum = x.reduce(fn(acc, x) { acc + x}, 0);
236 |
237 | println(sum); // 15
238 | ```
239 |
240 | Array reduce function applies the function to each element of the array and returns a single value
241 |
242 | #### Array.contains(function) -> boolean
243 |
244 | ```swift
245 | let arr = [1, 3, 5];
246 | println(arr.contains(fn(x) { x % 2 == 0})); // false
247 |
248 | arr = [1, 2, 3, 4, 5];
249 | println(arr.contains(fn(x) { x % 2 == 0})); // true
250 | ```
251 |
252 | Array contains function applies the function to each element of the array and returns true if the function returns true for any element
253 |
254 | #### Array.count(function) -> int
255 |
256 | ```swift
257 | let arr = [1, 3, 5];
258 | println(arr.count(fn(x) { x % 2 == 0})); // 0
259 |
260 | arr = [1, 2, 3, 4, 5];
261 | println(arr.count(fn(x) { x % 2 == 0})); // 2
262 | ```
263 |
264 | Array count function applies the function to each element of the array and returns the number of times the function returns true
265 |
266 | #### Array.clone() -> any[]
267 |
268 | ```swift
269 | let x = [1, 2, 3];
270 | let notCloned = x;
271 | let cloned = x.clone();
272 |
273 | x[0] = "changed";
274 | println(notCloned[0]); // Changed
275 | println(cloned[0]); // 1
276 | ```
277 |
278 | Array clone function returns a copy of the array
279 |
280 | ### Strings
281 |
282 | Strings in Wind start and end with a double quote `"` and can contain any character and can be multi-line
283 |
284 | #### String.len(separator) -> int
285 |
286 | ```swift
287 | let x = "Hello";
288 |
289 | println(x.len()); // 5
290 | ```
291 |
292 | String len function returns the length of the string
293 |
294 | #### String.charAt(index) -> string
295 |
296 | ```swift
297 | let name = "youssef";
298 | println(name.charAt(0)); // y
299 | ```
300 |
301 | String charAt function returns the character at the specified index
302 |
303 | #### String.contains(substr) -> boolean
304 |
305 | ```swift
306 | let name = "youssef";
307 |
308 | println(name.contains("ss")); // true
309 | ```
310 |
311 | String contains function returns true if the string contains the exact substring
312 |
313 | #### String.containsAny(substr) -> boolean
314 |
315 | ```swift
316 | let vowels = "aeiou";
317 | let name = "youssef";
318 |
319 | println(name.containsAny(vowels)); // true
320 | ```
321 |
322 | String contains function returns true if the string contains any character of the substring
323 |
324 | #### String.count(substr) -> string
325 |
326 | ```swift
327 | let name = "youssef";
328 |
329 | println(name.count("s")); // 2
330 | ```
331 |
332 | String count function returns the number of times the substring appears in the string
333 |
334 | #### String.replace(old, new) -> string
335 |
336 | ```swift
337 | let name = "John Doe";
338 |
339 | println(name.replace("o", "x")); // Jxhn Doe
340 | ```
341 |
342 | String replace function returns a new string after replacing one old substring with a new substring
343 |
344 | #### String.replaceAll(old, new) -> string
345 |
346 | ```swift
347 | let name = "John Doe";
348 |
349 | println(name.replaceAll("o", "x")); // Jxhn Dxe
350 | ```
351 |
352 | String replace function returns a new string after replacing all old substring with a new substring
353 |
354 | #### String.replaceN(old, new, n) -> string
355 |
356 | ```swift
357 | let name = "Youssef";
358 |
359 | println(name.replaceN("s", "x", 1)); // Youxsef
360 | println(name.replaceN("s", "x", 2)); // Youxxef
361 | ```
362 |
363 | String replace function returns a new string after replacing n of old substring with a new substring
364 |
365 | #### String.changeAt(index, new) -> string
366 |
367 | ```swift
368 | let name = "Ahmed";
369 |
370 | println(name.changeAt(0, "a")); // ahmed
371 | ```
372 |
373 | String changeAt function returns a new string after changing the character at the specified index
374 |
375 | #### String.indexOf(substr) -> int
376 |
377 | ```swift
378 | let name = "John Doe";
379 |
380 | println(name.indexOf("o")); // 1
381 | ```
382 |
383 | String indexOf function returns the index of the first occurrence of the substring
384 |
385 | #### String.lastIndexOf(substr) -> int
386 |
387 | ```swift
388 | let name = "John Doe";
389 |
390 | println(name.lastIndexOf("o")); // 6
391 | ```
392 |
393 | String indexOf function returns the index of the last occurrence of the substring
394 |
395 | #### String.split(separator) -> string[]
396 |
397 | ```swift
398 | let name = "Youssef Ahmed";
399 | let names = name.split(" "); // ["Youssef", "Ahmed"]
400 | let firstName = names[0];
401 | let lastName = names[1];
402 |
403 | println("First name is: " + firstName); // First name is: Youssef
404 | println("Last name is: " + lastName); // Last name is: Ahmed
405 | ```
406 |
407 | String join function returns an array of strings by splitting the string by the separator
408 |
409 | #### String.trim() -> string
410 |
411 | ```swift
412 | let name = " John Doe ";
413 |
414 | println(name.trim()); // "John Doe"
415 | ```
416 |
417 | String trim function removes whitespace from the start/end of the string and returns a new string
418 |
419 | #### String.toLowerCase() -> string
420 |
421 | ```swift
422 | let name = "JoHn dOe";
423 |
424 | println(name.toLowerCase()); // john doe
425 | ```
426 |
427 | String toLowerCase function returns a new string with all the characters in lower case
428 |
429 | #### String.toUpperCase() -> string
430 |
431 | ```swift
432 | let name = "JoHn dOe";
433 |
434 | println(name.toUpperCase()); // JOHN DOE
435 | ```
436 |
437 | String toLowerCase function returns a new string with all the characters in upper case
438 |
439 | ### Functions
440 |
441 | ```swift
442 | let add = fn(x, y) { x + y; };
443 |
444 |
445 | // This is the same as
446 | let add = fn(x, y) {
447 | return x + y;
448 | };
449 |
450 | println(add(1, 2)); // 3
451 | ```
452 |
453 | Yes, this looks like Rust functions. The last expression in a function is implicitly returned
454 |
455 | ### Closures
456 |
457 | ```swift
458 | let addConstructor = fn(x) {
459 | fn(y) {
460 | x + y;
461 | };
462 | };
463 |
464 |
465 | // This is the same as
466 | let addConstructor = fn(x) {
467 | return fn(y) {
468 | return x + y;
469 | };
470 | };
471 |
472 |
473 | let addTwo = addConstructor(2); // This will return a function
474 |
475 | println(addTwo(3)); // 5
476 | ```
477 |
478 | This is one of the coolest things implemented in Wind. As I said functions in Wind are expressions, so you can return a function or pass a function to another function.
479 |
480 | ```swift
481 | let welcome = fn(name, greeter) {
482 | greeter() + " " + name
483 | };
484 |
485 | let greeter = fn() { "Hello 👋"};
486 |
487 | println(welcome("Wind 🍃", greeter)); // Hello 👋 Wind 🍃
488 | ```
489 |
490 | ### If expressions
491 |
492 | ```swift
493 | // Use if as an expression
494 | let grade = 85;
495 | let msg = if (grade > 50) { "You passed" } else { "You didn't pass" };
496 |
497 | println(msg); // You passed
498 |
499 | // Or use if as a statement
500 | if (grade > 50) {
501 | println("You passed");
502 | } else {
503 | println("You didn't pass");
504 | }
505 | ```
506 |
507 | As you can see here we can use it as an expression or as a statement
508 |
509 | Note that after any expression the semicolon is optional. We can type `"You passed";` or `"You passed"` and it will implicitly return it
510 |
511 | ### Include statement
512 |
513 | ```swift
514 | // test.wind
515 |
516 | let msg = "Hello 👋";
517 |
518 | let greeter = fn() {
519 | println(msg);
520 | };
521 | ```
522 |
523 | ```swift
524 | // main.wind
525 | include "./test.wind";
526 |
527 | greeter(); // Hello 👋
528 |
529 | // You can also alias includes
530 | include "./test.wind" as test;
531 |
532 | test.greeter(); // Hello 👋
533 | ```
534 |
535 | Include statements allow you to include other Wind scripts, It initializes them once and can be used by multiple files at the same time while preserving state.
536 |
537 | ### For loops
538 |
539 | ```swift
540 | let names = ["Youssef", "Ahmed", "Soren", "Morten", "Mads", "Jakob"];
541 |
542 | for (let i = 0; i < len(names); i++) {
543 | println("Hello " + names[i]);
544 | }
545 |
546 | // Hello Youssef
547 | // Hello Ahmed
548 | // Hello Soren
549 | // Hello Morten
550 | // Hello Mads
551 | // Hello Jakob
552 | ```
553 |
554 | ### While loops
555 |
556 | ```swift
557 | let x = 0;
558 |
559 | while (x < 5) {
560 | println(x);
561 | x++;
562 | }
563 |
564 | // 0
565 | // 1
566 | // 2
567 | // 3
568 | // 4
569 | ```
570 |
571 | ### HashMaps
572 |
573 | ```swift
574 | let person = {
575 | "name": "Youssef",
576 | "age": 18,
577 | "incrementAge": fn() {
578 | person.age++;
579 | }
580 | };
581 |
582 | println(person["name"]); // Youssef
583 | println(person.age); // 18
584 | person.incrementAge();
585 | println(person.age); // 19
586 | ```
587 |
588 | Hashmaps are like js object and can store key value pairs, Keys can be integers, strings and booleans. Values can be any type.
589 |
590 | ## Todos
591 |
592 | - ~~Named include statements~~
593 |
594 | - ~~HashMaps (Javascript objects~~
595 |
596 | - A bytecode interpreter maybe
597 |
--------------------------------------------------------------------------------
/assets/WindLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joetifa2003/windlang/babe562548e63133938c9da64cfba1760fb9030f/assets/WindLogo.png
--------------------------------------------------------------------------------
/ast/ast.go:
--------------------------------------------------------------------------------
1 | package ast
2 |
3 | type Node interface {
4 | TokenLiteral() string
5 | String() string
6 | }
7 |
8 | type Statement interface{ Node }
9 | type Expression interface{ Node }
10 |
--------------------------------------------------------------------------------
/ast/expressions.go:
--------------------------------------------------------------------------------
1 | package ast
2 |
3 | import (
4 | "bytes"
5 | "strings"
6 |
7 | "github.com/joetifa2003/windlang/token"
8 | )
9 |
10 | type Identifier struct {
11 | Expression
12 |
13 | Token token.Token // the token.IDENT token
14 | Value string
15 | }
16 |
17 | func (i *Identifier) TokenLiteral() string { return i.Token.Literal }
18 | func (i *Identifier) String() string { return i.Value }
19 |
20 | type IntegerLiteral struct {
21 | Expression
22 |
23 | Token token.Token
24 | Value int
25 | }
26 |
27 | func (il *IntegerLiteral) TokenLiteral() string { return il.Token.Literal }
28 | func (il *IntegerLiteral) String() string { return il.TokenLiteral() }
29 |
30 | type FloatLiteral struct {
31 | Expression
32 |
33 | Token token.Token
34 | Value float64
35 | }
36 |
37 | func (fl *FloatLiteral) TokenLiteral() string { return fl.Token.Literal }
38 | func (fl *FloatLiteral) String() string { return fl.TokenLiteral() }
39 |
40 | type Boolean struct {
41 | Expression
42 |
43 | Token token.Token
44 | Value bool
45 | }
46 |
47 | func (b *Boolean) TokenLiteral() string { return b.Token.Literal }
48 | func (b *Boolean) String() string { return b.Token.Literal }
49 |
50 | type PrefixExpression struct {
51 | Expression
52 |
53 | Token token.Token // The prefix token, e.g. !
54 | Operator string
55 | Right Expression
56 | }
57 |
58 | func (pe *PrefixExpression) TokenLiteral() string { return pe.Token.Literal }
59 | func (pe *PrefixExpression) String() string {
60 | var out bytes.Buffer
61 | out.WriteString("(")
62 | out.WriteString(pe.Operator)
63 | out.WriteString(pe.Right.String())
64 | out.WriteString(")")
65 | return out.String()
66 | }
67 |
68 | type InfixExpression struct {
69 | Expression
70 |
71 | Token token.Token // The operator token, e.g. +
72 | Left Expression
73 | Operator string
74 | Right Expression
75 | }
76 |
77 | func (oe *InfixExpression) TokenLiteral() string { return oe.Token.Literal }
78 | func (oe *InfixExpression) String() string {
79 | var out bytes.Buffer
80 | out.WriteString("(")
81 | out.WriteString(oe.Left.String())
82 | out.WriteString(" " + oe.Operator + " ")
83 | out.WriteString(oe.Right.String())
84 | out.WriteString(")")
85 | return out.String()
86 | }
87 |
88 | type IfExpression struct {
89 | Expression
90 |
91 | Token token.Token // The 'if' token
92 | Condition Expression
93 | ThenBranch Statement
94 | ElseBranch Statement
95 | }
96 |
97 | func (ie *IfExpression) TokenLiteral() string { return ie.Token.Literal }
98 | func (ie *IfExpression) String() string {
99 | var out bytes.Buffer
100 | out.WriteString("if")
101 | out.WriteString(ie.Condition.String())
102 | out.WriteString(" ")
103 | out.WriteString(ie.ThenBranch.String())
104 |
105 | if ie.ElseBranch != nil {
106 | out.WriteString("else ")
107 | out.WriteString(ie.ElseBranch.String())
108 | }
109 |
110 | return out.String()
111 | }
112 |
113 | type FunctionLiteral struct {
114 | Expression
115 |
116 | Token token.Token // The 'fn' token
117 | Parameters []*Identifier
118 | Body *BlockStatement
119 | }
120 |
121 | func (fl *FunctionLiteral) TokenLiteral() string { return fl.Token.Literal }
122 | func (fl *FunctionLiteral) String() string {
123 | var out bytes.Buffer
124 |
125 | params := []string{}
126 | for _, p := range fl.Parameters {
127 | params = append(params, p.String())
128 | }
129 |
130 | out.WriteString(fl.TokenLiteral())
131 | out.WriteString("(")
132 | out.WriteString(strings.Join(params, ", "))
133 | out.WriteString(") ")
134 | out.WriteString(fl.Body.String())
135 |
136 | return out.String()
137 | }
138 |
139 | type CallExpression struct {
140 | Expression
141 |
142 | Token token.Token // The '(' token
143 | Function Expression // Identifier or FunctionLiteral
144 | Arguments []Expression
145 | }
146 |
147 | func (ce *CallExpression) TokenLiteral() string { return ce.Token.Literal }
148 | func (ce *CallExpression) String() string {
149 | var out bytes.Buffer
150 | args := []string{}
151 |
152 | for _, a := range ce.Arguments {
153 | args = append(args, a.String())
154 | }
155 |
156 | out.WriteString(ce.Function.String())
157 | out.WriteString("(")
158 | out.WriteString(strings.Join(args, ", "))
159 | out.WriteString(")")
160 |
161 | return out.String()
162 | }
163 |
164 | type StringLiteral struct {
165 | Expression
166 |
167 | Token token.Token
168 | Value string
169 | }
170 |
171 | func (sl *StringLiteral) TokenLiteral() string { return sl.Token.Literal }
172 | func (sl *StringLiteral) String() string { return sl.Token.Literal }
173 |
174 | type PostfixExpression struct {
175 | Expression
176 |
177 | Token token.Token
178 | Left Expression
179 | Operator string
180 | }
181 |
182 | func (pe *PostfixExpression) TokenLiteral() string { return pe.Token.Literal }
183 | func (pe *PostfixExpression) String() string {
184 | var out bytes.Buffer
185 |
186 | out.WriteString("(")
187 | out.WriteString(pe.Left.String())
188 | out.WriteString(" " + pe.Operator + ")")
189 |
190 | return out.String()
191 | }
192 |
193 | type AssignExpression struct {
194 | Expression
195 |
196 | Token token.Token
197 | Name Expression
198 | Value Expression
199 | }
200 |
201 | func (ae *AssignExpression) TokenLiteral() string { return ae.Token.Literal }
202 | func (ae *AssignExpression) String() string {
203 | var out bytes.Buffer
204 |
205 | out.WriteString("(")
206 | out.WriteString(ae.Name.String())
207 | out.WriteString(" = " + ae.Value.String() + ")")
208 |
209 | return out.String()
210 | }
211 |
212 | type ArrayLiteral struct {
213 | Expression
214 |
215 | Token token.Token
216 | Value []Expression
217 | }
218 |
219 | func (ae *ArrayLiteral) TokenLiteral() string { return ae.Token.Literal }
220 | func (a *ArrayLiteral) Inspect() string {
221 | var out bytes.Buffer
222 |
223 | out.WriteString("[")
224 | for _, obj := range a.Value {
225 | out.WriteString(obj.String())
226 | out.WriteString(",")
227 | }
228 | out.WriteString("]")
229 |
230 | return out.String()
231 | }
232 |
233 | type IndexExpression struct {
234 | Expression
235 |
236 | Token token.Token
237 | Left Expression
238 | Index Expression
239 | }
240 |
241 | func (ie *IndexExpression) TokenLiteral() string { return ie.Token.Literal }
242 | func (ie *IndexExpression) String() string {
243 | var out bytes.Buffer
244 |
245 | out.WriteString("(")
246 | out.WriteString(ie.Left.String())
247 | out.WriteString("[")
248 | out.WriteString(ie.Index.String())
249 | out.WriteString("])")
250 |
251 | return out.String()
252 | }
253 |
254 | type NilLiteral struct{ Expression, Token token.Token }
255 |
256 | func (ne *NilLiteral) TokenLiteral() string { return ne.Token.Literal }
257 | func (ne *NilLiteral) String() string { return "nil" }
258 |
259 | type HashLiteral struct {
260 | Expression
261 |
262 | Token token.Token
263 | Pairs map[Expression]Expression
264 | }
265 |
266 | func (hl *HashLiteral) TokenLiteral() string { return hl.Token.Literal }
267 | func (hl *HashLiteral) String() string { return "hash" }
268 |
--------------------------------------------------------------------------------
/ast/statements.go:
--------------------------------------------------------------------------------
1 | package ast
2 |
3 | import (
4 | "bytes"
5 |
6 | "github.com/joetifa2003/windlang/token"
7 | )
8 |
9 | type Program struct {
10 | Statements []Statement
11 | }
12 |
13 | func (p *Program) TokenLiteral() string {
14 | if len(p.Statements) > 0 {
15 | return p.Statements[0].TokenLiteral()
16 | } else {
17 | return ""
18 | }
19 | }
20 |
21 | func (p *Program) String() string {
22 | var out bytes.Buffer
23 |
24 | for _, s := range p.Statements {
25 | out.WriteString(s.String())
26 | out.WriteString("\n")
27 | }
28 |
29 | return out.String()
30 | }
31 |
32 | type LetStatement struct {
33 | Statement
34 |
35 | Token token.Token // the token.LET token
36 | Name *Identifier
37 | Value Expression
38 | Constant bool
39 | }
40 |
41 | func (ls *LetStatement) TokenLiteral() string { return ls.Token.Literal }
42 | func (ls *LetStatement) String() string {
43 | var out bytes.Buffer
44 |
45 | out.WriteString(ls.TokenLiteral() + " ")
46 | out.WriteString(ls.Name.String())
47 | out.WriteString(" = ")
48 |
49 | if ls.Value != nil {
50 | out.WriteString(ls.Value.String())
51 | }
52 |
53 | out.WriteString(";")
54 | return out.String()
55 | }
56 |
57 | type ReturnStatement struct {
58 | Statement
59 |
60 | Token token.Token // the 'return' token
61 | ReturnValue Expression
62 | }
63 |
64 | func (rs *ReturnStatement) TokenLiteral() string { return rs.Token.Literal }
65 | func (rs *ReturnStatement) String() string {
66 | var out bytes.Buffer
67 |
68 | out.WriteString(rs.TokenLiteral() + " ")
69 | if rs.ReturnValue != nil {
70 | out.WriteString(rs.ReturnValue.String())
71 | }
72 |
73 | out.WriteString(";")
74 | return out.String()
75 | }
76 |
77 | type ExpressionStatement struct {
78 | Statement
79 |
80 | Token token.Token // the first token of the expression
81 | Expression Expression
82 | }
83 |
84 | func (es *ExpressionStatement) TokenLiteral() string { return es.Token.Literal }
85 | func (es *ExpressionStatement) String() string {
86 | if es.Expression != nil {
87 | return es.Expression.String()
88 | }
89 |
90 | return ""
91 | }
92 |
93 | type BlockStatement struct {
94 | Statement
95 |
96 | Token token.Token // the { token
97 | Statements []Statement
98 | VarCount int
99 | }
100 |
101 | func (bs *BlockStatement) TokenLiteral() string { return bs.Token.Literal }
102 | func (bs *BlockStatement) String() string {
103 | var out bytes.Buffer
104 |
105 | for _, s := range bs.Statements {
106 | out.WriteString(s.String())
107 | }
108 |
109 | return out.String()
110 | }
111 |
112 | type ForStatement struct {
113 | Statement
114 |
115 | Token token.Token // the 'for' token
116 | Initializer Statement
117 | Condition Expression
118 | Increment Expression
119 | Body Statement
120 | }
121 |
122 | func (fs *ForStatement) TokenLiteral() string { return fs.Token.Literal }
123 | func (fs *ForStatement) String() string {
124 | return ""
125 | }
126 |
127 | type IncludeStatement struct {
128 | Statement
129 |
130 | Token token.Token // the 'include' token
131 | Path string
132 | Alias *Identifier
133 | }
134 |
135 | func (is *IncludeStatement) TokenLiteral() string { return is.Token.Literal }
136 | func (is *IncludeStatement) String() string {
137 | var out bytes.Buffer
138 |
139 | out.WriteString(is.TokenLiteral() + " ")
140 | out.WriteString(is.Path)
141 | out.WriteString(";")
142 |
143 | return out.String()
144 | }
145 |
146 | type WhileStatement struct {
147 | Statement
148 |
149 | Token token.Token
150 | Condition Expression
151 | Body Statement
152 | }
153 |
154 | func (ws *WhileStatement) TokenLiteral() string { return ws.Token.Literal }
155 | func (ws *WhileStatement) String() string {
156 | return ""
157 | }
158 |
159 | type EchoStatement struct {
160 | Statement
161 |
162 | Token token.Token
163 | Value Expression
164 | }
165 |
166 | func (es *EchoStatement) TokenLiteral() string { return es.Token.Literal }
167 | func (es *EchoStatement) String() string { return "" }
168 |
--------------------------------------------------------------------------------
/cmd/root.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "os"
5 |
6 | "github.com/spf13/cobra"
7 | )
8 |
9 | var rootCmd = &cobra.Command{
10 | Use: "windlang",
11 | Short: "WindLang, A simple programming language written in golang",
12 | }
13 |
14 | func Execute() {
15 | err := rootCmd.Execute()
16 | if err != nil {
17 | os.Exit(1)
18 | }
19 | }
20 |
21 | func init() {
22 | rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
23 | }
24 |
--------------------------------------------------------------------------------
/cmd/run.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "os"
7 |
8 | "github.com/joetifa2003/windlang/evaluator"
9 | "github.com/joetifa2003/windlang/lexer"
10 | "github.com/joetifa2003/windlang/parser"
11 |
12 | "github.com/spf13/cobra"
13 | )
14 |
15 | // runCmd represents the run command
16 | var runCmd = &cobra.Command{
17 | Use: "run [file]",
18 | Short: "Run a Wind script using Tree Walking interpreter",
19 | Args: func(cmd *cobra.Command, args []string) error {
20 | if len(args) != 1 {
21 | return fmt.Errorf("requires 1 argument")
22 | }
23 |
24 | return nil
25 | },
26 | Run: func(cmd *cobra.Command, args []string) {
27 | filePath := args[0]
28 | if filePath == "" {
29 | log.Fatal("File path is required")
30 | }
31 |
32 | file, err := os.ReadFile(filePath)
33 | if err != nil {
34 | log.Fatalln("Could not read file:", err)
35 | return
36 | }
37 |
38 | input := string(file)
39 | lexer := lexer.New(input)
40 | parser := parser.New(lexer, filePath)
41 | program := parser.ParseProgram()
42 | parserErrors := parser.ReportErrors()
43 | if len(parserErrors) > 0 {
44 | for _, err := range parserErrors {
45 | fmt.Println(err)
46 | }
47 |
48 | os.Exit(1)
49 | }
50 |
51 | envManager := evaluator.NewEnvironmentManager()
52 | env, _ := envManager.Get(filePath)
53 | ev := evaluator.New(envManager, filePath)
54 | evaluated, evErr := ev.Eval(program, env, nil)
55 | if evErr != nil {
56 | fmt.Println(evErr.Inspect())
57 | }
58 |
59 | if evaluated == nil {
60 | return
61 | }
62 | },
63 | }
64 |
65 | func init() {
66 | rootCmd.AddCommand(runCmd)
67 | }
68 |
--------------------------------------------------------------------------------
/cmd/vm.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "os"
7 |
8 | "github.com/joetifa2003/windlang/compiler"
9 | "github.com/joetifa2003/windlang/lexer"
10 | "github.com/joetifa2003/windlang/parser"
11 | "github.com/joetifa2003/windlang/vm"
12 | "github.com/spf13/cobra"
13 | )
14 |
15 | var (
16 | debug = false
17 | )
18 |
19 | var vmCommand = &cobra.Command{
20 | Use: "vm [file]",
21 | Short: "Run a Wind script using vm",
22 | Args: func(cmd *cobra.Command, args []string) error {
23 | if len(args) != 1 {
24 | return fmt.Errorf("requires 1 argument")
25 | }
26 |
27 | return nil
28 | },
29 | Run: func(cmd *cobra.Command, args []string) {
30 | filePath := args[0]
31 |
32 | file, err := os.ReadFile(filePath)
33 | if err != nil {
34 | log.Fatalln("Could not read file:", err)
35 | return
36 | }
37 |
38 | // defer profile.Start().Stop()
39 | input := string(file)
40 | lexer := lexer.New(input)
41 | parser := parser.New(lexer, filePath)
42 | program := parser.ParseProgram()
43 | parserErrors := parser.ReportErrors()
44 | if len(parserErrors) > 0 {
45 | for _, err := range parserErrors {
46 | fmt.Println(err)
47 | }
48 |
49 | os.Exit(1)
50 | }
51 |
52 | compiler := compiler.NewCompiler()
53 | instructions := compiler.Compile(program)
54 |
55 | virtualM := vm.NewVM(compiler.Constants)
56 | virtualM.Interpret(instructions)
57 | },
58 | }
59 |
60 | func init() {
61 | vmCommand.PersistentFlags().BoolVarP(&debug, "debug", "d", false, "Show debug info")
62 | rootCmd.AddCommand(vmCommand)
63 | }
64 |
--------------------------------------------------------------------------------
/compiler/compiler.go:
--------------------------------------------------------------------------------
1 | package compiler
2 |
3 | import (
4 | "github.com/joetifa2003/windlang/ast"
5 | "github.com/joetifa2003/windlang/opcode"
6 | "github.com/joetifa2003/windlang/value"
7 | )
8 |
9 | type Compiler struct {
10 | Scopes [][]string
11 | Constants []value.Value
12 | }
13 |
14 | func NewCompiler() Compiler {
15 | return Compiler{
16 | Scopes: [][]string{},
17 | Constants: []value.Value{},
18 | }
19 | }
20 |
21 | func (c *Compiler) addConstant(v value.Value) int {
22 | c.Constants = append(c.Constants, v)
23 |
24 | return len(c.Constants) - 1
25 | }
26 |
27 | func (c *Compiler) beginScope() {
28 | c.Scopes = append(c.Scopes, []string{})
29 | }
30 |
31 | func (c *Compiler) endScope() []string {
32 | lastScope := c.Scopes[len(c.Scopes)-1]
33 | c.Scopes = c.Scopes[:len(c.Scopes)-1]
34 |
35 | return lastScope
36 | }
37 |
38 | func (c *Compiler) addToScope(name string) int {
39 | scope := &c.Scopes[len(c.Scopes)-1]
40 | *scope = append(*scope, name)
41 |
42 | return len(*scope) - 1
43 | }
44 |
45 | // returns scope index and the value index inside it
46 | func (c *Compiler) findInScope(name string) (int, int) {
47 | for scopesIndex := len(c.Scopes) - 1; scopesIndex >= 0; scopesIndex-- {
48 | for valueIndex, v := range c.Scopes[scopesIndex] {
49 | if v == name {
50 | return scopesIndex, valueIndex
51 | }
52 | }
53 | }
54 |
55 | panic("")
56 | }
57 |
58 | func (c *Compiler) Compile(node ast.Node) []opcode.OpCode {
59 | switch node := node.(type) {
60 | case *ast.Program:
61 | var instructions []opcode.OpCode
62 |
63 | c.beginScope()
64 | programInstructions := append(instructions, c.CompileProgram(node.Statements)...)
65 | scope := c.endScope()
66 |
67 | instructions = append(instructions, opcode.OP_BLOCK)
68 | instructions = append(instructions, opcode.OpCode(len(scope)))
69 | instructions = append(instructions, programInstructions...)
70 | instructions = append(instructions, opcode.OP_END_BLOCK)
71 |
72 | return instructions
73 |
74 | case *ast.ExpressionStatement:
75 | expr := c.Compile(node.Expression)
76 | expr = append(expr, opcode.OP_POP)
77 |
78 | return expr
79 |
80 | case *ast.InfixExpression:
81 | var instructions []opcode.OpCode
82 |
83 | left := c.Compile(node.Left)
84 | right := c.Compile(node.Right)
85 |
86 | instructions = append(instructions, left...)
87 | instructions = append(instructions, right...)
88 |
89 | switch node.Operator {
90 | case "+":
91 | instructions = append(instructions, opcode.OP_ADD)
92 | case "-":
93 | instructions = append(instructions, opcode.OP_SUBTRACT)
94 | case "*":
95 | instructions = append(instructions, opcode.OP_MULTIPLY)
96 | case "/":
97 | instructions = append(instructions, opcode.OP_DIVIDE)
98 | case "<=":
99 | instructions = append(instructions, opcode.OP_LESSEQ)
100 | case "%":
101 | instructions = append(instructions, opcode.OP_MODULO)
102 | case "==":
103 | instructions = append(instructions, opcode.OP_EQ)
104 |
105 | default:
106 | panic("Unimplemented operator " + node.Operator)
107 | }
108 |
109 | return instructions
110 |
111 | case *ast.IfExpression:
112 | var instructions []opcode.OpCode
113 |
114 | condition := c.Compile(node.Condition)
115 | thenBranch := c.Compile(node.ThenBranch)
116 | elseBranch := []opcode.OpCode{}
117 | if node.ElseBranch != nil {
118 | elseBranch = c.Compile(node.ElseBranch)
119 | }
120 |
121 | instructions = append(instructions, condition...)
122 | instructions = append(instructions, opcode.OP_JUMP_FALSE)
123 | instructions = append(instructions, opcode.OpCode(len(thenBranch)+3))
124 | instructions = append(instructions, thenBranch...)
125 | instructions = append(instructions, opcode.OP_JUMP)
126 | instructions = append(instructions, opcode.OpCode(len(elseBranch)+1))
127 | instructions = append(instructions, elseBranch...)
128 |
129 | return instructions
130 |
131 | case *ast.IntegerLiteral:
132 | return []opcode.OpCode{
133 | opcode.OP_CONST,
134 | opcode.OpCode(
135 | c.addConstant(value.NewIntValue(node.Value)),
136 | ),
137 | }
138 |
139 | case *ast.Boolean:
140 | return []opcode.OpCode{
141 | opcode.OP_CONST,
142 | opcode.OpCode(
143 | c.addConstant(value.NewBoolValue(node.Value)),
144 | ),
145 | }
146 |
147 | case *ast.BlockStatement:
148 | var instructions []opcode.OpCode
149 | var bodyInstructions []opcode.OpCode
150 |
151 | if node.VarCount != 0 {
152 | c.beginScope()
153 | for _, stmt := range node.Statements {
154 | bodyInstructions = append(bodyInstructions, c.Compile(stmt)...)
155 | }
156 | scope := c.endScope()
157 |
158 | instructions = append(instructions, opcode.OP_BLOCK)
159 | instructions = append(instructions, opcode.OpCode(len(scope)))
160 | instructions = append(instructions, bodyInstructions...)
161 | instructions = append(instructions, opcode.OP_END_BLOCK)
162 |
163 | return instructions
164 | } else {
165 | for _, stmt := range node.Statements {
166 | bodyInstructions = append(bodyInstructions, c.Compile(stmt)...)
167 | }
168 |
169 | return bodyInstructions
170 | }
171 |
172 | case *ast.WhileStatement:
173 | var instructions []opcode.OpCode
174 |
175 | condition := c.Compile(node.Condition)
176 | body := c.Compile(node.Body)
177 |
178 | instructions = append(instructions, condition...)
179 | instructions = append(instructions, opcode.OP_JUMP_FALSE)
180 | instructions = append(instructions, opcode.OpCode(len(body)+3))
181 | instructions = append(instructions, body...)
182 | instructions = append(instructions, opcode.OP_JUMP)
183 | instructions = append(instructions, opcode.OpCode(-len(body)-len(condition)-3))
184 |
185 | return instructions
186 |
187 | case *ast.ForStatement:
188 | var bodyInstructions []opcode.OpCode
189 | var instructions []opcode.OpCode
190 |
191 | c.beginScope()
192 | initializer := c.Compile(node.Initializer)
193 | condition := c.Compile(node.Condition)
194 | body := c.Compile(node.Body)
195 | increment := c.Compile(node.Increment)
196 | increment = append(increment, opcode.OP_POP)
197 | scope := c.endScope()
198 |
199 | bodyInstructions = append(bodyInstructions, initializer...)
200 | bodyInstructions = append(bodyInstructions, condition...)
201 | bodyInstructions = append(bodyInstructions, opcode.OP_JUMP_FALSE)
202 | bodyInstructions = append(bodyInstructions, opcode.OpCode(len(body)+len(increment)+3))
203 | bodyInstructions = append(bodyInstructions, body...)
204 | bodyInstructions = append(bodyInstructions, increment...)
205 | bodyInstructions = append(bodyInstructions, opcode.OP_JUMP)
206 | bodyInstructions = append(bodyInstructions, opcode.OpCode(-len(body)-len(increment)-len(condition)-3))
207 |
208 | instructions = append(instructions, opcode.OP_BLOCK)
209 | instructions = append(instructions, opcode.OpCode(len(scope)))
210 | instructions = append(instructions, bodyInstructions...)
211 | instructions = append(instructions, opcode.OP_END_BLOCK)
212 |
213 | return instructions
214 |
215 | case *ast.LetStatement:
216 | var instructions []opcode.OpCode
217 |
218 | value := c.Compile(node.Value)
219 | index := c.addToScope(node.Name.Value)
220 | instructions = append(instructions, value...)
221 | instructions = append(instructions, opcode.OP_LET)
222 | instructions = append(instructions, opcode.OpCode(index))
223 |
224 | return instructions
225 |
226 | case *ast.Identifier:
227 | scopeIndex, valueIndex := c.findInScope(node.Value)
228 | return []opcode.OpCode{
229 | opcode.OP_GET,
230 | opcode.OpCode(valueIndex),
231 | opcode.OpCode(scopeIndex),
232 | }
233 |
234 | case *ast.AssignExpression:
235 | var instructions []opcode.OpCode
236 | scopeIndex, valueIndex := c.findInScope(node.Name.TokenLiteral())
237 | value := c.Compile(node.Value)
238 | instructions = append(instructions, value...)
239 | instructions = append(instructions,
240 | opcode.OP_SET,
241 | opcode.OpCode(valueIndex),
242 | opcode.OpCode(scopeIndex),
243 | )
244 |
245 | return instructions
246 |
247 | case *ast.PostfixExpression:
248 | var instructions []opcode.OpCode
249 | scopeIndex, valueIndex := c.findInScope(node.Left.TokenLiteral())
250 | instructions = append(instructions,
251 | opcode.OP_INC,
252 | opcode.OpCode(valueIndex),
253 | opcode.OpCode(scopeIndex),
254 | )
255 |
256 | return instructions
257 |
258 | case *ast.EchoStatement:
259 | var instructions []opcode.OpCode
260 |
261 | instructions = append(instructions, c.Compile(node.Value)...)
262 | instructions = append(instructions, opcode.OP_ECHO)
263 |
264 | return instructions
265 |
266 | case *ast.NilLiteral:
267 | return []opcode.OpCode{opcode.OP_CONST, opcode.OpCode(c.addConstant(value.NewNilValue()))}
268 |
269 | case *ast.ArrayLiteral:
270 | instructions := []opcode.OpCode{}
271 |
272 | for i := len(node.Value) - 1; i >= 0; i-- {
273 | instructions = append(instructions, c.Compile(node.Value[i])...)
274 | }
275 |
276 | instructions = append(instructions, opcode.OP_ARRAY, opcode.OpCode(len(node.Value)))
277 |
278 | return instructions
279 |
280 | default:
281 | panic("Unimplemented Ast %d")
282 | }
283 | }
284 |
285 | func (c *Compiler) CompileProgram(statements []ast.Statement) []opcode.OpCode {
286 | var instructions []opcode.OpCode
287 | for _, stmt := range statements {
288 | instructions = append(instructions, c.Compile(stmt)...)
289 | }
290 |
291 | return instructions
292 | }
293 |
--------------------------------------------------------------------------------
/evaluator/builtins.go:
--------------------------------------------------------------------------------
1 | package evaluator
2 |
3 | import (
4 | "bufio"
5 | "fmt"
6 | "os"
7 |
8 | "github.com/joetifa2003/windlang/ast"
9 | )
10 |
11 | var builtins = map[string]*GoFunction{
12 | "println": {
13 | ArgsCount: -1,
14 | Fn: func(evaluator *Evaluator, node *ast.CallExpression, args ...Object) (Object, *Error) {
15 | argsString := []interface{}{}
16 | for _, arg := range args {
17 | argsString = append(argsString, arg.Inspect())
18 | }
19 |
20 | fmt.Println(argsString...)
21 |
22 | return NIL, nil
23 | },
24 | },
25 | "print": {
26 | ArgsCount: -1,
27 | Fn: func(evaluator *Evaluator, node *ast.CallExpression, args ...Object) (Object, *Error) {
28 | argsString := []interface{}{}
29 | for _, arg := range args {
30 | argsString = append(argsString, arg.Inspect())
31 | }
32 |
33 | fmt.Print(argsString...)
34 |
35 | return NIL, nil
36 | },
37 | },
38 | "string": {
39 | ArgsCount: 1,
40 | Fn: func(evaluator *Evaluator, node *ast.CallExpression, args ...Object) (Object, *Error) {
41 | switch arg := args[0].(type) {
42 | case *Integer:
43 | return &String{Value: fmt.Sprintf("%d", arg.Value)}, nil
44 | case *Float:
45 | return &String{Value: fmt.Sprintf("%f", arg.Value)}, nil
46 | }
47 |
48 | return nil, evaluator.newError(node.Token, "argument to `string` not supported")
49 | },
50 | },
51 | "input": {
52 | ArgsCount: -1,
53 | ArgsTypes: []ObjectType{StringObj},
54 | Fn: func(evaluator *Evaluator, node *ast.CallExpression, args ...Object) (Object, *Error) {
55 | if len(args) != 0 {
56 | prompt := args[0]
57 | fmt.Print(prompt.Inspect())
58 | }
59 |
60 | var input string
61 | scanner := bufio.NewScanner(os.Stdin)
62 | scanner.Scan()
63 | input = scanner.Text()
64 |
65 | return &String{Value: input}, nil
66 | },
67 | },
68 | }
69 |
--------------------------------------------------------------------------------
/evaluator/env.go:
--------------------------------------------------------------------------------
1 | package evaluator
2 |
3 | type Environment struct {
4 | Store map[string]Object
5 | ConstantStore map[string]Object
6 | Outer *Environment
7 | Includes []*Environment
8 | IncludesAliased map[string]*IncludeObject
9 | }
10 |
11 | func NewEnvironment() *Environment {
12 | return &Environment{
13 | Store: nil,
14 | ConstantStore: nil,
15 | Outer: nil,
16 | Includes: nil,
17 | IncludesAliased: nil,
18 | }
19 | }
20 |
21 | func NewEnclosedEnvironment(outer *Environment) *Environment {
22 | env := NewEnvironment()
23 | env.Outer = outer
24 |
25 | return env
26 | }
27 |
28 | func (e *Environment) Get(name string) (Object, bool) {
29 | if e.Store == nil && e.ConstantStore == nil {
30 | if e.Outer != nil {
31 | return e.Outer.Get(name)
32 | } else {
33 | return e.getIncludes(name)
34 | }
35 | }
36 |
37 | obj, ok := e.ConstantStore[name]
38 | if ok {
39 | return obj, ok
40 | }
41 |
42 | obj, ok = e.Store[name]
43 | if !ok {
44 | if e.Outer != nil {
45 | obj, ok = e.Outer.Get(name)
46 | } else {
47 | return e.getIncludes(name)
48 | }
49 | }
50 |
51 | return obj, ok
52 | }
53 |
54 | func (e *Environment) getIncludes(name string) (Object, bool) {
55 | aliasedInclude, ok := e.IncludesAliased[name]
56 | if ok {
57 | return aliasedInclude, true
58 | }
59 |
60 | for _, include := range e.Includes {
61 | if obj, ok := include.Store[name]; ok {
62 | return obj, ok
63 | }
64 | }
65 |
66 | return nil, false
67 | }
68 |
69 | // Set For assigning
70 | func (e *Environment) Set(name string, val Object) (Object, bool) {
71 | if e.Store == nil {
72 | if e.Outer != nil {
73 | return e.Outer.Set(name, val)
74 | } else {
75 | return nil, false
76 | }
77 | }
78 |
79 | _, ok := e.Store[name]
80 | if !ok {
81 | if e.Outer != nil {
82 | return e.Outer.Set(name, val)
83 | } else {
84 | return nil, false
85 | }
86 | }
87 |
88 | e.Store[name] = val
89 |
90 | return val, true
91 | }
92 |
93 | // Let For local scope variables
94 | func (e *Environment) Let(name string, val Object) Object {
95 | if e.Store == nil {
96 | e.Store = make(map[string]Object)
97 | }
98 |
99 | e.Store[name] = val
100 |
101 | return val
102 | }
103 |
104 | func (e *Environment) LetConstant(name string, val Object) Object {
105 | if e.ConstantStore == nil {
106 | e.ConstantStore = make(map[string]Object)
107 | }
108 |
109 | e.ConstantStore[name] = val
110 |
111 | return val
112 | }
113 |
114 | func (e *Environment) IsConstant(name string) bool {
115 | if e.ConstantStore == nil {
116 | if e.Outer != nil {
117 | return e.Outer.IsConstant(name)
118 | } else {
119 | return false
120 | }
121 | }
122 |
123 | _, ok := e.ConstantStore[name]
124 | if ok {
125 | return true
126 | } else {
127 | if e.Outer != nil {
128 | return e.Outer.IsConstant(name)
129 | } else {
130 | return false
131 | }
132 | }
133 | }
134 |
135 | func (e *Environment) ClearStore() {
136 | for k := range e.Store {
137 | delete(e.Store, k)
138 | }
139 | }
140 |
141 | func (e *Environment) AddAlias(name string, include *IncludeObject) {
142 | if e.IncludesAliased == nil {
143 | e.IncludesAliased = make(map[string]*IncludeObject)
144 | }
145 |
146 | e.IncludesAliased[name] = include
147 | }
148 |
--------------------------------------------------------------------------------
/evaluator/envManager.go:
--------------------------------------------------------------------------------
1 | package evaluator
2 |
3 | type EnvironmentManager struct {
4 | environments map[string]*Environment // A map of environments for each file
5 | }
6 |
7 | func NewEnvironmentManager() *EnvironmentManager {
8 | return &EnvironmentManager{
9 | environments: make(map[string]*Environment),
10 | }
11 | }
12 |
13 | // Get returns the environment for the given file name. and whither it's evaluated or not
14 | func (em *EnvironmentManager) Get(fileName string) (*Environment, bool) {
15 | env, ok := GetStdlib(fileName)
16 | if ok {
17 | em.environments[fileName] = env
18 | return env, true
19 | }
20 |
21 | env, ok = em.environments[fileName]
22 | if ok {
23 | return env, true
24 | }
25 |
26 | return em.createFileEnv(fileName), false
27 | }
28 |
29 | func (em *EnvironmentManager) createFileEnv(fileName string) *Environment {
30 | env := NewEnvironment()
31 | em.environments[fileName] = env
32 |
33 | return env
34 | }
35 |
--------------------------------------------------------------------------------
/evaluator/evaluator.go:
--------------------------------------------------------------------------------
1 | package evaluator
2 |
3 | import (
4 | "fmt"
5 | "io/ioutil"
6 | "math"
7 |
8 | "github.com/joetifa2003/windlang/ast"
9 | "github.com/joetifa2003/windlang/lexer"
10 | "github.com/joetifa2003/windlang/parser"
11 | "github.com/joetifa2003/windlang/token"
12 | )
13 |
14 | var (
15 | NIL = &Nil{}
16 | TRUE = &Boolean{Value: true}
17 | FALSE = &Boolean{Value: false}
18 | )
19 |
20 | type Evaluator struct {
21 | envManager *EnvironmentManager
22 | filePath string
23 | }
24 |
25 | func New(envManager *EnvironmentManager, filePath string) *Evaluator {
26 | return &Evaluator{
27 | envManager: envManager,
28 | filePath: filePath,
29 | }
30 | }
31 |
32 | // Eval returns the result of the evaluation and potential error
33 | func (e *Evaluator) Eval(node ast.Node, env *Environment, this Object) (Object, *Error) {
34 | switch node := node.(type) {
35 | case *ast.Program:
36 | return e.evalProgram(node.Statements, env, this)
37 |
38 | case *ast.BlockStatement:
39 | return e.evalBlockStatement(node, env, this)
40 |
41 | case *ast.ExpressionStatement:
42 | return e.Eval(node.Expression, env, this)
43 |
44 | case *ast.LetStatement:
45 | return e.evalLetStatement(node, env, this)
46 |
47 | case *ast.ReturnStatement:
48 | return e.evalReturnStatement(node, env, this)
49 |
50 | case *ast.ForStatement:
51 | return e.evalForStatement(node, env, this)
52 |
53 | case *ast.WhileStatement:
54 | return e.evalWhileStatement(node, env, this)
55 |
56 | case *ast.IncludeStatement:
57 | return e.evalIncludeStatement(node, env, this)
58 |
59 | // Expressions
60 | case *ast.IntegerLiteral:
61 | return Integer{Value: node.Value}, nil
62 |
63 | case *ast.FloatLiteral:
64 | return &Float{Value: node.Value}, nil
65 |
66 | case *ast.Boolean:
67 | return boolToBoolObject(node.Value), nil
68 |
69 | case *ast.PrefixExpression:
70 | return e.evalPrefixExpression(node, env, this)
71 |
72 | case *ast.InfixExpression:
73 | return e.evalInfixExpression(node, env, this)
74 |
75 | case *ast.PostfixExpression:
76 | return e.evalPostfixExpression(node, env, this)
77 |
78 | case *ast.IfExpression:
79 | return e.evalIfExpression(node, env, this)
80 |
81 | case *ast.Identifier:
82 | return e.evalIdentifier(node, env, this)
83 |
84 | case *ast.FunctionLiteral:
85 | return &Function{Parameters: node.Parameters, Body: node.Body, Env: env, This: this}, nil
86 |
87 | case *ast.CallExpression:
88 | return e.evalCallExpression(node, env, this)
89 |
90 | case *ast.StringLiteral:
91 | return &String{Value: node.Value}, nil
92 |
93 | case *ast.AssignExpression:
94 | return e.evalAssignExpression(node, env, this)
95 |
96 | case *ast.ArrayLiteral:
97 | return e.evalArrayLiteral(node, env, this)
98 |
99 | case *ast.IndexExpression:
100 | return e.evalIndexExpression(node, env, this)
101 |
102 | case *ast.NilLiteral:
103 | return NIL, nil
104 |
105 | case *ast.HashLiteral:
106 | return e.evalHashLiteral(node, env)
107 |
108 | case *ast.EchoStatement:
109 | val, err := e.Eval(node.Value, env, this)
110 | if err != nil {
111 | return nil, err
112 | }
113 |
114 | fmt.Println(val.Inspect())
115 | }
116 |
117 | return NIL, nil
118 | }
119 |
120 | func (e *Evaluator) evalCallExpression(node *ast.CallExpression, env *Environment, this Object) (Object, *Error) {
121 | function, err := e.Eval(node.Function, env, this)
122 | if err != nil {
123 | return nil, err
124 | }
125 |
126 | args, err := e.evalExpressions(node.Arguments, env, this)
127 | if err != nil {
128 | return nil, err
129 | }
130 |
131 | return e.applyFunction(node, function, args)
132 | }
133 |
134 | func (e *Evaluator) applyFunction(node *ast.CallExpression, fn Object, args []Object) (Object, *Error) {
135 | switch fn := fn.(type) {
136 | case *Function:
137 | if len(args) != len(fn.Parameters) {
138 | return nil, e.newError(node.Token, "expected %d arg(s) got %d", len(fn.Parameters), len(args))
139 | }
140 |
141 | extendedEnv := e.extendFunctionEnv(fn, args)
142 | evaluated, err := e.Eval(fn.Body, extendedEnv, fn.This)
143 | if err != nil {
144 | return nil, err
145 | }
146 |
147 | return unwrapReturnValue(evaluated), nil
148 |
149 | case *GoFunction:
150 | if fn.ArgsCount != -1 && len(args) != fn.ArgsCount {
151 | return nil, e.newError(node.Token, "expected %d arg(s) got %d", fn.ArgsCount, len(args))
152 | }
153 |
154 | for i, t := range fn.ArgsTypes {
155 | if t != Any && t != args[i].Type() {
156 | return nil, e.newError(node.Token, "expected arg %d to be of type %s got %s", i, t, args[i].Type())
157 | }
158 | }
159 |
160 | return fn.Fn(e, node, args...)
161 |
162 | default:
163 | return nil, e.newError(node.Token, "not a function: %s", fn.Inspect())
164 | }
165 |
166 | }
167 |
168 | func (e *Evaluator) extendFunctionEnv(
169 | fn *Function,
170 | args []Object,
171 | ) *Environment {
172 | env := NewEnclosedEnvironment(fn.Env)
173 |
174 | for paramIdx, param := range fn.Parameters {
175 | env.Let(param.Value, args[paramIdx])
176 | }
177 |
178 | return env
179 | }
180 |
181 | func unwrapReturnValue(obj Object) Object {
182 | if returnValue, ok := obj.(*ReturnValue); ok {
183 | return returnValue.Value
184 | }
185 |
186 | return obj
187 | }
188 |
189 | func (e *Evaluator) evalLetStatement(node *ast.LetStatement, env *Environment, this Object) (Object, *Error) {
190 | val, err := e.Eval(node.Value, env, this)
191 | if err != nil {
192 | return nil, err
193 | }
194 |
195 | if node.Constant {
196 | env.LetConstant(node.Name.Value, val)
197 | } else {
198 | env.Let(node.Name.Value, val)
199 | }
200 |
201 | return NIL, nil
202 | }
203 |
204 | func (e *Evaluator) evalReturnStatement(node *ast.ReturnStatement, env *Environment, this Object) (Object, *Error) {
205 | val, err := e.Eval(node.ReturnValue, env, this)
206 | if err != nil {
207 | return nil, err
208 | }
209 |
210 | return &ReturnValue{Value: val}, nil
211 | }
212 |
213 | func (e *Evaluator) evalExpressions(exps []ast.Expression, env *Environment, this Object) ([]Object, *Error) {
214 | var result []Object
215 |
216 | for _, exp := range exps {
217 | evaluated, err := e.Eval(exp, env, this)
218 | if err != nil {
219 | return nil, err
220 | }
221 |
222 | result = append(result, evaluated)
223 | }
224 |
225 | return result, nil
226 | }
227 |
228 | func (e *Evaluator) evalProgram(statements []ast.Statement, env *Environment, this Object) (Object, *Error) {
229 | var result Object
230 | var err *Error
231 |
232 | for _, statement := range statements {
233 | result, err = e.Eval(statement, env, this)
234 | if err != nil {
235 | return nil, err
236 | }
237 |
238 | switch result := result.(type) {
239 | case *ReturnValue:
240 | return result.Value, nil
241 | }
242 | }
243 |
244 | return result, nil
245 | }
246 |
247 | func (e *Evaluator) evalBlockStatement(block *ast.BlockStatement, env *Environment, this Object) (Object, *Error) {
248 | enclosedEnv := NewEnclosedEnvironment(env)
249 |
250 | var result Object
251 | var err *Error
252 |
253 | for _, statement := range block.Statements {
254 | result, err = e.Eval(statement, enclosedEnv, this)
255 | if err != nil {
256 | return nil, err
257 | }
258 |
259 | if isReturn(result) {
260 | return result, nil
261 | }
262 | }
263 |
264 | return result, nil
265 | }
266 |
267 | func (e *Evaluator) evalForStatement(node *ast.ForStatement, env *Environment, this Object) (Object, *Error) {
268 | enclosedEnv := NewEnclosedEnvironment(env)
269 |
270 | _, err := e.Eval(node.Initializer, enclosedEnv, this)
271 | if err != nil {
272 | return nil, err
273 | }
274 |
275 | switch body := node.Body.(type) {
276 | case *ast.BlockStatement: // to optimize for block statements
277 | bodyEnv := NewEnclosedEnvironment(enclosedEnv)
278 |
279 | for {
280 | condition, err := e.Eval(node.Condition, enclosedEnv, this)
281 | if err != nil {
282 | return nil, err
283 | }
284 |
285 | if !isTruthy(condition) {
286 | break
287 | }
288 |
289 | result, err := e.evalBlockStatement(body, bodyEnv, this)
290 | if err != nil {
291 | return nil, err
292 | }
293 |
294 | if isReturn(result) {
295 | return result, nil
296 | }
297 |
298 | bodyEnv.ClearStore()
299 |
300 | _, err = e.Eval(node.Increment, enclosedEnv, this)
301 | if err != nil {
302 | return nil, err
303 | }
304 | }
305 |
306 | default:
307 | for {
308 | condition, err := e.Eval(node.Condition, enclosedEnv, this)
309 | if err != nil {
310 | return nil, err
311 | }
312 |
313 | if !isTruthy(condition) {
314 | break
315 | }
316 |
317 | result, err := e.Eval(body, enclosedEnv, this)
318 | if err != nil {
319 | return nil, err
320 | }
321 |
322 | if isReturn(result) {
323 | return result, nil
324 | }
325 |
326 | _, err = e.Eval(node.Increment, enclosedEnv, this)
327 | if err != nil {
328 | return nil, err
329 | }
330 | }
331 | }
332 |
333 | return NIL, nil
334 | }
335 |
336 | func (e *Evaluator) evalWhileStatement(node *ast.WhileStatement, env *Environment, this Object) (Object, *Error) {
337 | for {
338 | condition, err := e.Eval(node.Condition, env, this)
339 | if err != nil {
340 | return nil, err
341 | }
342 |
343 | if !isTruthy(condition) {
344 | break
345 | }
346 |
347 | result, err := e.Eval(node.Body, env, this)
348 | if err != nil {
349 | return nil, err
350 | }
351 |
352 | if isReturn(result) {
353 | return result, nil
354 | }
355 | }
356 |
357 | return NIL, nil
358 | }
359 |
360 | func (e *Evaluator) evalIncludeStatement(node *ast.IncludeStatement, env *Environment, this Object) (Object, *Error) {
361 | path := node.Path
362 | fileEnv, evaluated := e.envManager.Get(path)
363 |
364 | if !evaluated {
365 | file, ioErr := ioutil.ReadFile(path)
366 | if ioErr != nil {
367 | return nil, e.newError(node.Token, "cannot read file: %s", path)
368 | }
369 |
370 | input := string(file)
371 | lexer := lexer.New(input)
372 | parser := parser.New(lexer, path)
373 | program := parser.ParseProgram()
374 | parser.ReportErrors()
375 |
376 | _, err := e.Eval(program, fileEnv, this)
377 | if err != nil {
378 | return nil, err
379 | }
380 | }
381 |
382 | if node.Alias != nil {
383 | includeObject := &IncludeObject{
384 | Value: fileEnv,
385 | }
386 |
387 | env.AddAlias(node.Alias.Value, includeObject)
388 | } else {
389 | env.Includes = append(env.Includes, fileEnv)
390 | }
391 |
392 | return NIL, nil
393 | }
394 |
395 | func (e *Evaluator) evalPrefixExpression(node *ast.PrefixExpression, env *Environment, this Object) (Object, *Error) {
396 | right, err := e.Eval(node.Right, env, this)
397 | if err != nil {
398 | return nil, err
399 | }
400 |
401 | switch node.Operator {
402 | case "!":
403 | return e.evalBangOperatorExpression(node, right)
404 | case "-":
405 | return e.evalMinusPrefixOperatorExpression(node, right)
406 |
407 | default:
408 | return nil, e.newError(node.Token, "unknown operator: %s%s", node.Operator, right.Inspect())
409 | }
410 | }
411 |
412 | func (e *Evaluator) evalBangOperatorExpression(node *ast.PrefixExpression, right Object) (Object, *Error) {
413 | return boolToBoolObject(!isTruthy(right)), nil
414 | }
415 |
416 | func (e *Evaluator) evalMinusPrefixOperatorExpression(node *ast.PrefixExpression, right Object) (Object, *Error) {
417 | switch right := right.(type) {
418 | case Integer:
419 | return Integer{Value: -right.Value}, nil
420 | case *Float:
421 | return &Float{Value: -right.Value}, nil
422 | default:
423 | return nil, e.newError(node.Token, "unknown operator: -%s", right.Inspect())
424 | }
425 | }
426 |
427 | func (e *Evaluator) evalInfixExpression(node *ast.InfixExpression, env *Environment, this Object) (Object, *Error) {
428 | left, err := e.Eval(node.Left, env, this)
429 | if err != nil {
430 | return nil, err
431 | }
432 |
433 | right, err := e.Eval(node.Right, env, this)
434 | if err != nil {
435 | return nil, err
436 | }
437 |
438 | switch {
439 | case left.Type() == IntegerObj && right.Type() == IntegerObj:
440 | leftVal := left.(Integer).Value
441 | rightVal := right.(Integer).Value
442 |
443 | return e.evalIntegerInfixExpression(node, node.Operator, leftVal, rightVal)
444 |
445 | case left.Type() == FloatObj && right.Type() == FloatObj:
446 | leftVal := left.(*Float).Value
447 | rightVal := right.(*Float).Value
448 |
449 | return e.evalFloatInfixExpression(node, node.Operator, leftVal, rightVal)
450 |
451 | case left.Type() == FloatObj && right.Type() == IntegerObj:
452 | leftVal := left.(*Float).Value
453 | rightVal := right.(Integer).Value
454 |
455 | return e.evalFloatInfixExpression(node, node.Operator, leftVal, float64(rightVal))
456 |
457 | case left.Type() == IntegerObj && right.Type() == FloatObj:
458 | leftVal := left.(Integer).Value
459 | rightVal := right.(*Float).Value
460 |
461 | return e.evalFloatInfixExpression(node, node.Operator, float64(leftVal), rightVal)
462 |
463 | case left.Type() == StringObj && right.Type() == StringObj:
464 | return e.evalStringInfixExpression(node, node.Operator, left, right)
465 |
466 | case node.Operator == "==":
467 | return boolToBoolObject(left == right), nil
468 |
469 | case node.Operator == "!=":
470 | return boolToBoolObject(left != right), nil
471 |
472 | case node.Operator == "&&":
473 | return boolToBoolObject(isTruthy(left) && isTruthy(right)), nil
474 |
475 | case node.Operator == "||":
476 | return boolToBoolObject(isTruthy(left) || isTruthy(right)), nil
477 |
478 | default:
479 | return nil, e.newError(node.Token, "unknown operator: %s %s %s",
480 | left.Inspect(), node.Operator, right.Inspect())
481 | }
482 | }
483 |
484 | func (e *Evaluator) evalIntegerInfixExpression(node *ast.InfixExpression, operator string, left, right int) (Object, *Error) {
485 | switch operator {
486 | case "<":
487 | return boolToBoolObject(left < right), nil
488 | case "<=":
489 | return boolToBoolObject(left <= right), nil
490 | case ">":
491 | return boolToBoolObject(left > right), nil
492 | case ">=":
493 | return boolToBoolObject(left >= right), nil
494 | case "==":
495 | return boolToBoolObject(left == right), nil
496 | case "!=":
497 | return boolToBoolObject(left != right), nil
498 | case "+":
499 | return Integer{Value: left + right}, nil
500 | case "-":
501 | return Integer{Value: left - right}, nil
502 | case "*":
503 | return Integer{Value: left * right}, nil
504 | case "/":
505 | return Integer{Value: left / right}, nil
506 | case "%":
507 | return Integer{Value: left % right}, nil
508 | default:
509 | return nil, e.newError(node.Token, "unknown operator: %d %s %d",
510 | left, operator, right)
511 | }
512 | }
513 |
514 | func (e *Evaluator) evalFloatInfixExpression(node *ast.InfixExpression, operator string, left, right float64) (Object, *Error) {
515 | switch operator {
516 | case "<":
517 | return boolToBoolObject(left < right), nil
518 | case "<=":
519 | return boolToBoolObject(left <= right), nil
520 | case ">":
521 | return boolToBoolObject(left > right), nil
522 | case ">=":
523 | return boolToBoolObject(left >= right), nil
524 | case "==":
525 | return boolToBoolObject(left == right), nil
526 | case "!=":
527 | return boolToBoolObject(left != right), nil
528 | case "+":
529 | return &Float{Value: left + right}, nil
530 | case "-":
531 | return &Float{Value: left - right}, nil
532 | case "*":
533 | return &Float{Value: left * right}, nil
534 | case "/":
535 | return &Float{Value: left / right}, nil
536 | case "%":
537 | return &Float{Value: math.Mod(left, right)}, nil
538 | default:
539 | return nil, e.newError(node.Token, "unknown operator: %f %s %f",
540 | left, operator, right)
541 | }
542 | }
543 |
544 | func (e *Evaluator) evalStringInfixExpression(node *ast.InfixExpression, operator string, left, right Object) (Object, *Error) {
545 | if operator != "+" {
546 | return nil, e.newError(node.Token, "unknown operator: %s %s %s",
547 | left.Inspect(), operator, right.Inspect())
548 | }
549 |
550 | leftVal := left.(*String).Value
551 | rightVal := right.(*String).Value
552 | return &String{Value: leftVal + rightVal}, nil
553 | }
554 |
555 | func (e *Evaluator) evalIfExpression(ie *ast.IfExpression, env *Environment, this Object) (Object, *Error) {
556 | condition, err := e.Eval(ie.Condition, env, this)
557 | if err != nil {
558 | return nil, err
559 | }
560 |
561 | if isTruthy(condition) {
562 | return e.Eval(ie.ThenBranch, env, this)
563 | } else if ie.ElseBranch != nil {
564 | return e.Eval(ie.ElseBranch, env, this)
565 | } else {
566 | return NIL, nil
567 | }
568 | }
569 |
570 | func (e *Evaluator) evalIdentifier(node *ast.Identifier, env *Environment, this Object) (Object, *Error) {
571 | if val, ok := env.Get(node.Value); ok {
572 | return val, nil
573 | }
574 |
575 | if node.Value == "this" {
576 | return this, nil
577 | }
578 |
579 | if builtin, ok := builtins[node.Value]; ok {
580 | return builtin, nil
581 | }
582 |
583 | return nil, e.newError(node.Token, "identifier not found: "+node.Value)
584 | }
585 |
586 | func (e *Evaluator) evalArrayLiteral(node *ast.ArrayLiteral, env *Environment, this Object) (Object, *Error) {
587 | objects := make([]Object, len(node.Value))
588 |
589 | for index, expr := range node.Value {
590 | object, err := e.Eval(expr, env, this)
591 | if err != nil {
592 | return nil, err
593 | }
594 |
595 | objects[index] = object
596 | }
597 |
598 | return &Array{Value: objects}, nil
599 | }
600 |
601 | func (e *Evaluator) evalIndexExpression(node *ast.IndexExpression, env *Environment, this Object) (Object, *Error) {
602 | left, err := e.Eval(node.Left, env, this)
603 | if err != nil {
604 | return nil, err
605 | }
606 |
607 | index, err := e.Eval(node.Index, env, this)
608 | if err != nil {
609 | return nil, err
610 | }
611 |
612 | switch left := left.(type) {
613 | case *Array:
614 | switch index.Type() {
615 | case IntegerObj:
616 | return e.evalArrayIndexExpression(node, left, index)
617 | default:
618 | return e.evalWithFunctionsIndexExpression(node, left, index)
619 | }
620 |
621 | case *Hash:
622 | return e.evalHashIndexExpression(node, left, index)
623 |
624 | case *IncludeObject:
625 | return e.evalIncludeIndexExpression(node, left, index)
626 |
627 | case ObjectWithFunctions:
628 | return e.evalWithFunctionsIndexExpression(node, left, index)
629 |
630 | default:
631 | return nil, e.newError(node.Token, "index operator not supported: %s", left.Inspect())
632 | }
633 | }
634 |
635 | func (e *Evaluator) evalHashLiteral(node *ast.HashLiteral, env *Environment) (Object, *Error) {
636 | hash := &Hash{Pairs: make(map[HashKey]Object)}
637 |
638 | for key, value := range node.Pairs {
639 | hashKey, err := e.Eval(key, env, hash)
640 | if err != nil {
641 | return nil, err
642 | }
643 |
644 | key, ok := hashKey.(Hashable)
645 | if !ok {
646 | return nil, e.newError(node.Token, "unusable as hash key: %s", hashKey.Inspect())
647 | }
648 |
649 | hashValue, err := e.Eval(value, env, hash)
650 | if err != nil {
651 | return nil, err
652 | }
653 |
654 | hash.Pairs[key.HashKey()] = hashValue
655 | }
656 |
657 | return hash, nil
658 | }
659 |
660 | func (e *Evaluator) evalArrayIndexExpression(node *ast.IndexExpression, array *Array, index Object) (Object, *Error) {
661 | idx := index.(Integer).Value
662 | max := len(array.Value) - 1
663 |
664 | if idx < 0 || idx > max {
665 | return NIL, nil
666 | }
667 |
668 | return array.Value[idx], nil
669 | }
670 |
671 | func (e *Evaluator) evalHashIndexExpression(node *ast.IndexExpression, hash *Hash, index Object) (Object, *Error) {
672 | key, ok := index.(Hashable)
673 | if !ok {
674 | return nil, e.newError(node.Token, "unusable as hash key: %s", index.Inspect())
675 | }
676 |
677 | if val, ok := hash.Pairs[key.HashKey()]; ok {
678 | return val, nil
679 | }
680 |
681 | return NIL, nil
682 | }
683 |
684 | func (e *Evaluator) evalIncludeIndexExpression(node *ast.IndexExpression, include, index Object) (Object, *Error) {
685 | includeObj := include.(*IncludeObject)
686 | key, ok := index.(*String)
687 | if !ok {
688 | return nil, e.newError(node.Token, "unusable as include key: %s", key.Inspect())
689 | }
690 |
691 | obj, ok := includeObj.Value.Store[key.Value]
692 | if !ok {
693 | return nil, e.newError(node.Token, "include key not found: %s", key.Inspect())
694 | }
695 |
696 | return obj, nil
697 | }
698 |
699 | func (e *Evaluator) evalWithFunctionsIndexExpression(node *ast.IndexExpression, obj ObjectWithFunctions, index Object) (Object, *Error) {
700 | name, ok := index.(*String)
701 | if !ok {
702 | return nil, e.newError(node.Token, "cannot use %s as an index", index.Type().String())
703 | }
704 |
705 | fn, ok := obj.GetFunction(name.Value)
706 | if !ok {
707 | return nil, e.newError(
708 | node.Token, "cannot find '%s' function on type %s",
709 | name.Value,
710 | obj.(Object).Type().String(),
711 | )
712 | }
713 |
714 | return fn, nil
715 | }
716 |
717 | func (e *Evaluator) evalAssignExpression(node *ast.AssignExpression, env *Environment, this Object) (Object, *Error) {
718 | val, err := e.Eval(node.Value, env, this)
719 | if err != nil {
720 | return nil, err
721 | }
722 |
723 | switch left := node.Name.(type) {
724 | case *ast.Identifier:
725 | return e.evalAssingIdentifierExpression(node, left, val, env)
726 |
727 | case *ast.IndexExpression:
728 | return e.evalAssingIndexExpression(node, left, val, env, this)
729 | }
730 |
731 | return nil, e.newError(node.Token, "cannot assign to %s", node.Name.String())
732 | }
733 |
734 | func (e *Evaluator) evalAssingIdentifierExpression(node *ast.AssignExpression, left *ast.Identifier, val Object, env *Environment) (Object, *Error) {
735 | if env.IsConstant(left.Value) {
736 | return nil, e.newError(node.Token, "cannot assign to a constant variable %s", left.Value)
737 | }
738 |
739 | _, ok := env.Set(left.Value, val)
740 | if !ok {
741 | return nil, e.newError(node.Token, "identifier not found: "+left.Value)
742 | }
743 |
744 | return val, nil
745 | }
746 |
747 | func (e *Evaluator) evalAssingIndexExpression(node *ast.AssignExpression, left *ast.IndexExpression, val Object, env *Environment, this Object) (Object, *Error) {
748 | leftObj, err := e.Eval(left.Left, env, this)
749 | if err != nil {
750 | return nil, err
751 | }
752 |
753 | index, err := e.Eval(left.Index, env, this)
754 | if err != nil {
755 | return nil, err
756 | }
757 |
758 | switch leftObj := leftObj.(type) {
759 | case *Array:
760 | return e.evalAssingArrayIndexExpression(node, leftObj, index, val)
761 | case *Hash:
762 | return e.evalAssingHashIndexExpression(node, leftObj, index, val)
763 | default:
764 | return nil, e.newError(node.Token, "index operator not supported: %s", leftObj.Inspect())
765 | }
766 | }
767 |
768 | func (e *Evaluator) evalAssingArrayIndexExpression(node *ast.AssignExpression, leftObj *Array, index Object, val Object) (Object, *Error) {
769 | idx := index.(Integer).Value
770 | max := len(leftObj.Value) - 1
771 |
772 | if idx < 0 || idx > max {
773 | return nil, e.newError(node.Token, "index out of bounds")
774 | }
775 |
776 | leftObj.Value[idx] = val
777 |
778 | return val, nil
779 | }
780 |
781 | func (e *Evaluator) evalAssingHashIndexExpression(node *ast.AssignExpression, leftObj *Hash, index Object, val Object) (Object, *Error) {
782 | key, ok := index.(Hashable)
783 | if !ok {
784 | return nil, e.newError(node.Token, "unusable as hash key: %s", index.Inspect())
785 | }
786 |
787 | leftObj.Pairs[key.HashKey()] = val
788 |
789 | return val, nil
790 | }
791 |
792 | func (e *Evaluator) evalPostfixExpression(node *ast.PostfixExpression, env *Environment, this Object) (Object, *Error) {
793 | left, err := e.Eval(node.Left, env, this)
794 | if err != nil {
795 | return nil, err
796 | }
797 |
798 | switch left := left.(type) {
799 | case Integer:
800 | return e.evalPostfixIntegerExpression(node, node.Operator, left)
801 | default:
802 | return nil, e.newError(node.Token, "postfix operator not supported: %s", left.Inspect())
803 | }
804 | }
805 |
806 | func (e *Evaluator) evalPostfixIntegerExpression(node *ast.PostfixExpression, operator string, left Integer) (Object, *Error) {
807 | switch operator {
808 | case "++":
809 | left.Value++
810 | return left, nil
811 | case "--":
812 | left.Value--
813 | return left, nil
814 | default:
815 | return nil, e.newError(node.Token, "postfix operator not supported: %s", operator)
816 | }
817 | }
818 |
819 | func (e *Evaluator) newError(token token.Token, format string, a ...interface{}) *Error {
820 | return &Error{Message: fmt.Sprintf("[file %s:%d] %s", e.filePath, token.Line, fmt.Sprintf(format, a...))}
821 | }
822 |
823 | func isTruthy(obj Object) bool {
824 | switch obj {
825 | case NIL:
826 | return false
827 |
828 | case TRUE:
829 | return true
830 |
831 | case FALSE:
832 | return false
833 |
834 | default:
835 | return true
836 | }
837 | }
838 |
839 | func boolToBoolObject(value bool) *Boolean {
840 | if value {
841 | return TRUE
842 | }
843 |
844 | return FALSE
845 | }
846 |
847 | func isReturn(obj Object) bool {
848 | rt := obj.Type()
849 |
850 | return rt == ReturnValueObj
851 | }
852 |
--------------------------------------------------------------------------------
/evaluator/evaluator_test.go:
--------------------------------------------------------------------------------
1 | package evaluator
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/joetifa2003/windlang/lexer"
7 | "github.com/joetifa2003/windlang/parser"
8 | "github.com/stretchr/testify/assert"
9 | )
10 |
11 | const fileName = "main-test.wind"
12 |
13 | func TestEvalBooleanInfixExpression(t *testing.T) {
14 | assert := assert.New(t)
15 |
16 | tests := []struct {
17 | input string
18 | expected bool
19 | }{
20 | {"true", true},
21 | {"false", false},
22 | {"1 < 2", true},
23 | {"1 > 2", false},
24 | {"1 < 1", false},
25 | {"1 > 1", false},
26 | {"1 == 1", true},
27 | {"1 != 1", false},
28 | {"1 == 2", false},
29 | {"1 != 2", true},
30 | {"3.1 > 3.0", true},
31 | {"3.1 > 3", true},
32 | {"3 < 3.1", true},
33 | {"true == true", true},
34 | {"false == false", true},
35 | {"true == false", false},
36 | {"true != false", true},
37 | {"false != true", true},
38 | }
39 |
40 | for _, tc := range tests {
41 | evaluated, err := testEval(tc.input)
42 | assert.Nil(err)
43 | assert.IsType(&Boolean{}, evaluated)
44 | boolVal := evaluated.(*Boolean).Value
45 | assert.Equal(tc.expected, boolVal)
46 | }
47 | }
48 |
49 | func TestBangOperator(t *testing.T) {
50 | assert := assert.New(t)
51 |
52 | tests := []struct {
53 | input string
54 | expected Object
55 | }{
56 | {"!true", &Boolean{Value: false}},
57 | {"!false", &Boolean{Value: true}},
58 | {"!5", &Boolean{Value: false}},
59 | {"!!true", &Boolean{Value: true}},
60 | {"!!false", &Boolean{Value: false}},
61 | {"!!5", &Boolean{Value: true}},
62 | }
63 |
64 | for _, tc := range tests {
65 | evaluated, err := testEval(tc.input)
66 | assert.Nil(err)
67 | assert.IsType(tc.expected, evaluated)
68 | assert.Equal(tc.expected, evaluated)
69 | }
70 | }
71 |
72 | func TestIfElseExpressions(t *testing.T) {
73 | assert := assert.New(t)
74 |
75 | tests := []struct {
76 | input string
77 | expected Object
78 | }{
79 | {"if (true) { 10 }", &Integer{Value: 10}},
80 | {"if (1) { 10 }", &Integer{Value: 10}},
81 | {"if (1 < 2) { 10 }", &Integer{Value: 10}},
82 | {"if (1 > 2) { 10 } else { 20 }", &Integer{Value: 20}},
83 | {"if (1 < 2) { 10 } else { 20 }", &Integer{Value: 10}},
84 | {"if (false) { 1 }", &Nil{}},
85 | }
86 |
87 | for _, tc := range tests {
88 | evaluated, err := testEval(tc.input)
89 | assert.Nil(err)
90 | assert.IsType(tc.expected, evaluated)
91 | assert.Equal(tc.expected, evaluated)
92 | }
93 | }
94 |
95 | func TestLetStatements(t *testing.T) {
96 | assert := assert.New(t)
97 |
98 | tests := []struct {
99 | input string
100 | expected Object
101 | }{
102 | {"let a = 5; a;", &Integer{Value: 5}},
103 | {"let a = 5 * 5; a;", &Integer{Value: 25}},
104 | {"let a = 5; let b = a; b;", &Integer{Value: 5}},
105 | {"let a = 5; let b = a; let c = a + b + 5; c;", &Integer{Value: 15}},
106 | }
107 |
108 | for _, tc := range tests {
109 | evaluated, err := testEval(tc.input)
110 | assert.Nil(err)
111 | assert.IsType(tc.expected, evaluated)
112 | assert.Equal(tc.expected, evaluated)
113 | }
114 | }
115 |
116 | func TestFunctions(t *testing.T) {
117 | assert := assert.New(t)
118 |
119 | tests := []struct {
120 | input string
121 | expected Object
122 | }{
123 | {"let double = fn(x) { x * 2; }; double(5);", &Integer{Value: 10}},
124 | {"let add = fn(x, y) { x + y; }; add(5, 5);", &Integer{Value: 10}},
125 | {"let add = fn(x, y) { x + y; }; add(5 + 5, add(5, 5));", &Integer{Value: 20}},
126 | {"fn(x) { x; }(5)", &Integer{Value: 5}},
127 | {"fn(x) { return x; }(5)", &Integer{Value: 5}},
128 | }
129 |
130 | for _, tc := range tests {
131 | evaluated, err := testEval(tc.input)
132 | assert.Nil(err)
133 | assert.IsType(tc.expected, evaluated)
134 | assert.Equal(tc.expected, evaluated)
135 | }
136 | }
137 |
138 | func TestClosures(t *testing.T) {
139 | assert := assert.New(t)
140 |
141 | tests := []struct {
142 | input string
143 | expected Object
144 | }{
145 | {"let newAdder = fn(x) { fn(y) { x + y }; }; let addTwo = newAdder(2); addTwo(2);", &Integer{Value: 4}},
146 | }
147 |
148 | for _, tc := range tests {
149 | evaluated, err := testEval(tc.input)
150 | assert.Nil(err)
151 | assert.IsType(tc.expected, evaluated)
152 | assert.Equal(tc.expected, evaluated)
153 | }
154 | }
155 |
156 | func TestStringLiteral(t *testing.T) {
157 | assert := assert.New(t)
158 |
159 | tests := []struct {
160 | input string
161 | expected Object
162 | }{
163 | {`"Hello World!"`, &String{Value: "Hello World!"}},
164 | {`"Hello" + " World!"`, &String{Value: "Hello World!"}},
165 | {`"Hello" + " " + "World!"`, &String{Value: "Hello World!"}},
166 | }
167 |
168 | for _, tc := range tests {
169 | evaluated, err := testEval(tc.input)
170 | assert.Nil(err)
171 | assert.IsType(tc.expected, evaluated)
172 | assert.Equal(tc.expected, evaluated)
173 | }
174 | }
175 |
176 | func TestHashLiterals(t *testing.T) {
177 | assert := assert.New(t)
178 |
179 | tests := []struct {
180 | input string
181 | expected Object
182 | }{
183 | {
184 | `let x = { "foo": 1, "bar": 2 }; x["foo"]`,
185 | &Integer{Value: 1},
186 | },
187 | {
188 | `let x = { "foo": 1, "bar": 2 }; x.bar`,
189 | &Integer{Value: 2},
190 | },
191 | {
192 | `let x = { "foo": fn() { return 1; } }; x["foo"]()`,
193 | &Integer{Value: 1},
194 | },
195 | {
196 | `let x = { "foo": fn() { return 1; } }; x.foo()`,
197 | &Integer{Value: 1},
198 | },
199 | {
200 | `let x = { "foo": 1 }; x.foo++; x.foo`,
201 | &Integer{Value: 2},
202 | },
203 | {
204 | `let x = { "foo": 1 }; x["foo"]++; x["foo"]`,
205 | &Integer{Value: 2},
206 | },
207 | {
208 | `let x = {"foo": { "bar": 1 } }; x.foo.bar`,
209 | &Integer{Value: 1},
210 | },
211 | {
212 | `let x = {"foo": { "bar": 1 } }; x.foo.bar = 2; x.foo.bar`,
213 | &Integer{Value: 2},
214 | },
215 | {
216 | `let x = { "foo": { "bar": fn() { return { "baz": 1 }; } } }; x.foo.bar().baz`,
217 | &Integer{Value: 1},
218 | },
219 | {
220 | `let x = { "value": 1, "incrementValue": fn() { this.value++ } }; x.incrementValue(); x.value`,
221 | &Integer{Value: 2},
222 | },
223 | }
224 |
225 | for _, tc := range tests {
226 | evaluated, err := testEval(tc.input)
227 | assert.Nil(err)
228 | assert.IsType(tc.expected, evaluated)
229 | assert.Equal(tc.expected, evaluated)
230 | }
231 | }
232 |
233 | func TestIntInfixExpression(t *testing.T) {
234 | assert := assert.New(t)
235 |
236 | tests := []struct {
237 | input string
238 | expected int64
239 | }{
240 | {"5 + 5;", 10},
241 | {"5 - 5;", 0},
242 | {"5 * 5;", 25},
243 | {"5 / 5;", 1},
244 | {"4 % 2;", 0},
245 | }
246 |
247 | for _, tc := range tests {
248 | evaluated, err := testEval(tc.input)
249 | assert.Nil(err)
250 | assert.IsType(&Integer{}, evaluated)
251 | intVal := evaluated.(*Integer).Value
252 | assert.Equal(tc.expected, intVal)
253 | }
254 | }
255 |
256 | func TestFloatInfixExpression(t *testing.T) {
257 | assert := assert.New(t)
258 |
259 | tests := []struct {
260 | input string
261 | expected float64
262 | }{
263 | {"5.0 + 5.0;", 10},
264 | {"5.5 - 5.5;", 0},
265 | {"5.5 * 5.5;", 30.25},
266 | {"5.8 / 5.8;", 1},
267 | {"4.0 % 2.0;", 0},
268 | }
269 |
270 | for _, tc := range tests {
271 | evaluated, err := testEval(tc.input)
272 | assert.Nil(err)
273 | assert.IsType(&Float{}, evaluated)
274 | intVal := evaluated.(*Float).Value
275 | assert.Equal(tc.expected, intVal)
276 | }
277 | }
278 |
279 | func TestFloatInfixIntExpression(t *testing.T) {
280 | assert := assert.New(t)
281 |
282 | tests := []struct {
283 | input string
284 | expected float64
285 | }{
286 | {"5.0 + 5;", 10},
287 | {"5.5 - 5;", 0.5},
288 | {"5.5 * 5;", 27.5},
289 | {"5.8 / 5;", 1.16},
290 | {"4.0 % 2;", 0},
291 | }
292 |
293 | for _, tc := range tests {
294 | evaluated, err := testEval(tc.input)
295 | assert.Nil(err)
296 | assert.IsType(&Float{}, evaluated)
297 | intVal := evaluated.(*Float).Value
298 | assert.Equal(tc.expected, intVal)
299 | }
300 | }
301 |
302 | func TestIntPrefixOperators(t *testing.T) {
303 | assert := assert.New(t)
304 |
305 | tests := []struct {
306 | input string
307 | expected int64
308 | }{
309 | {"-5;", -5},
310 | }
311 |
312 | for _, tc := range tests {
313 | evaluated, err := testEval(tc.input)
314 | assert.Nil(err)
315 | assert.IsType(&Integer{}, evaluated)
316 | intVal := evaluated.(*Integer).Value
317 | assert.Equal(tc.expected, intVal)
318 | }
319 | }
320 |
321 | func TestFloatPrefixOperators(t *testing.T) {
322 | assert := assert.New(t)
323 |
324 | tests := []struct {
325 | input string
326 | expected float64
327 | }{
328 | {"-5.0;", -5},
329 | }
330 |
331 | for _, tc := range tests {
332 | evaluated, err := testEval(tc.input)
333 | assert.Nil(err)
334 | assert.IsType(&Float{}, evaluated)
335 | intVal := evaluated.(*Float).Value
336 | assert.Equal(tc.expected, intVal)
337 | }
338 | }
339 |
340 | func TestArrayLiteral(t *testing.T) {
341 | assert := assert.New(t)
342 |
343 | tests := []struct {
344 | input string
345 | expected []Object
346 | }{
347 | {
348 | `[1, 2, 3]`,
349 | []Object{&Integer{Value: 1}, &Integer{Value: 2}, &Integer{Value: 3}},
350 | },
351 | {
352 | `[1, 2.5, 3, true, "Hello"]`,
353 | []Object{&Integer{Value: 1}, &Float{Value: 2.5}, &Integer{Value: 3}, &Boolean{Value: true}, &String{Value: "Hello"}},
354 | },
355 | }
356 |
357 | for _, tc := range tests {
358 | evaluated, err := testEval(tc.input)
359 | assert.Nil(err)
360 | assert.IsType(&Array{}, evaluated)
361 | arrayVal := evaluated.(*Array).Value
362 | assert.Equal(tc.expected, arrayVal)
363 | }
364 | }
365 |
366 | func TestArrayIndex(t *testing.T) {
367 | assert := assert.New(t)
368 |
369 | tests := []struct {
370 | input string
371 | expected Object
372 | }{
373 | {
374 | `[1, 2, 3][0]`,
375 | &Integer{Value: 1},
376 | },
377 | {
378 | `[1, 5, true, "Hello", fn() { return "Hello"; }][4]()`,
379 | &String{Value: "Hello"},
380 | },
381 | }
382 |
383 | for _, tc := range tests {
384 | evaluated, err := testEval(tc.input)
385 | assert.Nil(err)
386 | assert.IsType(tc.expected, evaluated)
387 | assert.Equal(tc.expected, evaluated)
388 | }
389 | }
390 |
391 | func TestForLoop(t *testing.T) {
392 | assert := assert.New(t)
393 |
394 | tests := []struct {
395 | input string
396 | expected Object
397 | }{
398 | {
399 | `
400 | let x = 0;
401 |
402 | for (let i = 0; i < 5; i++) {
403 | x = x + 1;
404 | }
405 |
406 | x
407 | `,
408 | &Integer{Value: 5},
409 | },
410 | }
411 |
412 | for _, tc := range tests {
413 | evaluated, err := testEval(tc.input)
414 | assert.Nil(err)
415 | assert.IsType(tc.expected, evaluated)
416 | assert.Equal(tc.expected, evaluated)
417 | }
418 | }
419 |
420 | func TestWhileLoop(t *testing.T) {
421 | assert := assert.New(t)
422 |
423 | tests := []struct {
424 | input string
425 | expected Object
426 | }{
427 | {
428 | `
429 | let x = 0;
430 |
431 | while (x < 5) {
432 | x = x + 1;
433 | }
434 |
435 | x
436 | `,
437 | &Integer{Value: 5},
438 | },
439 | }
440 |
441 | for _, tc := range tests {
442 | evaluated, err := testEval(tc.input)
443 | assert.Nil(err)
444 | assert.IsType(tc.expected, evaluated)
445 | assert.Equal(tc.expected, evaluated)
446 | }
447 | }
448 |
449 | func TestRecursion(t *testing.T) {
450 | assert := assert.New(t)
451 |
452 | tests := []struct {
453 | input string
454 | expected Object
455 | }{
456 | {
457 | `
458 | let multiply = fn(n, m) {
459 | if (m == 0) {
460 | return 0;
461 | }
462 |
463 | return n + multiply(n, m - 1);
464 | };
465 | multiply(2, 3);
466 | `,
467 | &Integer{Value: 6},
468 | },
469 | }
470 |
471 | for _, tc := range tests {
472 | evaluated, err := testEval(
473 | tc.input,
474 | )
475 | assert.Nil(err)
476 | assert.IsType(tc.expected, evaluated)
477 | assert.Equal(tc.expected, evaluated)
478 | }
479 | }
480 |
481 | func testEval(input string) (Object, *Error) {
482 | l := lexer.New(input)
483 | p := parser.New(l, fileName)
484 | program := p.ParseProgram()
485 |
486 | envManager := NewEnvironmentManager()
487 | env, _ := envManager.Get(fileName)
488 | evaluator := New(envManager, fileName)
489 |
490 | return evaluator.Eval(program, env, nil)
491 | }
492 |
--------------------------------------------------------------------------------
/evaluator/object.go:
--------------------------------------------------------------------------------
1 | package evaluator
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "math"
7 | "strings"
8 |
9 | "github.com/joetifa2003/windlang/ast"
10 | )
11 |
12 | type ObjectType int
13 |
14 | const (
15 | Any ObjectType = iota
16 | IntegerObj
17 | FloatObj
18 | BooleanObj
19 | NilObj
20 | ReturnValueObj
21 | ErrorObj
22 | FunctionObj
23 | StringObj
24 | BuiltinObj
25 | ArrayObj
26 | HashObj
27 | IncludeObj
28 | )
29 |
30 | func (ot ObjectType) String() string {
31 | switch ot {
32 | case IntegerObj:
33 | return "INTEGER"
34 | case FloatObj:
35 | return "FLOAT"
36 | case BooleanObj:
37 | return "BOOLEAN"
38 | case NilObj:
39 | return "NIL"
40 | case ReturnValueObj:
41 | return "RETURN_VALUE"
42 | case ErrorObj:
43 | return "ERROR"
44 | case FunctionObj:
45 | return "FUNCTION"
46 | case StringObj:
47 | return "STRING"
48 | case BuiltinObj:
49 | return "BUILTIN"
50 | case ArrayObj:
51 | return "ARRAY"
52 | case HashObj:
53 | return "HASH"
54 | case IncludeObj:
55 | return "INCLUDE"
56 | default:
57 | return "UNKNOWN"
58 | }
59 | }
60 |
61 | type Object interface {
62 | Type() ObjectType
63 | Inspect() string
64 | Clone() Object
65 | }
66 |
67 | type OwnedFunction[T Object] struct {
68 | ArgsCount int
69 | ArgsTypes []ObjectType
70 | Fn func(evaluator *Evaluator, node *ast.CallExpression, this T, args ...Object) (Object, *Error)
71 | }
72 |
73 | type ObjectWithFunctions interface {
74 | GetFunction(name string) (*GoFunction, bool)
75 | }
76 |
77 | func GetFunctionFromObject[T Object](name string, object T, functions map[string]OwnedFunction[T]) (*GoFunction, bool) {
78 | if fn, ok := functions[name]; ok {
79 | return &GoFunction{
80 | ArgsCount: fn.ArgsCount,
81 | ArgsTypes: fn.ArgsTypes,
82 | Fn: func(evaluator *Evaluator, node *ast.CallExpression, args ...Object) (Object, *Error) {
83 | return fn.Fn(evaluator, node, object, args...)
84 | },
85 | }, true
86 | }
87 |
88 | return nil, false
89 | }
90 |
91 | type Hashable interface {
92 | HashKey() HashKey
93 | }
94 |
95 | type HashKey struct {
96 | Type ObjectType
97 | Value uint64
98 | InspectValue string
99 | }
100 |
101 | func (hk *HashKey) Inspect() string {
102 | return hk.InspectValue
103 | }
104 |
105 | type Integer struct {
106 | Value int
107 | }
108 |
109 | func (i Integer) Type() ObjectType { return IntegerObj }
110 | func (i Integer) Inspect() string { return fmt.Sprintf("%d", i.Value) }
111 | func (i Integer) HashKey() HashKey {
112 | return HashKey{Type: i.Type(), Value: uint64(i.Value)}
113 | }
114 | func (i Integer) Clone() Object {
115 | return &i
116 | }
117 |
118 | type Float struct {
119 | Value float64
120 | }
121 |
122 | func (f *Float) Type() ObjectType { return FloatObj }
123 | func (f *Float) Inspect() string { return fmt.Sprintf("%f", f.Value) }
124 | func (f Float) Clone() Object {
125 | return &f
126 | }
127 |
128 | type Boolean struct {
129 | Value bool
130 | }
131 |
132 | func (b *Boolean) Type() ObjectType { return BooleanObj }
133 | func (b *Boolean) Inspect() string { return fmt.Sprintf("%t", b.Value) }
134 | func (b *Boolean) HashKey() HashKey {
135 | var val uint64
136 |
137 | if b.Value {
138 | val = 1
139 | } else {
140 | val = 2
141 | }
142 |
143 | return HashKey{Type: b.Type(), Value: val, InspectValue: b.Inspect()}
144 | }
145 | func (b Boolean) Clone() Object {
146 | return &b
147 | }
148 |
149 | type Nil struct{}
150 |
151 | func (n *Nil) Type() ObjectType { return NilObj }
152 | func (n *Nil) Inspect() string { return "nil" }
153 | func (n Nil) Clone() Object {
154 | return &n
155 | }
156 |
157 | type ReturnValue struct {
158 | Value Object
159 | }
160 |
161 | func (rv *ReturnValue) Type() ObjectType { return ReturnValueObj }
162 | func (rv *ReturnValue) Inspect() string { return rv.Value.Inspect() }
163 | func (rv ReturnValue) Clone() Object {
164 | return &rv
165 | }
166 |
167 | type Error struct {
168 | Message string
169 | }
170 |
171 | func (e *Error) Type() ObjectType { return ErrorObj }
172 | func (e *Error) Inspect() string { return e.Message }
173 |
174 | type Function struct {
175 | Parameters []*ast.Identifier
176 | Body *ast.BlockStatement
177 | Env *Environment
178 | This Object
179 | }
180 |
181 | func (f *Function) Type() ObjectType { return FunctionObj }
182 | func (f *Function) Inspect() string {
183 | var out bytes.Buffer
184 | params := []string{}
185 | for _, p := range f.Parameters {
186 | params = append(params, p.String())
187 | }
188 | out.WriteString("fn")
189 | out.WriteString("(")
190 | out.WriteString(strings.Join(params, ", "))
191 | out.WriteString(") {\n")
192 | out.WriteString(f.Body.String())
193 | out.WriteString("\n}")
194 |
195 | return out.String()
196 | }
197 | func (f Function) Clone() Object {
198 | return &f
199 | }
200 |
201 | type BuiltinFunction func(evaluator *Evaluator, node *ast.CallExpression, args ...Object) (Object, *Error)
202 |
203 | type GoFunction struct {
204 | ArgsCount int
205 | ArgsTypes []ObjectType
206 | Fn BuiltinFunction
207 | }
208 |
209 | func (b *GoFunction) Type() ObjectType { return BuiltinObj }
210 | func (b *GoFunction) Inspect() string { return "builtin function" }
211 | func (b GoFunction) Clone() Object {
212 | return &b
213 | }
214 |
215 | type Hash struct {
216 | Pairs map[HashKey]Object
217 | }
218 |
219 | func (h *Hash) Type() ObjectType { return HashObj }
220 | func (h *Hash) Inspect() string {
221 | var out bytes.Buffer
222 |
223 | out.WriteString("{")
224 |
225 | for key, value := range h.Pairs {
226 | out.WriteString(key.Inspect())
227 | out.WriteString(": ")
228 | out.WriteString(value.Inspect())
229 | out.WriteString(", ")
230 | }
231 |
232 | out.WriteString("}")
233 |
234 | return out.String()
235 | }
236 | func (h Hash) Clone() Object {
237 | return &h
238 | }
239 |
240 | type IncludeObject struct {
241 | Value *Environment
242 | }
243 |
244 | func (i *IncludeObject) Type() ObjectType { return IncludeObj }
245 | func (i *IncludeObject) Inspect() string {
246 | return "include_OBJ"
247 | }
248 | func (i IncludeObject) Clone() Object {
249 | return &i
250 | }
251 |
252 | func GetObjectFromInterFace(v interface{}) Object {
253 | switch v := v.(type) {
254 | case float64:
255 | if v == math.Trunc(v) {
256 | return &Integer{Value: int(v)}
257 | } else {
258 | return &Float{Value: v}
259 | }
260 |
261 | case string:
262 | return &String{Value: v}
263 |
264 | case bool:
265 | if v {
266 | return TRUE
267 | } else {
268 | return FALSE
269 | }
270 |
271 | case []interface{}:
272 | res := make([]Object, len(v))
273 | for i, val := range v {
274 | res[i] = GetObjectFromInterFace(val)
275 | }
276 |
277 | return &Array{Value: res}
278 | }
279 |
280 | return NIL
281 | }
282 |
--------------------------------------------------------------------------------
/evaluator/objectArray.go:
--------------------------------------------------------------------------------
1 | package evaluator
2 |
3 | import (
4 | "bytes"
5 | "strings"
6 |
7 | "github.com/joetifa2003/windlang/ast"
8 | )
9 |
10 | type Array struct {
11 | Value []Object
12 | }
13 |
14 | func (a *Array) GetFunction(name string) (*GoFunction, bool) {
15 | return GetFunctionFromObject(name, a, arrayFunctions)
16 | }
17 | func (a *Array) Type() ObjectType { return ArrayObj }
18 | func (a *Array) Inspect() string {
19 | var out bytes.Buffer
20 |
21 | out.WriteString("[")
22 | for _, obj := range a.Value {
23 | out.WriteString(obj.Inspect())
24 | out.WriteString(",")
25 | }
26 | out.WriteString("]")
27 |
28 | return out.String()
29 | }
30 | func (a Array) Clone() Object {
31 | return &a
32 | }
33 |
34 | var arrayFunctions = map[string]OwnedFunction[*Array]{
35 | "len": {
36 | ArgsCount: 0,
37 | ArgsTypes: []ObjectType{},
38 | Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *Array, args ...Object) (Object, *Error) {
39 | return &Integer{
40 | Value: len(this.Value),
41 | }, nil
42 | },
43 | },
44 | "join": {
45 | ArgsCount: 1,
46 | ArgsTypes: []ObjectType{StringObj},
47 | Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *Array, args ...Object) (Object, *Error) {
48 | strArr := []string{}
49 | for _, obj := range this.Value {
50 | strArr = append(strArr, obj.Inspect())
51 | }
52 |
53 | return &String{
54 | Value: strings.Join(strArr, args[0].(*String).Value),
55 | }, nil
56 | },
57 | },
58 | "filter": {
59 | ArgsCount: 1,
60 | ArgsTypes: []ObjectType{FunctionObj},
61 | Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *Array, args ...Object) (Object, *Error) {
62 | fn := args[0].(*Function)
63 |
64 | filtered := []Object{}
65 | for _, obj := range this.Value {
66 | result, err := evaluator.applyFunction(node, fn, []Object{obj})
67 | if err != nil {
68 | return nil, err
69 | }
70 |
71 | if result == TRUE {
72 | filtered = append(filtered, obj)
73 | }
74 | }
75 |
76 | return &Array{
77 | Value: filtered,
78 | }, nil
79 | },
80 | },
81 | "map": {
82 | ArgsCount: 1,
83 | ArgsTypes: []ObjectType{FunctionObj},
84 | Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *Array, args ...Object) (Object, *Error) {
85 | fn := args[0].(*Function)
86 |
87 | mapped := []Object{}
88 | for _, obj := range this.Value {
89 | result, err := evaluator.applyFunction(node, fn, []Object{obj})
90 | if err != nil {
91 | return nil, err
92 | }
93 |
94 | mapped = append(mapped, result)
95 | }
96 |
97 | return &Array{
98 | Value: mapped,
99 | }, nil
100 | },
101 | },
102 | "reduce": {
103 | ArgsCount: 2,
104 | ArgsTypes: []ObjectType{FunctionObj, Any},
105 | Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *Array, args ...Object) (Object, *Error) {
106 | fn := args[0].(*Function)
107 | initial := args[1]
108 |
109 | accumulator := initial
110 | for _, obj := range this.Value {
111 | result, err := evaluator.applyFunction(node, fn, []Object{accumulator, obj})
112 | if err != nil {
113 | return nil, err
114 | }
115 |
116 | accumulator = result
117 | }
118 |
119 | return accumulator, nil
120 | },
121 | },
122 | "push": {
123 | ArgsCount: 1,
124 | ArgsTypes: []ObjectType{Any},
125 | Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *Array, args ...Object) (Object, *Error) {
126 | this.Value = append(this.Value, args[0].Clone())
127 | return this, nil
128 | },
129 | },
130 | "pop": {
131 | ArgsCount: 0,
132 | ArgsTypes: []ObjectType{},
133 | Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *Array, args ...Object) (Object, *Error) {
134 | last := this.Value[len(this.Value)-1]
135 | this.Value = this.Value[:len(this.Value)-1]
136 | return last, nil
137 | },
138 | },
139 | "contains": {
140 | ArgsCount: 1,
141 | ArgsTypes: []ObjectType{FunctionObj},
142 | Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *Array, args ...Object) (Object, *Error) {
143 | fn := args[0].(*Function)
144 |
145 | for _, obj := range this.Value {
146 | result, err := evaluator.applyFunction(node, fn, []Object{obj})
147 | if err != nil {
148 | return nil, err
149 | }
150 |
151 | if result == TRUE {
152 | return TRUE, nil
153 | }
154 | }
155 |
156 | return FALSE, nil
157 | },
158 | },
159 | "count": {
160 | ArgsCount: 1,
161 | ArgsTypes: []ObjectType{FunctionObj},
162 | Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *Array, args ...Object) (Object, *Error) {
163 | fn := args[0].(*Function)
164 |
165 | count := 0
166 | for _, obj := range this.Value {
167 | result, err := evaluator.applyFunction(node, fn, []Object{obj})
168 | if err != nil {
169 | return nil, err
170 | }
171 |
172 | if result == TRUE {
173 | count++
174 | }
175 | }
176 |
177 | return &Integer{
178 | Value: int(count),
179 | }, nil
180 | },
181 | },
182 | "clone": {
183 | ArgsCount: 0,
184 | ArgsTypes: []ObjectType{},
185 | Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *Array, args ...Object) (Object, *Error) {
186 | newValue := make([]Object, len(this.Value))
187 |
188 | copy(this.Value, newValue)
189 |
190 | return &Array{
191 | Value: newValue,
192 | }, nil
193 | },
194 | },
195 | "removeAt": {
196 | ArgsCount: 1,
197 | ArgsTypes: []ObjectType{IntegerObj},
198 | Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *Array, args ...Object) (Object, *Error) {
199 | index := args[0].(*Integer).Value
200 |
201 | if index < 0 || index >= len(this.Value) {
202 | return nil, evaluator.newError(node.Token, "index %d out of bounds", index)
203 | }
204 |
205 | newValue := []Object{}
206 | for i, v := range this.Value {
207 | if i != int(index) {
208 | newValue = append(newValue, v)
209 | }
210 | }
211 | removedValue := this.Value[index]
212 | this.Value = newValue
213 |
214 | return removedValue, nil
215 | },
216 | },
217 | }
218 |
--------------------------------------------------------------------------------
/evaluator/objectString.go:
--------------------------------------------------------------------------------
1 | package evaluator
2 |
3 | import (
4 | "hash/fnv"
5 | "strings"
6 |
7 | "github.com/joetifa2003/windlang/ast"
8 | )
9 |
10 | type String struct {
11 | Value string
12 | }
13 |
14 | func (s *String) GetFunction(name string) (*GoFunction, bool) {
15 | return GetFunctionFromObject(name, s, stringFunctions)
16 | }
17 | func (s *String) Type() ObjectType { return StringObj }
18 | func (s *String) Inspect() string { return s.Value }
19 | func (s *String) HashKey() HashKey {
20 | algo := fnv.New64a()
21 | algo.Write([]byte(s.Value))
22 | return HashKey{Type: s.Type(), Value: algo.Sum64(), InspectValue: s.Inspect()}
23 | }
24 | func (s String) Clone() Object {
25 | return &s
26 | }
27 |
28 | var stringFunctions = map[string]OwnedFunction[*String]{
29 | "len": {
30 | ArgsCount: 0,
31 | ArgsTypes: []ObjectType{},
32 | Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *String, args ...Object) (Object, *Error) {
33 | return &Integer{
34 | Value: len(this.Value),
35 | }, nil
36 | },
37 | },
38 | "charAt": {
39 | ArgsCount: 1,
40 | ArgsTypes: []ObjectType{IntegerObj},
41 | Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *String, args ...Object) (Object, *Error) {
42 | index := args[0].(*Integer)
43 |
44 | if index.Value >= len(this.Value) {
45 | return NIL, nil
46 | }
47 |
48 | return &String{
49 | Value: string([]rune(this.Value)[index.Value]),
50 | }, nil
51 | },
52 | },
53 | "contains": {
54 | ArgsCount: 1,
55 | ArgsTypes: []ObjectType{StringObj},
56 | Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *String, args ...Object) (Object, *Error) {
57 | substr := args[0].(*String)
58 |
59 | if strings.Contains(this.Value, substr.Value) {
60 | return TRUE, nil
61 | } else {
62 | return FALSE, nil
63 | }
64 | },
65 | },
66 | "containsAny": {
67 | ArgsCount: 1,
68 | ArgsTypes: []ObjectType{StringObj},
69 | Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *String, args ...Object) (Object, *Error) {
70 | substr := args[0].(*String)
71 |
72 | if strings.ContainsAny(this.Value, substr.Value) {
73 | return TRUE, nil
74 | } else {
75 | return FALSE, nil
76 | }
77 | },
78 | },
79 | "count": {
80 | ArgsCount: 1,
81 | ArgsTypes: []ObjectType{StringObj},
82 | Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *String, args ...Object) (Object, *Error) {
83 | substr := args[0].(*String)
84 |
85 | return &Integer{
86 | Value: strings.Count(this.Value, substr.Value),
87 | }, nil
88 | },
89 | },
90 | "replace": {
91 | ArgsCount: 2,
92 | ArgsTypes: []ObjectType{StringObj, StringObj},
93 | Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *String, args ...Object) (Object, *Error) {
94 | old := args[0].(*String)
95 | new := args[1].(*String)
96 |
97 | return &String{
98 | Value: strings.Replace(this.Value, old.Value, new.Value, 1),
99 | }, nil
100 | },
101 | },
102 | "replaceN": {
103 | ArgsCount: 3,
104 | ArgsTypes: []ObjectType{StringObj, StringObj, IntegerObj},
105 | Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *String, args ...Object) (Object, *Error) {
106 | old := args[0].(*String)
107 | new := args[1].(*String)
108 | n := args[2].(*Integer)
109 |
110 | return &String{
111 | Value: strings.Replace(this.Value, old.Value, new.Value, int(n.Value)),
112 | }, nil
113 | },
114 | },
115 | "replaceAll": {
116 | ArgsCount: 2,
117 | ArgsTypes: []ObjectType{StringObj, StringObj},
118 | Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *String, args ...Object) (Object, *Error) {
119 | old := args[0].(*String)
120 | new := args[1].(*String)
121 |
122 | return &String{
123 | Value: strings.ReplaceAll(this.Value, old.Value, new.Value),
124 | }, nil
125 | },
126 | },
127 | "toLowerCase": {
128 | ArgsCount: 0,
129 | ArgsTypes: []ObjectType{},
130 | Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *String, args ...Object) (Object, *Error) {
131 | return &String{
132 | Value: strings.ToLower(this.Value),
133 | }, nil
134 | },
135 | },
136 | "toUpperCase": {
137 | ArgsCount: 0,
138 | ArgsTypes: []ObjectType{},
139 | Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *String, args ...Object) (Object, *Error) {
140 | return &String{
141 | Value: strings.ToUpper(this.Value),
142 | }, nil
143 | },
144 | },
145 | "indexOf": {
146 | ArgsCount: 1,
147 | ArgsTypes: []ObjectType{StringObj},
148 | Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *String, args ...Object) (Object, *Error) {
149 | substr := args[0].(*String)
150 |
151 | return &Integer{
152 | Value: strings.Index(this.Value, substr.Value),
153 | }, nil
154 | },
155 | },
156 | "lastIndexOf": {
157 | ArgsCount: 1,
158 | ArgsTypes: []ObjectType{StringObj},
159 | Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *String, args ...Object) (Object, *Error) {
160 | substr := args[0].(*String)
161 |
162 | return &Integer{
163 | Value: strings.LastIndex(this.Value, substr.Value),
164 | }, nil
165 | },
166 | },
167 | "changeAt": {
168 | ArgsCount: 2,
169 | ArgsTypes: []ObjectType{IntegerObj, StringObj},
170 | Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *String, args ...Object) (Object, *Error) {
171 | index := args[0].(*Integer)
172 | newValue := args[1].(*String)
173 |
174 | if index.Value >= len(this.Value) {
175 | return nil, evaluator.newError(node.Token, "index out of range: got %d max %d", index.Value, len(this.Value)-1)
176 | }
177 |
178 | if len(newValue.Value) > 1 {
179 | return nil, evaluator.newError(node.Token, "new value can be at most one character")
180 | }
181 |
182 | return &String{
183 | Value: this.Value[:index.Value] + newValue.Value + this.Value[index.Value+1:],
184 | }, nil
185 | },
186 | },
187 | "trim": {
188 | ArgsCount: 0,
189 | ArgsTypes: []ObjectType{},
190 | Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *String, args ...Object) (Object, *Error) {
191 | return &String{
192 | Value: strings.TrimSpace(this.Value),
193 | }, nil
194 | },
195 | },
196 | "split": {
197 | ArgsCount: 1,
198 | ArgsTypes: []ObjectType{StringObj},
199 | Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *String, args ...Object) (Object, *Error) {
200 | seperator := args[0].(*String)
201 |
202 | strArr := strings.Split(this.Value, seperator.Value)
203 | objArr := []Object{}
204 |
205 | for _, str := range strArr {
206 | objArr = append(objArr, &String{Value: str})
207 | }
208 |
209 | return &Array{
210 | Value: objArr,
211 | }, nil
212 | },
213 | },
214 | }
215 |
--------------------------------------------------------------------------------
/evaluator/stdLib.go:
--------------------------------------------------------------------------------
1 | package evaluator
2 |
3 | var initiatedLibraries map[string]*Environment
4 |
5 | func GetStdlib(filePath string) (*Environment, bool) {
6 | switch filePath {
7 | case "math":
8 | return getLibrary("math", stdLibMath), true
9 |
10 | case "request":
11 | return getLibrary("request", stdLibReq), true
12 | }
13 |
14 | return nil, false
15 | }
16 |
17 | func getLibrary(name string, initiator func() *Environment) *Environment {
18 | lib, ok := initiatedLibraries[name]
19 | if !ok {
20 | lib = initiator()
21 | initiatedLibraries[name] = lib
22 | }
23 |
24 | return lib
25 | }
26 |
27 | func init() {
28 | initiatedLibraries = make(map[string]*Environment)
29 | }
30 |
--------------------------------------------------------------------------------
/evaluator/stdLibMath.go:
--------------------------------------------------------------------------------
1 | package evaluator
2 |
3 | import (
4 | "math"
5 |
6 | "github.com/joetifa2003/windlang/ast"
7 | )
8 |
9 | func stdLibMath() *Environment {
10 | return &Environment{
11 | Store: map[string]Object{
12 | "abs": &GoFunction{
13 | ArgsCount: 1,
14 | ArgsTypes: []ObjectType{FloatObj},
15 | Fn: func(evaluator *Evaluator, node *ast.CallExpression, args ...Object) (Object, *Error) {
16 | num := args[0].(*Float).Value
17 |
18 | return &Float{Value: math.Abs(num)}, nil
19 | },
20 | },
21 | },
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/evaluator/stdLibRequest.go:
--------------------------------------------------------------------------------
1 | package evaluator
2 |
3 | import (
4 | "encoding/json"
5 | "io/ioutil"
6 | "net/http"
7 |
8 | "github.com/joetifa2003/windlang/ast"
9 | )
10 |
11 | func stdLibReq() *Environment {
12 | return &Environment{
13 | Store: map[string]Object{
14 | "get": &GoFunction{
15 | ArgsCount: 1,
16 | ArgsTypes: []ObjectType{StringObj},
17 | Fn: func(evaluator *Evaluator, node *ast.CallExpression, args ...Object) (Object, *Error) {
18 | url := args[0].(*String)
19 |
20 | resp, err := http.Get(url.Value)
21 | if err != nil {
22 | return NIL, evaluator.newError(node.Token, "get request failed")
23 | }
24 |
25 | respBytes, err := ioutil.ReadAll(resp.Body)
26 | if err != nil {
27 | return NIL, evaluator.newError(node.Token, "get request failed")
28 | }
29 |
30 | result := make(map[string]interface{})
31 | json.Unmarshal(respBytes, &result)
32 |
33 | objectResults := make(map[HashKey]Object)
34 | for k, v := range result {
35 | key := &String{Value: k}
36 |
37 | objectResults[key.HashKey()] = GetObjectFromInterFace(v)
38 | }
39 |
40 | return &Hash{Pairs: objectResults}, nil
41 |
42 | },
43 | },
44 | // "post": &GoFunction{
45 | // ArgsCount: 1,
46 | // ArgsTypes: []ObjectType{StringObj},
47 | // Fn: func(evaluator *Evaluator, node *ast.CallExpression, args ...Object) (Object, *Error) {
48 | // http.Post()
49 | // },
50 | // },
51 | },
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/joetifa2003/windlang
2 |
3 | go 1.19
4 |
5 | require (
6 | github.com/spf13/cobra v1.4.0
7 | github.com/stretchr/testify v1.8.0
8 | )
9 |
10 | require (
11 | github.com/davecgh/go-spew v1.1.1 // indirect
12 | github.com/felixge/fgprof v0.9.3 // indirect
13 | github.com/google/pprof v0.0.0-20211214055906-6f57359322fd // indirect
14 | github.com/inconshreveable/mousetrap v1.0.0 // indirect
15 | github.com/pkg/profile v1.7.0 // indirect
16 | github.com/pmezard/go-difflib v1.0.0 // indirect
17 | github.com/spf13/pflag v1.0.5 // indirect
18 | gopkg.in/yaml.v3 v3.0.1 // indirect
19 | )
20 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
2 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
3 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
4 | github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
5 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
6 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
7 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
8 | github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
9 | github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
10 | github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y=
11 | github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
12 | github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
13 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
14 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
15 | github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA=
16 | github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo=
17 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
18 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
19 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
20 | github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q=
21 | github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=
22 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
23 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
24 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
25 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
26 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
27 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
28 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
29 | golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
30 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
31 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
32 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
33 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
34 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
35 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
36 |
--------------------------------------------------------------------------------
/lexer/lexer.go:
--------------------------------------------------------------------------------
1 | package lexer
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/joetifa2003/windlang/token"
7 | )
8 |
9 | type Lexer struct {
10 | input []rune
11 | position int // current position in input (points to current char)
12 | readPosition int // current reading position in input (after current char)
13 | ch rune // current char under examination
14 | Line int
15 | }
16 |
17 | func New(input string) *Lexer {
18 | l := Lexer{input: []rune(input), Line: 1}
19 | l.readChar()
20 |
21 | return &l
22 | }
23 |
24 | func (l *Lexer) readChar() {
25 | if l.readPosition >= len(l.input) {
26 | l.ch = 0
27 | } else {
28 | l.ch = l.input[l.readPosition]
29 | }
30 |
31 | l.position = l.readPosition
32 | l.readPosition++
33 | }
34 |
35 | func (l *Lexer) NextToken() token.Token {
36 | var tok token.Token
37 |
38 | l.skipWhitespace()
39 |
40 | switch l.ch {
41 | case '=':
42 | if l.peekChar() == '=' {
43 | l.readChar()
44 | tok = token.Token{Type: token.EQ, Literal: "=="}
45 | } else {
46 | tok = l.newToken(token.ASSIGN, l.ch)
47 | }
48 | case '&':
49 | if l.peekChar() == '&' {
50 | l.readChar()
51 |
52 | tok = token.Token{Type: token.AND, Literal: "&&"}
53 | } else {
54 | tok = token.Token{Type: token.ILLEGAL, Literal: string(l.ch) + string(l.peekChar())}
55 | }
56 | case '|':
57 | if l.peekChar() == '|' {
58 | l.readChar()
59 |
60 | tok = token.Token{Type: token.OR, Literal: "||"}
61 | } else {
62 | tok = token.Token{Type: token.ILLEGAL, Literal: string(l.ch) + string(l.peekChar())}
63 | }
64 | case ';':
65 | tok = l.newToken(token.SEMICOLON, l.ch)
66 | case '(':
67 | tok = l.newToken(token.LPAREN, l.ch)
68 | case ')':
69 | tok = l.newToken(token.RPAREN, l.ch)
70 | case ',':
71 | tok = l.newToken(token.COMMA, l.ch)
72 | case '%':
73 | tok = l.newToken(token.MODULO, l.ch)
74 | case '+':
75 | if l.peekChar() == '+' {
76 | l.readChar()
77 | tok = token.Token{Type: token.PLUSPLUS, Literal: "++"}
78 | } else {
79 | tok = l.newToken(token.PLUS, l.ch)
80 | }
81 | case '-':
82 | tok = l.newToken(token.MINUS, l.ch)
83 | case '!':
84 | if l.peekChar() == '=' {
85 | l.readChar()
86 | tok = token.Token{Type: token.NOT_EQ, Literal: "!="}
87 | } else {
88 | tok = l.newToken(token.BANG, l.ch)
89 | }
90 | case '*':
91 | tok = l.newToken(token.ASTERISK, l.ch)
92 | case '/':
93 | if l.peekChar() == '/' {
94 | l.readChar()
95 |
96 | // Skip comment
97 | for l.ch != '\n' && l.ch != 0 {
98 | l.readChar()
99 | }
100 |
101 | return l.NextToken()
102 | } else {
103 | tok = l.newToken(token.SLASH, l.ch)
104 | }
105 | case '<':
106 | if l.peekChar() == '=' {
107 | l.readChar()
108 |
109 | tok = token.Token{Type: token.LT_EQ, Literal: "<="}
110 | } else {
111 | tok = l.newToken(token.LT, l.ch)
112 | }
113 | case '>':
114 | if l.peekChar() == '=' {
115 | l.readChar()
116 |
117 | tok = token.Token{Type: token.GT_EQ, Literal: ">="}
118 | } else {
119 | tok = l.newToken(token.GT, l.ch)
120 | }
121 | case '{':
122 | tok = l.newToken(token.LBRACE, l.ch)
123 | case '}':
124 | tok = l.newToken(token.RBRACE, l.ch)
125 | case '[':
126 | tok = l.newToken(token.LBRACKET, l.ch)
127 | case ']':
128 | tok = l.newToken(token.RBRACKET, l.ch)
129 | case '"':
130 | tok.Type = token.STRING
131 | tok.Literal = l.readString()
132 | case ':':
133 | tok = l.newToken(token.COLON, l.ch)
134 | case '.':
135 | if l.peekChar() == '.' {
136 | l.readChar()
137 |
138 | tok = token.Token{Type: token.DOTDOT, Literal: ".."}
139 | } else {
140 | tok = l.newToken(token.DOT, l.ch)
141 | }
142 | case 0:
143 | tok.Literal = ""
144 | tok.Type = token.EOF
145 | default:
146 | if isLetter(l.ch) {
147 | tok.Literal = l.readIdentifier()
148 | tok.Type = token.LookupIdent(tok.Literal)
149 | tok.Line = l.Line
150 |
151 | return tok
152 | } else if isDigit(l.ch) {
153 | tok.Literal, tok.Type = l.readNumber()
154 | tok.Line = l.Line
155 |
156 | return tok
157 | } else {
158 | tok = l.newToken(token.ILLEGAL, l.ch)
159 | }
160 | }
161 |
162 | l.readChar()
163 |
164 | tok.Line = l.Line
165 | return tok
166 | }
167 |
168 | func (l *Lexer) readIdentifier() string {
169 | position := l.position
170 |
171 | for isLetter(l.ch) {
172 | l.readChar()
173 | }
174 |
175 | return string(l.input[position:l.position])
176 | }
177 |
178 | func (l *Lexer) readString() string {
179 | position := l.position + 1
180 |
181 | for {
182 | l.readChar()
183 |
184 | if l.ch == '"' || l.ch == 0 {
185 | break
186 | }
187 | }
188 |
189 | return escapeCharacters(string(l.input[position:l.position]))
190 | }
191 |
192 | func (l *Lexer) readNumber() (string, token.TokenType) {
193 | position := l.position
194 |
195 | dotCount := 0
196 | for isDigit(l.ch) || l.ch == '.' {
197 | l.readChar()
198 |
199 | if l.ch == '.' {
200 | dotCount++
201 | }
202 | }
203 |
204 | if dotCount == 1 {
205 | return string(l.input[position:l.position]), token.FLOAT
206 | } else {
207 | return string(l.input[position:l.position]), token.INT
208 | }
209 | }
210 |
211 | func (l *Lexer) skipWhitespace() {
212 | for l.ch == ' ' || l.ch == '\t' || l.ch == '\n' || l.ch == '\r' {
213 | if l.ch == '\n' {
214 | l.Line++
215 | }
216 |
217 | l.readChar()
218 | }
219 | }
220 |
221 | func (l *Lexer) peekChar() rune {
222 | if l.readPosition >= len(l.input) {
223 | return 0
224 | } else {
225 | return l.input[l.readPosition]
226 | }
227 | }
228 |
229 | func (l *Lexer) newToken(tokenType token.TokenType, ch rune) token.Token {
230 | return token.Token{Type: tokenType, Literal: string(ch)}
231 | }
232 |
233 | func isLetter(ch rune) bool {
234 | return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_'
235 | }
236 |
237 | func isDigit(ch rune) bool {
238 | return '0' <= ch && ch <= '9'
239 | }
240 |
241 | func escapeCharacters(str string) string {
242 | str = strings.ReplaceAll(str, `\n`, "\n")
243 | str = strings.ReplaceAll(str, `\t`, "\t")
244 | str = strings.ReplaceAll(str, `\r`, "\r")
245 | str = strings.ReplaceAll(str, `\b`, "\b")
246 | str = strings.ReplaceAll(str, `\f`, "\f")
247 | str = strings.ReplaceAll(str, `\a`, "\a")
248 | str = strings.ReplaceAll(str, `\v`, "\v")
249 |
250 | return str
251 | }
252 |
--------------------------------------------------------------------------------
/lexer/lexer_test.go:
--------------------------------------------------------------------------------
1 | package lexer
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/joetifa2003/windlang/token"
7 |
8 | "github.com/stretchr/testify/assert"
9 | )
10 |
11 | var nextTokenTestCases = []struct {
12 | input string
13 | expectedTokens []token.Token
14 | }{
15 | {
16 | input: "(){},;",
17 | expectedTokens: []token.Token{
18 | {Type: token.LPAREN, Literal: "(", Line: 1},
19 | {Type: token.RPAREN, Literal: ")", Line: 1},
20 | {Type: token.LBRACE, Literal: "{", Line: 1},
21 | {Type: token.RBRACE, Literal: "}", Line: 1},
22 | {Type: token.COMMA, Literal: ",", Line: 1},
23 | {Type: token.SEMICOLON, Literal: ";", Line: 1},
24 | {Type: token.EOF, Literal: "", Line: 1},
25 | },
26 | },
27 | {
28 | input: "+-/*",
29 | expectedTokens: []token.Token{
30 | {Type: token.PLUS, Literal: "+", Line: 1},
31 | {Type: token.MINUS, Literal: "-", Line: 1},
32 | {Type: token.SLASH, Literal: "/", Line: 1},
33 | {Type: token.ASTERISK, Literal: "*", Line: 1},
34 | {Type: token.EOF, Literal: "", Line: 1},
35 | },
36 | },
37 | {
38 | input: "!= == > < <= >=",
39 | expectedTokens: []token.Token{
40 | {Type: token.NOT_EQ, Literal: "!=", Line: 1},
41 | {Type: token.EQ, Literal: "==", Line: 1},
42 | {Type: token.GT, Literal: ">", Line: 1},
43 | {Type: token.LT, Literal: "<", Line: 1},
44 | {Type: token.LT_EQ, Literal: "<=", Line: 1},
45 | {Type: token.GT_EQ, Literal: ">=", Line: 1},
46 | {Type: token.EOF, Literal: "", Line: 1},
47 | },
48 | },
49 | {
50 | input: `true false 1 3.14 "hello" x`,
51 | expectedTokens: []token.Token{
52 | {Type: token.TRUE, Literal: "true", Line: 1},
53 | {Type: token.FALSE, Literal: "false", Line: 1},
54 | {Type: token.INT, Literal: "1", Line: 1},
55 | {Type: token.FLOAT, Literal: "3.14", Line: 1},
56 | {Type: token.STRING, Literal: "hello", Line: 1},
57 | {Type: token.IDENT, Literal: "x", Line: 1},
58 | {Type: token.EOF, Literal: "", Line: 1},
59 | },
60 | },
61 | }
62 |
63 | func TestNextToken(t *testing.T) {
64 | assert := assert.New(t)
65 |
66 | for _, testCase := range nextTokenTestCases {
67 | lexer := New(testCase.input)
68 |
69 | for _, expectedToken := range testCase.expectedTokens {
70 | actualToken := lexer.NextToken()
71 | assert.Equal(expectedToken, actualToken)
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/joetifa2003/windlang/cmd"
5 | )
6 |
7 | func main() {
8 | cmd.Execute()
9 | }
10 |
--------------------------------------------------------------------------------
/opcode/opcode.go:
--------------------------------------------------------------------------------
1 | package opcode
2 |
3 | type OpCode int
4 |
5 | const (
6 | OP_CONST OpCode = iota // args: [const index]
7 | OP_ADD
8 | OP_SUBTRACT
9 | OP_MULTIPLY
10 | OP_DIVIDE
11 | OP_MODULO
12 | OP_LESS
13 | OP_LESSEQ
14 | OP_LET // args: [index]
15 | OP_EQ
16 | OP_JUMP_FALSE // args: [offset]
17 | OP_JUMP // args: [offset]
18 | OP_BLOCK // args: [N of variables]
19 | OP_END_BLOCK
20 | OP_SET // args: [index, scope index]
21 | OP_GET // args: [index, scope index]
22 | OP_INC // args: [index, scope index]
23 | OP_POP
24 | OP_ECHO
25 | OP_ARRAY // args: [n of elements]
26 | )
27 |
--------------------------------------------------------------------------------
/parser/parser.go:
--------------------------------------------------------------------------------
1 | package parser
2 |
3 | import (
4 | "fmt"
5 | "path/filepath"
6 | "strconv"
7 | "strings"
8 |
9 | "github.com/joetifa2003/windlang/ast"
10 | "github.com/joetifa2003/windlang/lexer"
11 | "github.com/joetifa2003/windlang/token"
12 | )
13 |
14 | type (
15 | prefixParseFn func() ast.Expression
16 | infixParseFn func(ast.Expression) ast.Expression
17 | )
18 |
19 | const (
20 | _ int = iota
21 | LOWEST
22 | ASSIGN // =
23 | OR // ||
24 | AND // &&
25 | EQUALS // ==
26 | LessGreater // > or <
27 | SUM // +
28 | PRODUCT // *
29 | PREFIX // -X or !X
30 | POSTFIX // x++ or x--
31 | HIGHEST
32 | )
33 |
34 | type ParserError struct {
35 | Token token.Token
36 | Msg string
37 | }
38 |
39 | type Parser struct {
40 | lexer *lexer.Lexer
41 | filePath string
42 |
43 | Errors []ParserError
44 |
45 | curToken token.Token
46 | peekToken token.Token
47 | }
48 |
49 | func New(l *lexer.Lexer, filePath string) *Parser {
50 | p := Parser{lexer: l, filePath: filePath}
51 |
52 | p.nextToken()
53 | p.nextToken()
54 |
55 | return &p
56 | }
57 |
58 | func (p *Parser) ParseProgram() *ast.Program {
59 | program := ast.Program{
60 | Statements: []ast.Statement{},
61 | }
62 |
63 | for p.curToken.Type != token.EOF {
64 | statement := p.parseStatement()
65 |
66 | if statement != nil {
67 | program.Statements = append(program.Statements, statement)
68 | }
69 | }
70 |
71 | return &program
72 | }
73 |
74 | func (p *Parser) getPrecedence(tokenType token.TokenType) int {
75 | switch tokenType {
76 | case token.EQ, token.NOT_EQ:
77 | return EQUALS
78 | case token.ASSIGN:
79 | return ASSIGN
80 | case token.LT, token.GT:
81 | return LessGreater
82 | case token.PLUS, token.MINUS:
83 | return SUM
84 | case token.SLASH, token.ASTERISK, token.MODULO:
85 | return PRODUCT
86 | case token.PLUSPLUS, token.MINUSMINUS:
87 | return POSTFIX
88 | case token.AND:
89 | return AND
90 | case token.OR:
91 | return OR
92 | case token.LPAREN, token.LBRACKET, token.DOT:
93 | return HIGHEST
94 | }
95 |
96 | return LOWEST
97 | }
98 |
99 | func (p *Parser) getPrefixParseFn(tokenType token.TokenType) prefixParseFn {
100 | switch tokenType {
101 | case token.IDENT:
102 | return p.parseIdentifier
103 | case token.INT:
104 | return p.parseIntegerLiteral
105 | case token.FLOAT:
106 | return p.parseFloatLiteral
107 | case token.BANG, token.MINUS:
108 | return p.parsePrefixExpression
109 | case token.TRUE, token.FALSE:
110 | return p.parseBoolean
111 | case token.LPAREN:
112 | return p.parseGroupedExpression
113 | case token.IF:
114 | return p.parseIfExpression
115 | case token.FUNCTION:
116 | return p.parseFunctionLiteral
117 | case token.STRING:
118 | return p.parseStringLiteral
119 | case token.LBRACKET:
120 | return p.parseArrayLiteral
121 | case token.NIL:
122 | return p.parseNilLiteral
123 | case token.LBRACE:
124 | return p.parseHashLiteral
125 | }
126 |
127 | return nil
128 | }
129 |
130 | func (p *Parser) getInfixParseFn(tokenType token.TokenType) infixParseFn {
131 | switch tokenType {
132 | case token.PLUS, token.MINUS, token.SLASH, token.ASTERISK, token.EQ, token.NOT_EQ, token.LT, token.LT_EQ, token.GT, token.GT_EQ, token.MODULO, token.AND, token.OR:
133 | return p.parseInfixExpression
134 | case token.LPAREN:
135 | return p.parseCallExpression
136 | case token.PLUSPLUS, token.MINUSMINUS:
137 | return p.parsePostfixExpression
138 | case token.ASSIGN:
139 | return p.parseAssignExpression
140 | case token.LBRACKET:
141 | return p.parseIndexExpression
142 | case token.DOT:
143 | return p.parseDotExpression
144 | }
145 |
146 | return nil
147 | }
148 |
149 | func (p *Parser) nextToken() {
150 | p.curToken = p.peekToken
151 | p.peekToken = p.lexer.NextToken()
152 | }
153 |
154 | func (p *Parser) parseStatement() ast.Statement {
155 | switch p.curToken.Type {
156 | case token.LET, token.CONST:
157 | return p.parseVarStatement()
158 | case token.RETURN:
159 | return p.parseReturnStatement()
160 | case token.FOR:
161 | return p.parseForStatement()
162 | case token.LBRACE:
163 | return p.parseBlockStatement()
164 | case token.INCLUDE:
165 | return p.parseIncludeStatement()
166 | case token.WHILE:
167 | return p.parseWhileStatement()
168 | // case token.BREAK:
169 | // return p.parseBreakStatement()
170 | // case token.CONTINUE:
171 | // return p.parseContinueStatement()
172 | case token.ECHO:
173 | return p.parseEchoStatement()
174 | default:
175 | return p.parseExpressionStatement()
176 | }
177 | }
178 |
179 | func (p *Parser) parseVarStatement() ast.Statement {
180 | stmt := ast.LetStatement{Token: p.curToken}
181 | stmt.Constant = p.curToken.Type == token.CONST
182 |
183 | p.nextToken()
184 |
185 | stmt.Name = &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal}
186 |
187 | p.expectCurrent(token.IDENT)
188 |
189 | p.expectCurrent(token.ASSIGN)
190 |
191 | stmt.Value = p.parseExpression(LOWEST)
192 |
193 | p.expectCurrent(token.SEMICOLON)
194 |
195 | return &stmt
196 | }
197 |
198 | func (p *Parser) parseReturnStatement() *ast.ReturnStatement {
199 | stmt := ast.ReturnStatement{Token: p.curToken}
200 |
201 | p.nextToken()
202 |
203 | stmt.ReturnValue = p.parseExpression(LOWEST)
204 |
205 | p.expectCurrent(token.SEMICOLON)
206 |
207 | return &stmt
208 | }
209 |
210 | func (p *Parser) parseForStatement() ast.Statement {
211 | stmt := ast.ForStatement{Token: p.curToken}
212 |
213 | p.nextToken()
214 |
215 | p.expectCurrent(token.LPAREN)
216 |
217 | stmt.Initializer = p.parseStatement()
218 |
219 | stmt.Condition = p.parseExpression(LOWEST)
220 |
221 | p.expectCurrent(token.SEMICOLON)
222 |
223 | stmt.Increment = p.parseExpression(LOWEST)
224 |
225 | p.expectCurrent(token.RPAREN)
226 |
227 | stmt.Body = p.parseBlockStatement()
228 |
229 | return &stmt
230 | }
231 |
232 | func (p *Parser) parseExpressionStatement() *ast.ExpressionStatement {
233 | stmt := ast.ExpressionStatement{Token: p.curToken}
234 |
235 | stmt.Expression = p.parseExpression(LOWEST)
236 |
237 | if p.currentTokenIs(token.SEMICOLON) {
238 | p.nextToken()
239 | }
240 |
241 | return &stmt
242 | }
243 |
244 | func (p *Parser) parseBlockStatement() *ast.BlockStatement {
245 | block := ast.BlockStatement{Token: p.curToken}
246 | block.Statements = []ast.Statement{}
247 |
248 | p.expectCurrent(token.LBRACE)
249 |
250 | for !p.currentTokenIs(token.RBRACE) && !p.currentTokenIs(token.EOF) {
251 | stmt := p.parseStatement()
252 | if _, ok := stmt.(*ast.LetStatement); ok {
253 | block.VarCount++
254 | }
255 |
256 | block.Statements = append(block.Statements, stmt)
257 | }
258 |
259 | p.expectCurrent(token.RBRACE)
260 |
261 | return &block
262 | }
263 |
264 | func (p *Parser) parseIncludeStatement() *ast.IncludeStatement {
265 | stmt := ast.IncludeStatement{Token: p.curToken}
266 |
267 | p.nextToken()
268 |
269 | if strings.Contains(p.curToken.Literal, "./") {
270 | stmt.Path = filepath.Join(filepath.Dir(p.filePath), p.curToken.Literal)
271 | } else {
272 | stmt.Path = p.curToken.Literal
273 | }
274 |
275 | p.nextToken()
276 |
277 | if p.currentTokenIs(token.AS) {
278 | p.nextToken()
279 |
280 | stmt.Alias = &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal}
281 |
282 | p.expectCurrent(token.IDENT)
283 |
284 | p.expectCurrent(token.SEMICOLON)
285 | } else {
286 | p.expectCurrent(token.SEMICOLON)
287 | }
288 |
289 | return &stmt
290 | }
291 |
292 | func (p *Parser) parseWhileStatement() *ast.WhileStatement {
293 | stmt := ast.WhileStatement{Token: p.curToken}
294 |
295 | p.nextToken()
296 |
297 | p.expectCurrent(token.LPAREN)
298 |
299 | stmt.Condition = p.parseExpression(LOWEST)
300 |
301 | p.expectCurrent(token.RPAREN)
302 |
303 | stmt.Body = p.parseBlockStatement()
304 |
305 | return &stmt
306 | }
307 |
308 | func (p *Parser) parseEchoStatement() *ast.EchoStatement {
309 | stmt := ast.EchoStatement{Token: p.curToken}
310 |
311 | p.nextToken()
312 |
313 | stmt.Value = p.parseExpression(LOWEST)
314 |
315 | p.expectCurrent(token.SEMICOLON)
316 |
317 | return &stmt
318 | }
319 |
320 | func (p *Parser) parseExpression(precedence int) ast.Expression {
321 | prefix := p.getPrefixParseFn(p.curToken.Type)
322 | if prefix == nil {
323 | msg := fmt.Sprintf("cannot parse %s as an expression", p.curToken.Literal)
324 | p.Errors = append(p.Errors, ParserError{
325 | Token: p.curToken,
326 | Msg: msg,
327 | })
328 | p.nextToken()
329 | return nil
330 | }
331 |
332 | leftExp := prefix()
333 |
334 | for !p.currentTokenIs(token.SEMICOLON) && p.curPrecedence() >= precedence {
335 | infix := p.getInfixParseFn(p.curToken.Type)
336 | if infix == nil {
337 | return leftExp
338 | }
339 |
340 | leftExp = infix(leftExp)
341 | }
342 |
343 | return leftExp
344 | }
345 |
346 | func (p *Parser) parseInfixExpression(left ast.Expression) ast.Expression {
347 | expression := ast.InfixExpression{
348 | Token: p.curToken,
349 | Operator: p.curToken.Literal,
350 | Left: left,
351 | }
352 |
353 | precedence := p.curPrecedence()
354 |
355 | p.nextToken()
356 |
357 | expression.Right = p.parseExpression(precedence)
358 |
359 | return &expression
360 | }
361 |
362 | func (p *Parser) parseIdentifier() ast.Expression {
363 | ident := ast.Identifier{Token: p.curToken, Value: p.curToken.Literal}
364 |
365 | p.nextToken()
366 |
367 | return &ident
368 | }
369 |
370 | func (p *Parser) parseIntegerLiteral() ast.Expression {
371 | integer := ast.IntegerLiteral{Token: p.curToken}
372 | value, err := strconv.Atoi(p.curToken.Literal)
373 |
374 | if err != nil {
375 | msg := fmt.Sprintf("could not parse %q as integer", p.curToken.Literal)
376 | p.Errors = append(p.Errors, ParserError{
377 | Token: p.curToken,
378 | Msg: msg,
379 | })
380 | return nil
381 | }
382 |
383 | integer.Value = value
384 |
385 | p.nextToken()
386 |
387 | return &integer
388 | }
389 |
390 | func (p *Parser) parseFloatLiteral() ast.Expression {
391 | float := ast.FloatLiteral{Token: p.curToken}
392 | value, err := strconv.ParseFloat(p.curToken.Literal, 64)
393 |
394 | if err != nil {
395 | msg := fmt.Sprintf("could not parse %q as float", p.curToken.Literal)
396 | p.Errors = append(p.Errors, ParserError{
397 | Token: p.curToken,
398 | Msg: msg,
399 | })
400 | return nil
401 | }
402 |
403 | float.Value = value
404 |
405 | p.nextToken()
406 |
407 | return &float
408 | }
409 |
410 | func (p *Parser) parseStringLiteral() ast.Expression {
411 | str := ast.StringLiteral{Token: p.curToken, Value: p.curToken.Literal}
412 |
413 | p.nextToken()
414 |
415 | return &str
416 | }
417 |
418 | func (p *Parser) parsePrefixExpression() ast.Expression {
419 | expression := ast.PrefixExpression{
420 | Token: p.curToken,
421 | Operator: p.curToken.Literal,
422 | }
423 |
424 | p.nextToken()
425 |
426 | expression.Right = p.parseExpression(PREFIX)
427 |
428 | return &expression
429 | }
430 |
431 | func (p *Parser) parseBoolean() ast.Expression {
432 | expr := ast.Boolean{Token: p.curToken, Value: p.currentTokenIs(token.TRUE)}
433 |
434 | p.nextToken()
435 |
436 | return &expr
437 | }
438 |
439 | func (p *Parser) parseGroupedExpression() ast.Expression {
440 | p.nextToken()
441 |
442 | exp := p.parseExpression(LOWEST)
443 |
444 | p.expectCurrent(token.RPAREN)
445 |
446 | return exp
447 | }
448 |
449 | func (p *Parser) parseIfExpression() ast.Expression {
450 | expression := ast.IfExpression{Token: p.curToken}
451 |
452 | p.nextToken()
453 |
454 | p.expectCurrent(token.LPAREN)
455 |
456 | expression.Condition = p.parseExpression(LOWEST)
457 |
458 | p.expectCurrent(token.RPAREN)
459 |
460 | thenStatement := p.parseStatement()
461 | expression.ThenBranch = thenStatement
462 |
463 | if p.currentTokenIs(token.ELSE) {
464 | p.nextToken()
465 |
466 | elseStatement := p.parseStatement()
467 | expression.ElseBranch = elseStatement
468 | }
469 |
470 | return &expression
471 |
472 | }
473 |
474 | func (p *Parser) parseFunctionLiteral() ast.Expression {
475 | lit := ast.FunctionLiteral{Token: p.curToken}
476 |
477 | p.nextToken()
478 |
479 | p.expectCurrent(token.LPAREN)
480 |
481 | lit.Parameters = p.parseFunctionParameters()
482 |
483 | lit.Body = p.parseBlockStatement()
484 |
485 | return &lit
486 | }
487 |
488 | func (p *Parser) parseFunctionParameters() []*ast.Identifier {
489 | identifiers := []*ast.Identifier{}
490 |
491 | if p.currentTokenIs(token.RPAREN) {
492 | p.nextToken()
493 | return identifiers
494 | }
495 |
496 | for p.peekTokenIs(token.COMMA) {
497 | ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal}
498 | identifiers = append(identifiers, ident)
499 |
500 | p.nextToken() // consume IDENT
501 | p.nextToken() // consume COMMA
502 | }
503 |
504 | // last IDENT
505 | ident := ast.Identifier{Token: p.curToken, Value: p.curToken.Literal}
506 | identifiers = append(identifiers, &ident)
507 | p.nextToken()
508 |
509 | p.expectCurrent(token.RPAREN)
510 |
511 | return identifiers
512 | }
513 |
514 | func (p *Parser) parseCallExpression(function ast.Expression) ast.Expression {
515 | exp := ast.CallExpression{Token: p.curToken, Function: function}
516 |
517 | p.nextToken()
518 |
519 | exp.Arguments = p.parseCallArguments(token.RPAREN)
520 |
521 | return &exp
522 | }
523 |
524 | func (p *Parser) parseArrayLiteral() ast.Expression {
525 | exp := ast.ArrayLiteral{Token: p.curToken}
526 |
527 | p.nextToken()
528 |
529 | exp.Value = p.parseCallArguments(token.RBRACKET)
530 |
531 | return &exp
532 | }
533 |
534 | func (p *Parser) parseNilLiteral() ast.Expression {
535 | exp := ast.NilLiteral{Token: p.curToken}
536 |
537 | p.nextToken()
538 |
539 | return &exp
540 | }
541 |
542 | func (p *Parser) parseHashLiteral() ast.Expression {
543 | hash := ast.HashLiteral{
544 | Token: p.curToken,
545 | Pairs: make(map[ast.Expression]ast.Expression),
546 | }
547 |
548 | p.nextToken()
549 |
550 | for !p.currentTokenIs(token.RBRACE) {
551 | key := p.parseExpression(LOWEST)
552 |
553 | p.expectCurrent(token.COLON)
554 |
555 | value := p.parseExpression(LOWEST)
556 |
557 | hash.Pairs[key] = value
558 |
559 | if !p.currentTokenIs(token.RBRACE) {
560 | p.expectCurrent(token.COMMA)
561 | }
562 | }
563 |
564 | p.expectCurrent(token.RBRACE)
565 |
566 | return &hash
567 | }
568 |
569 | func (p *Parser) parseCallArguments(endToken token.TokenType) []ast.Expression {
570 | args := []ast.Expression{}
571 |
572 | if p.currentTokenIs(endToken) {
573 | p.nextToken()
574 | return args
575 | }
576 |
577 | args = append(args, p.parseExpression(LOWEST))
578 |
579 | for p.currentTokenIs(token.COMMA) {
580 | p.nextToken()
581 | args = append(args, p.parseExpression(LOWEST))
582 | }
583 |
584 | p.expectCurrent(endToken)
585 |
586 | return args
587 | }
588 |
589 | func (p *Parser) parsePostfixExpression(left ast.Expression) ast.Expression {
590 | expression := ast.PostfixExpression{
591 | Token: p.curToken,
592 | Operator: p.curToken.Literal,
593 | Left: left,
594 | }
595 |
596 | p.nextToken()
597 |
598 | return &expression
599 | }
600 |
601 | func (p *Parser) parseAssignExpression(left ast.Expression) ast.Expression {
602 | expression := ast.AssignExpression{
603 | Token: p.curToken,
604 | Name: left,
605 | }
606 |
607 | precedence := p.curPrecedence()
608 |
609 | p.nextToken() // consume ASSIGN
610 |
611 | expression.Value = p.parseExpression(precedence)
612 |
613 | return &expression
614 | }
615 |
616 | func (p *Parser) parseIndexExpression(left ast.Expression) ast.Expression {
617 | exp := ast.IndexExpression{Token: p.curToken, Left: left}
618 |
619 | p.nextToken()
620 |
621 | exp.Index = p.parseExpression(LOWEST)
622 |
623 | p.expectCurrent(token.RBRACKET)
624 |
625 | return &exp
626 | }
627 |
628 | func (p *Parser) parseDotExpression(left ast.Expression) ast.Expression {
629 | exp := ast.IndexExpression{Token: p.curToken, Left: left}
630 |
631 | p.expectPeek(token.IDENT)
632 |
633 | p.curToken.Type = token.STRING
634 | exp.Index = &ast.StringLiteral{Token: p.curToken, Value: p.curToken.Literal}
635 |
636 | p.nextToken()
637 |
638 | return &exp
639 | }
640 |
641 | func (p *Parser) curPrecedence() int {
642 | return p.getPrecedence(p.curToken.Type)
643 | }
644 |
645 | func (p *Parser) expectPeek(tokenType token.TokenType) bool {
646 | if p.peekTokenIs(tokenType) {
647 | p.nextToken()
648 | return true
649 | }
650 |
651 | p.peekError(tokenType)
652 | return false
653 | }
654 |
655 | func (p *Parser) expectCurrent(tokenType token.TokenType) bool {
656 | if p.currentTokenIs(tokenType) {
657 | p.nextToken()
658 | return true
659 | }
660 |
661 | p.currentError(tokenType)
662 | return false
663 | }
664 |
665 | func (p *Parser) peekTokenIs(tokenType token.TokenType) bool {
666 | return p.peekToken.Type == tokenType
667 | }
668 |
669 | func (p *Parser) currentTokenIs(tokenType token.TokenType) bool {
670 | return p.curToken.Type == tokenType
671 | }
672 |
673 | func (p *Parser) peekError(t token.TokenType) {
674 | msg := fmt.Sprintf("expected next token to be %s, got %s instead",
675 | t.String(), p.peekToken.Type.String())
676 |
677 | p.Errors = append(p.Errors, ParserError{
678 | Token: p.peekToken,
679 | Msg: msg,
680 | })
681 | }
682 |
683 | func (p *Parser) currentError(t token.TokenType) {
684 | msg := fmt.Sprintf("expected token to be %s, got %s instead",
685 | t.String(), p.curToken.Type.String())
686 |
687 | p.Errors = append(p.Errors, ParserError{
688 | Token: p.curToken,
689 | Msg: msg,
690 | })
691 | }
692 |
693 | func (p *Parser) ReportErrors() []string {
694 | errors := []string{}
695 |
696 | if len(p.Errors) != 0 {
697 | for _, e := range p.Errors {
698 | errors = append(errors, fmt.Sprintf("[file %s:%d]: %s", p.filePath, e.Token.Line, e.Msg))
699 | }
700 | }
701 |
702 | return errors
703 | }
704 |
--------------------------------------------------------------------------------
/token/token.go:
--------------------------------------------------------------------------------
1 | package token
2 |
3 | type Token struct {
4 | Type TokenType
5 | Literal string
6 | Line int
7 | }
8 |
9 | func LookupIdent(ident string) TokenType {
10 | if tok, ok := isKeyword(ident); ok {
11 | return tok
12 | }
13 |
14 | return IDENT
15 | }
16 |
17 | func isKeyword(ident string) (TokenType, bool) {
18 | switch ident {
19 | case "fn":
20 | return FUNCTION, true
21 | case "let":
22 | return LET, true
23 | case "const":
24 | return CONST, true
25 | case "true":
26 | return TRUE, true
27 | case "false":
28 | return FALSE, true
29 | case "if":
30 | return IF, true
31 | case "else":
32 | return ELSE, true
33 | case "return":
34 | return RETURN, true
35 | case "for":
36 | return FOR, true
37 | case "include":
38 | return INCLUDE, true
39 | case "while":
40 | return WHILE, true
41 | case "nil":
42 | return NIL, true
43 | case "as":
44 | return AS, true
45 | case "break":
46 | return BREAK, true
47 | case "continue":
48 | return CONTINUE, true
49 | case "echo":
50 | return ECHO, true
51 | }
52 |
53 | return IDENT, false
54 | }
55 |
--------------------------------------------------------------------------------
/token/tokenTypes.go:
--------------------------------------------------------------------------------
1 | package token
2 |
3 | type TokenType int
4 |
5 | const (
6 | ILLEGAL TokenType = iota
7 | EOF
8 |
9 | // Identifiers + literals
10 | IDENT // add, foobar, x, y, ...
11 | INT // 1343456
12 | STRING
13 | FLOAT
14 |
15 | // Operators
16 | ASSIGN
17 | PLUS
18 | MINUS
19 | BANG
20 | ASTERISK
21 | SLASH
22 | MODULO // %
23 | LT // <
24 | GT // >
25 | LT_EQ // <=
26 | GT_EQ // >=
27 | AND // &&
28 | OR // ||
29 | EQ // ==
30 | NOT_EQ // !=
31 | PLUSPLUS
32 | MINUSMINUS
33 | DOTDOT
34 |
35 | // Delimiters
36 | COMMA
37 | COLON
38 | DOT
39 | SEMICOLON
40 | LPAREN
41 | RPAREN
42 | LBRACE
43 | RBRACE
44 | LBRACKET
45 | RBRACKET
46 |
47 | // Keywords
48 | FUNCTION
49 | LET
50 | CONST
51 | TRUE
52 | FALSE
53 | IF
54 | ELSE
55 | RETURN
56 | FOR
57 | INCLUDE
58 | AS
59 | WHILE
60 | NIL
61 | BREAK
62 | CONTINUE
63 | ECHO
64 | )
65 |
66 | func (t *TokenType) String() string {
67 | switch *t {
68 | case ILLEGAL:
69 | return "ILLEGAL"
70 | case EOF:
71 | return "EOF"
72 | case IDENT:
73 | return "IDENT"
74 | case INT:
75 | return "INT"
76 | case ASSIGN:
77 | return "="
78 | case PLUS:
79 | return "+"
80 | case MINUS:
81 | return "-"
82 | case BANG:
83 | return "!"
84 | case ASTERISK:
85 | return "*"
86 | case SLASH:
87 | return "/"
88 | case LT:
89 | return "<"
90 | case GT:
91 | return ">"
92 | case EQ:
93 | return "=="
94 | case NOT_EQ:
95 | return "!="
96 | case COMMA:
97 | return ","
98 | case SEMICOLON:
99 | return ";"
100 | case LPAREN:
101 | return "("
102 | case RPAREN:
103 | return ")"
104 | case LBRACE:
105 | return "{"
106 | case RBRACE:
107 | return "}"
108 | case FUNCTION:
109 | return "FUNCTION"
110 | case LET:
111 | return "LET"
112 | case TRUE:
113 | return "TRUE"
114 | case FALSE:
115 | return "FALSE"
116 | case IF:
117 | return "IF"
118 | case ELSE:
119 | return "ELSE"
120 | case RETURN:
121 | return "RETURN"
122 | case STRING:
123 | return "STRING"
124 | case FOR:
125 | return "FOR"
126 | case PLUSPLUS:
127 | return "++"
128 | case MINUSMINUS:
129 | return "--"
130 | case INCLUDE:
131 | return "INCLUDE"
132 | case WHILE:
133 | return "WHILE"
134 | case FLOAT:
135 | return "FLOAT"
136 | case NIL:
137 | return "NIL"
138 | case AS:
139 | return "AS"
140 | case DOTDOT:
141 | return "DOTDOT"
142 | case BREAK:
143 | return "BREAK"
144 | case CONTINUE:
145 | return "CONTINUE"
146 | case ECHO:
147 | return "ECHO"
148 | default:
149 | return "UNKNOWN"
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/value/value.go:
--------------------------------------------------------------------------------
1 | package value
2 |
3 | import (
4 | "fmt"
5 | "unsafe"
6 | )
7 |
8 | type ValueType int
9 |
10 | const (
11 | VALUE_INT ValueType = iota
12 | VALUE_BOOL
13 | VALUE_NIL
14 | VALUE_ARRAY
15 | VALUE_OBJECT
16 | )
17 |
18 | type Value struct {
19 | VType ValueType
20 | primitiveData [8]byte // int64, float64, bool
21 | nonPrimitive *NonPrimitiveData
22 | }
23 |
24 | type NonPrimitiveData struct {
25 | ArrayV []Value
26 | }
27 |
28 | func NewNilValue() Value {
29 | return Value{
30 | VType: VALUE_NIL,
31 | }
32 | }
33 |
34 | func NewIntValue(v int) Value {
35 | value := Value{
36 | VType: VALUE_INT,
37 | primitiveData: [8]byte{},
38 | }
39 |
40 | *(*int)(unsafe.Pointer(&value.primitiveData[0])) = v
41 |
42 | return value
43 | }
44 |
45 | func NewBoolValue(v bool) Value {
46 | value := Value{
47 | VType: VALUE_BOOL,
48 | primitiveData: [8]byte{},
49 | }
50 |
51 | *(*bool)(unsafe.Pointer(&value.primitiveData[0])) = v
52 |
53 | return value
54 | }
55 |
56 | func NewArrayValue(v []Value) Value {
57 | return Value{
58 | VType: VALUE_ARRAY,
59 | nonPrimitive: &NonPrimitiveData{
60 | ArrayV: v,
61 | },
62 | }
63 | }
64 |
65 | func (v *Value) GetArray() []Value {
66 | return v.nonPrimitive.ArrayV
67 | }
68 |
69 | func (v *Value) GetInt() int {
70 | return *(*int)(unsafe.Pointer(&v.primitiveData[0]))
71 | }
72 |
73 | func (v *Value) GetIntPtr() *int {
74 | return (*int)(unsafe.Pointer(&v.primitiveData[0]))
75 | }
76 |
77 | func (v *Value) GetBool() bool {
78 | return *(*bool)(unsafe.Pointer(&v.primitiveData[0]))
79 | }
80 |
81 | func (v *Value) String() string {
82 | switch v.VType {
83 | case VALUE_INT:
84 | return fmt.Sprint(v.GetInt())
85 | case VALUE_BOOL:
86 | return fmt.Sprint(v.GetBool())
87 | case VALUE_NIL:
88 | return "nil"
89 | case VALUE_ARRAY:
90 | return fmt.Sprint(v.GetArray())
91 | }
92 |
93 | panic("Unimplemented String() for value type")
94 | }
95 |
--------------------------------------------------------------------------------
/vm/env.go:
--------------------------------------------------------------------------------
1 | package vm
2 |
3 | import (
4 | "github.com/joetifa2003/windlang/value"
5 | )
6 |
7 | type Environment struct {
8 | Store []value.Value
9 | }
10 |
11 | func NewEnvironment(varCount int) Environment {
12 | store := make([]value.Value, varCount)
13 | return Environment{
14 | Store: store,
15 | }
16 | }
17 |
18 | type EnvironmentStack struct {
19 | Value []Environment
20 | p int
21 | }
22 |
23 | func NewEnvironmentStack() EnvironmentStack {
24 | return EnvironmentStack{
25 | Value: make([]Environment, 2048),
26 | }
27 | }
28 |
29 | func (s *EnvironmentStack) pop() Environment {
30 | lastEle := (s.Value)[s.p-1]
31 | (s.Value)[s.p-1] = Environment{Store: nil}
32 | s.p--
33 | return lastEle
34 | }
35 |
36 | func (s *EnvironmentStack) push(env Environment) {
37 | s.Value[s.p] = env
38 | s.p++
39 | }
40 |
41 | func (s *EnvironmentStack) let(index int, val value.Value) {
42 | env := &s.Value[s.p-1]
43 |
44 | env.Store[index] = val
45 | }
46 |
47 | func (s *EnvironmentStack) get(scopeIndex, index int) value.Value {
48 | env := &s.Value[scopeIndex]
49 |
50 | return env.Store[index]
51 | }
52 |
53 | func (s *EnvironmentStack) set(scopeIndex, index int, value value.Value) value.Value {
54 | env := &s.Value[scopeIndex]
55 | env.Store[index] = value
56 |
57 | return env.Store[index]
58 | }
59 |
60 | func (s *EnvironmentStack) increment(scopeIndex, index int) (ok bool) {
61 | env := &s.Value[scopeIndex]
62 | val := &env.Store[index]
63 | if val.VType != value.VALUE_INT {
64 | return false
65 | }
66 |
67 | ptr := val.GetIntPtr()
68 | *ptr++
69 |
70 | return true
71 | }
72 |
--------------------------------------------------------------------------------
/vm/stack.go:
--------------------------------------------------------------------------------
1 | package vm
2 |
3 | import (
4 | "github.com/joetifa2003/windlang/value"
5 | )
6 |
7 | type Stack struct {
8 | Value []value.Value
9 | }
10 |
11 | func NewStack() Stack {
12 | return Stack{
13 | Value: make([]value.Value, 0, 2048),
14 | }
15 | }
16 |
17 | func (s *Stack) pop() value.Value {
18 | lastEle := s.Value[len(s.Value)-1]
19 | s.Value = s.Value[:len(s.Value)-1]
20 | return lastEle
21 | }
22 |
23 | func (s *Stack) push(value value.Value) {
24 | s.Value = append(s.Value, value)
25 | }
26 |
--------------------------------------------------------------------------------
/vm/vm.go:
--------------------------------------------------------------------------------
1 | package vm
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/joetifa2003/windlang/opcode"
7 | "github.com/joetifa2003/windlang/value"
8 | )
9 |
10 | type VM struct {
11 | Stack Stack
12 | EnvStack EnvironmentStack
13 | Constants []value.Value
14 | }
15 |
16 | func NewVM(constants []value.Value) VM {
17 | stack := NewStack()
18 | envStack := NewEnvironmentStack()
19 |
20 | return VM{
21 | Stack: stack,
22 | EnvStack: envStack,
23 | Constants: constants,
24 | }
25 | }
26 |
27 | func (v *VM) Interpret(instructions []opcode.OpCode) {
28 | ip := 0
29 | for ip < len(instructions) {
30 | switch instructions[ip] {
31 | case opcode.OP_CONST:
32 | ip++
33 | value := v.Constants[instructions[ip]]
34 |
35 | v.Stack.push(value)
36 |
37 | case opcode.OP_ADD:
38 | right := v.Stack.pop()
39 | left := v.Stack.pop()
40 |
41 | switch {
42 | case left.VType == value.VALUE_INT && right.VType == value.VALUE_INT:
43 | leftNumber := left.GetInt()
44 | rightNumber := right.GetInt()
45 |
46 | v.Stack.push(value.NewIntValue(leftNumber + rightNumber))
47 | }
48 |
49 | case opcode.OP_SUBTRACT:
50 | right := v.Stack.pop()
51 | left := v.Stack.pop()
52 |
53 | switch {
54 | case left.VType == value.VALUE_INT && right.VType == value.VALUE_INT:
55 | leftNumber := left.GetInt()
56 | rightNumber := right.GetInt()
57 |
58 | v.Stack.push(value.NewIntValue(leftNumber - rightNumber))
59 | }
60 |
61 | case opcode.OP_MULTIPLY:
62 | right := v.Stack.pop()
63 | left := v.Stack.pop()
64 |
65 | switch {
66 | case left.VType == value.VALUE_INT && right.VType == value.VALUE_INT:
67 | leftNumber := left.GetInt()
68 | rightNumber := right.GetInt()
69 |
70 | v.Stack.push(value.NewIntValue(leftNumber * rightNumber))
71 | }
72 |
73 | case opcode.OP_MODULO:
74 | right := v.Stack.pop()
75 | left := v.Stack.pop()
76 |
77 | switch {
78 | case left.VType == value.VALUE_INT && right.VType == value.VALUE_INT:
79 | leftNumber := left.GetInt()
80 | rightNumber := right.GetInt()
81 |
82 | v.Stack.push(value.NewIntValue(leftNumber % rightNumber))
83 | }
84 |
85 | case opcode.OP_DIVIDE:
86 | right := v.Stack.pop()
87 | left := v.Stack.pop()
88 |
89 | switch {
90 | case left.VType == value.VALUE_INT && right.VType == value.VALUE_INT:
91 | leftNumber := left.GetInt()
92 | rightNumber := right.GetInt()
93 |
94 | v.Stack.push(value.NewIntValue(leftNumber / rightNumber))
95 | }
96 |
97 | case opcode.OP_EQ:
98 | right := v.Stack.pop()
99 | left := v.Stack.pop()
100 |
101 | switch {
102 | case left.VType == value.VALUE_INT && right.VType == value.VALUE_INT:
103 | leftNumber := left.GetInt()
104 | rightNumber := right.GetInt()
105 |
106 | v.Stack.push(value.NewBoolValue(leftNumber == rightNumber))
107 | }
108 |
109 | case opcode.OP_LESSEQ:
110 | right := v.Stack.pop()
111 | left := v.Stack.pop()
112 |
113 | switch {
114 | case left.VType == value.VALUE_INT && right.VType == value.VALUE_INT:
115 | leftNumber := left.GetInt()
116 | rightNumber := right.GetInt()
117 |
118 | v.Stack.push(value.NewBoolValue(leftNumber <= rightNumber))
119 | }
120 |
121 | case opcode.OP_JUMP_FALSE:
122 | operand := v.Stack.pop()
123 | ip++
124 | offset := int(instructions[ip])
125 |
126 | if !isTruthy(operand) {
127 | ip += offset
128 |
129 | continue
130 | }
131 |
132 | case opcode.OP_JUMP:
133 | ip++
134 | offset := int(instructions[ip])
135 |
136 | ip += offset
137 |
138 | continue
139 |
140 | case opcode.OP_BLOCK:
141 | ip++
142 | varCount := int(instructions[ip])
143 |
144 | v.EnvStack.push(NewEnvironment(varCount))
145 |
146 | case opcode.OP_END_BLOCK:
147 | v.EnvStack.pop()
148 |
149 | case opcode.OP_LET:
150 | value := v.Stack.pop()
151 |
152 | ip++
153 | index := int(instructions[ip])
154 |
155 | v.EnvStack.let(index, value)
156 |
157 | case opcode.OP_SET:
158 | value := v.Stack.pop()
159 |
160 | ip++
161 | index := int(instructions[ip])
162 | ip++
163 | scopeIndex := int(instructions[ip])
164 |
165 | newVal := v.EnvStack.set(scopeIndex, index, value)
166 | v.Stack.push(newVal)
167 |
168 | case opcode.OP_GET:
169 | ip++
170 | index := int(instructions[ip])
171 | ip++
172 | scopeIndex := int(instructions[ip])
173 |
174 | value := v.EnvStack.get(scopeIndex, index)
175 | v.Stack.push(value)
176 |
177 | case opcode.OP_POP:
178 | if len(v.Stack.Value) != 0 {
179 | v.Stack.pop()
180 | }
181 |
182 | case opcode.OP_ECHO:
183 | operand := v.Stack.pop()
184 |
185 | fmt.Println(operand.String())
186 |
187 | case opcode.OP_ARRAY:
188 | ip++
189 | n := int(instructions[ip])
190 | values := make([]value.Value, n)
191 |
192 | for i := 0; i < n; i++ {
193 | values[i] = v.Stack.pop()
194 | }
195 |
196 | v.Stack.push(value.NewArrayValue(values))
197 |
198 | case opcode.OP_INC:
199 | ip++
200 | index := int(instructions[ip])
201 | ip++
202 | scopeIndex := int(instructions[ip])
203 |
204 | ok := v.EnvStack.increment(scopeIndex, index)
205 | if !ok {
206 | panic("")
207 | }
208 |
209 | default:
210 | panic("Unimplemented OpCode " + fmt.Sprint(instructions[ip]))
211 | }
212 |
213 | ip++
214 | }
215 | }
216 |
217 | func isTruthy(input value.Value) bool {
218 | switch input.VType {
219 | case value.VALUE_BOOL:
220 | return input.GetBool()
221 | default:
222 | return true
223 | }
224 | }
225 |
--------------------------------------------------------------------------------
/vscode-extention/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | // A launch configuration that launches the extension inside a new window
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 | {
6 | "version": "0.2.0",
7 | "configurations": [
8 | {
9 | "name": "Extension",
10 | "type": "extensionHost",
11 | "request": "launch",
12 | "args": [
13 | "--extensionDevelopmentPath=${workspaceFolder}"
14 | ]
15 | }
16 | ]
17 | }
--------------------------------------------------------------------------------
/vscode-extention/.vscodeignore:
--------------------------------------------------------------------------------
1 | .vscode/**
2 | .vscode-test/**
3 | .gitignore
4 | vsc-extension-quickstart.md
5 |
--------------------------------------------------------------------------------
/vscode-extention/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to the "windlang" extension will be documented in this file.
4 |
5 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
6 |
7 | ## [Unreleased]
8 |
9 | - Initial release
--------------------------------------------------------------------------------
/vscode-extention/README.md:
--------------------------------------------------------------------------------
1 | [WindLang](https://github.com/joetifa2003/windlang) syntax highlighting
2 |
--------------------------------------------------------------------------------
/vscode-extention/assets/WindLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joetifa2003/windlang/babe562548e63133938c9da64cfba1760fb9030f/vscode-extention/assets/WindLogo.png
--------------------------------------------------------------------------------
/vscode-extention/language-configuration.json:
--------------------------------------------------------------------------------
1 | {
2 | "comments": {
3 | // symbol used for single line comment. Remove this entry if your language does not support line comments
4 | "lineComment": "//",
5 | // symbols used for start and end a block comment. Remove this entry if your language does not support block comments
6 | "blockComment": [ "/*", "*/" ]
7 | },
8 | // symbols used as brackets
9 | "brackets": [
10 | ["{", "}"],
11 | ["[", "]"],
12 | ["(", ")"]
13 | ],
14 | // symbols that are auto closed when typing
15 | "autoClosingPairs": [
16 | ["{", "}"],
17 | ["[", "]"],
18 | ["(", ")"],
19 | ["\"", "\""],
20 | ["'", "'"]
21 | ],
22 | // symbols that can be used to surround a selection
23 | "surroundingPairs": [
24 | ["{", "}"],
25 | ["[", "]"],
26 | ["(", ")"],
27 | ["\"", "\""],
28 | ["'", "'"]
29 | ]
30 | }
--------------------------------------------------------------------------------
/vscode-extention/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "windlang",
3 | "displayName": "Windlang",
4 | "description": "Syntax highlighting for Windlang",
5 | "version": "0.1.6",
6 | "icon": "assets/WindLogo.png",
7 | "publisher": "YoussefAhmed",
8 | "repository": {
9 | "url": "https://github.com/joetifa2003/windlang"
10 | },
11 | "engines": {
12 | "vscode": "^1.64.0"
13 | },
14 | "categories": [
15 | "Programming Languages"
16 | ],
17 | "contributes": {
18 | "languages": [
19 | {
20 | "id": "windlang",
21 | "icon": {
22 | "light": "./assets/WindLogo.png",
23 | "dark": "./assets/WindLogo.png"
24 | },
25 | "aliases": [
26 | "windlang",
27 | "windlang"
28 | ],
29 | "extensions": [
30 | "wind"
31 | ],
32 | "configuration": "./language-configuration.json"
33 | }
34 | ],
35 | "grammars": [
36 | {
37 | "language": "windlang",
38 | "scopeName": "source.wind",
39 | "path": "./syntaxes/windlang.tmLanguage.json"
40 | }
41 | ]
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/vscode-extention/syntaxes/windlang.tmLanguage.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
3 | "name": "windlang",
4 | "patterns": [
5 | {
6 | "include": "#comments"
7 | },
8 | {
9 | "include": "#keywords"
10 | },
11 | {
12 | "include": "#strings"
13 | },
14 | {
15 | "include": "#numbers"
16 | },
17 | {
18 | "include": "#operators"
19 | },
20 | {
21 | "include": "#identifiers"
22 | },
23 | {
24 | "include": "#semicolons"
25 | }
26 | ],
27 | "repository": {
28 | "keywords": {
29 | "patterns": [
30 | {
31 | "name": "keyword.control.windlang",
32 | "match": "(true|false|if|while|for|return|include|let|fn|as|const|this)"
33 | }
34 | ]
35 | },
36 | "strings": {
37 | "name": "string.quoted.double.windlang",
38 | "begin": "\"",
39 | "end": "\"",
40 | "patterns": [
41 | {
42 | "name": "constant.character.escape.windlang",
43 | "match": "\\\\."
44 | }
45 | ]
46 | },
47 | "numbers": {
48 | "patterns": [
49 | {
50 | "name": "constant.numeric.windlang",
51 | "match": "(0x[0-9a-fA-F]+|[0-9]+(\\.[0-9]+)?)"
52 | }
53 | ]
54 | },
55 | "operators": {
56 | "patterns": [
57 | {
58 | "name": "keyword.operator.windlang",
59 | "match": "\\+|\\-|\\*|\\/|\\%|\\^|\\=|\\!|\\<|\\>|\\&|\\||\\?|\\:|\\~"
60 | }
61 | ]
62 | },
63 | "identifiers": {
64 | "patterns": [
65 | {
66 | "name": "variable.parameter.windlang",
67 | "match": "[a-zA-Z_][a-zA-Z0-9_]*"
68 | }
69 | ]
70 | },
71 | "semicolons": {
72 | "patterns": [
73 | {
74 | "name": "punctuation.separator.semicolon.windlang",
75 | "match": ";"
76 | },
77 | {
78 | "name": "punctuation.separator.comma.windlang",
79 | "match": ","
80 | },
81 | {
82 | "name": "punctuation.separator.period.windlang",
83 | "match": "\\."
84 | },
85 | {
86 | "name": "punctuation.separator.colon.windlang",
87 | "match": ":"
88 | },
89 | {
90 | "name": "punctuation.definition.function.windlang",
91 | "match": "\\("
92 | },
93 | {
94 | "name": "punctuation.definition.function.windlang",
95 | "match": "\\)"
96 | }
97 | ]
98 | },
99 | "comments": {
100 | "patterns": [
101 | {
102 | "name": "comment.line.windlang",
103 | "match": "//.*$"
104 | }
105 | ]
106 | }
107 | },
108 | "scopeName": "source.wind"
109 | }
110 |
--------------------------------------------------------------------------------
/vscode-extention/vsc-extension-quickstart.md:
--------------------------------------------------------------------------------
1 | # Welcome to your VS Code Extension
2 |
3 | ## What's in the folder
4 |
5 | * This folder contains all of the files necessary for your extension.
6 | * `package.json` - this is the manifest file in which you declare your language support and define the location of the grammar file that has been copied into your extension.
7 | * `syntaxes/windlang.tmLanguage.json` - this is the Text mate grammar file that is used for tokenization.
8 | * `language-configuration.json` - this is the language configuration, defining the tokens that are used for comments and brackets.
9 |
10 | ## Get up and running straight away
11 |
12 | * Make sure the language configuration settings in `language-configuration.json` are accurate.
13 | * Press `F5` to open a new window with your extension loaded.
14 | * Create a new file with a file name suffix matching your language.
15 | * Verify that syntax highlighting works and that the language configuration settings are working.
16 |
17 | ## Make changes
18 |
19 | * You can relaunch the extension from the debug toolbar after making changes to the files listed above.
20 | * You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes.
21 |
22 | ## Add more language features
23 |
24 | * To add features such as intellisense, hovers and validators check out the VS Code extenders documentation at https://code.visualstudio.com/docs
25 |
26 | ## Install your extension
27 |
28 | * To start using your extension with Visual Studio Code copy it into the `/.vscode/extensions` folder and restart Code.
29 | * To share your extension with the world, read on https://code.visualstudio.com/docs about publishing an extension.
30 |
--------------------------------------------------------------------------------