├── .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 | [![Go Report Card](https://goreportcard.com/badge/github.com/joetifa2003/windlang)](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 | --------------------------------------------------------------------------------