├── .gitignore ├── LICENSE.txt ├── README.md ├── builtins.ls ├── deprecated ├── README-old.md ├── builtins-old.ls ├── interpreter-old.ls ├── interpreter-older.ls └── parser-old.ls └── interpreter.ls /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vscode 3 | *.pyc 4 | *.js 5 | node_modules 6 | .npmignore -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2016, Devin Hill 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Key concepts of Woden 2 | 3 | Demonstrating key concepts in Woden. 4 | This serves as the primary documentation for the language. 5 | Although it is divided into sections, certain parts have been placed 6 | out of order to help comprehension when reading this document from start 7 | to finish. 8 | 9 | The reader is encouraged to test the examples for themselves, either 10 | using the Woden REPL (the `-p` flag to enable persistence is recommended) 11 | or within a file. I would be honored if you experimented 12 | with the language and contributed bug findings, opinions, and ideas. 13 | 14 | ## How to use Woden 15 | To use Woden, first clone this repo. Then install [node.js](https://nodejs.org/en/) (and npm) 16 | in whichever way is recommended for your system. To setup Woden's dependencies, `cd` into the cloned directory and: 17 | ``` 18 | $ npm install livescript -g 19 | $ npm install yargs 20 | ``` 21 | Then, to run the interpreter you have two options. Either run it directly with livescript: 22 | ``` 23 | $ lsc interpreter 24 | ``` 25 | Or, compile it to javascript and run with node: 26 | ``` 27 | $ lsc -c builtins interpreter 28 | $ node interpreter 29 | ``` 30 | 31 | Eventually I plan to make Woden an npm package, which should make usage 32 | and setup much simpler. 33 | 34 | The `-h` flag will show all the command line options. 35 | Listing no options will begin the REPL, and listing filenames 36 | will run the interpreter on each in order, with a persistent namespace. 37 | This means that while "importing" files is not really possible, listing them 38 | first will make all their definitions available in later files. 39 | 40 | 41 | # Syntax 42 | ``` 43 | (# First things first! 44 | This is a multiline comment #) 45 | 46 | (# This is the multiline commenting 47 | # style which will be used in this document 48 | # when such a comment is needed. #) 49 | 50 | 51 | (## <- Any number of hashes can be used, 52 | but they must match on both ends! 53 | not the end: #) 54 | the end: ##) 55 | 56 | (## You can (# nest things #) this way ##) 57 | 58 | (# Or (## this ##) way #) 59 | 60 | ## This is a single-line comment. 61 | ## It ends at the next newline, like // in C, Java, etc. 62 | ## Pretty simple, really. 63 | ``` 64 | 65 | 66 | 67 | # Literals and values 68 | 69 | ## Numbers 70 | 71 | Number literals in Woden are unsurprising if one 72 | has experience with a C-like language, with some 73 | exceptions. Currently all numbers must be written in base 10, but this may change. 74 | 75 | Working examples for whole numbers include: 76 | ``` 77 | 10 ## Works as expected 78 | 400_000_000 ## Underscores for clarity are fine (just not leading or trailing) 79 | 1_________0 ## 10, but with ridiculous levels of clarity 80 | 01189998819991197253 ## Leading 0's are OK too (no octal here) 81 | ``` 82 | 83 | Working examples for floating point include: 84 | ``` 85 | 3.14 ## Fine 86 | 0.1009 ## Mind the 0 before the point; it's required 87 | 123.0 ## The 0 after the point is similarly necessary 88 | 999_999.999 ## Underscores work here too 89 | 1_123.129_912 ## If you want, they can be after the point as well 90 | 1____.____0 ## How not to input the number 1 (but it works) 91 | ``` 92 | 93 | Negative number literals are not possible as of yet, but 94 | are largely superfluous when functions like `neg` (negate) exist. 95 | Functions will be discussed later, of course. 96 | 97 | All numbers in Woden are in fact floating-point internally, as a result 98 | of the engine running on Javascript which has this behavior. 99 | 100 | Finally, character literals in Woden simply represent the UTF-8 codepoint 101 | of the character in question, which is just a number. 102 | Character literals are made by writing `'` directly in front of the character: 103 | ``` 104 | 'a ## equivalent to 97 105 | 'か ## equivalent to 12363 106 | ``` 107 | 108 | 109 | 110 | ## Arrays 111 | 112 | Array literals vary quite a lot from language to language, 113 | but Woden is not altogether unusual in its syntax here. 114 | 115 | Arrays are delineated with the `[` and `]` characters, and 116 | the elements they contain are simply listed, separated by space. 117 | Keep in mind that the elements are NOT separated with commas! 118 | Arrays in Woden can be heterogenous. 119 | 120 | Valid array literals: 121 | ``` 122 | [1 2 3] ## Just space between, please. 123 | [ 2 ] ## Space before or after the braces is irrelevant. 124 | [1 2 [3 4 5]] ## Heterogenous array example. 125 | ``` 126 | 127 | Strings in Woden are really just arrays of code points, so the 128 | following are also technically array literals: 129 | ``` 130 | "Just an array" ## So meta 131 | "Real strings are for chumps" ## Debatable 132 | ``` 133 | 134 | More about arrays later, when it'll be more relevant. 135 | 136 | 137 | 138 | # Performing operations on values 139 | 140 | Woden has one very pervasive design choice: it is stack-based. 141 | This means that values are pushed onto a _stack_, 142 | or "First-in, Last-out" (FILO) data structure when they are encountered. 143 | Functions/operators are written AFTER the values they 144 | modify, remove values from the stack as their parameters, 145 | and then their results are pushed onto the stack as well. 146 | This allows for much work to be done without 147 | explicitly naming variables, and in fact there is no concept 148 | of a _variable_ per se in Woden, as it is a purely functional language. 149 | 150 | Postfix (a.k.a. RPN, [Reverse Polish Notation](https://en.wikipedia.org/wiki/Reverse_Polish_notation)) function and operator application sounds strange, 151 | but it isn't that hard to get used to. It also comes with some 152 | advantages, such as not requiring parentheses to reorder operations. 153 | 154 | Consider the following examples for a better idea of how it works. 155 | The traditional infix is written first, followed by the equivalent in postfix: 156 | ``` 157 | 1 + 2 -> 1 2 + 158 | 1 - 2 -> 1 2 - ## Same deal 159 | 1 + 2 + 3 -> 1 2 + 3 + ## Slightly more nefarious 160 | 1 / 2 + 3 -> 1 2 / 3 + ## Preserves order of operations 161 | 1 / (2 + 3) -> 1 2 3 + / ## Still does, but no parentheses required! 162 | 163 | ## Longer examples 164 | 1 + 2 / 3 - 4 * 5 -> 1 2 3 / + 4 5 * - 165 | (1 + 2) / (3 - 4) * 5 -> 1 2 + 3 4 - / 5 * 166 | 1 + 2 / (3 - 4 * 5) -> 1 2 3 4 5 * - / + 167 | ``` 168 | If you come from a more lispy background, visualizing 169 | ``` 170 | 1 2 3 + / 171 | ``` 172 | as 173 | ``` 174 | (1 (2 3 +) /) ## This is valid Woden too, in fact 175 | ``` 176 | may provide some insight. 177 | 178 | Visualizing both the operators being applied _in the order they are written_ 179 | and how each value and operator affects the stack is key. 180 | Also important to note is that the "operator" functions are not special in 181 | any way. Their names just happen to be made up of symbols. A summary of valid 182 | identifiers will be explained later on. 183 | 184 | >With that in place though, Woden just seems like a calculator. 185 | >Besides, wouldn't it be more useful if `+` just summed up 186 | >all the numbers I wrote before it? I demand justification! 187 | 188 | Woden has one more important concept up its sleeve, stemming 189 | largely from the use of stack-based mechanics, but also from 190 | a functional design... 191 | 192 | 193 | 194 | # Arity 195 | 196 | The _arity_ of a function or operator is defined as the number 197 | of arguments or parameters it takes in. A function with 2 parameters 198 | has, of course, an arity of 2. 199 | 200 | The mathematical operators `+`, `-`, `*`, `/` all have an arity of 2 as well, 201 | since each requires two numbers to produce their results. 202 | 203 | Every function in Woden has a set arity, that is, there is no ability of a function 204 | to take a variable number of arguments as input. This enables better reasoning 205 | about the way functions are applied, especially in a stack-based setting. 206 | Arity in itself is a useful concept for stack-based functions, but in Woden 207 | it also has other consequences, involving the use of functions as values, 208 | and higher-order functions. 209 | 210 | If a function is referred to when there are not enough values on the stack 211 | to perform its computation, it will be placed on the stack itself, acting as 212 | a reference to the function and able to be used as an input to other functions. 213 | 214 | For example, if one simply types a lone `+` 215 | into the Woden prompt, it will result in a reference to the function being 216 | pushed onto the stack, since there are fewer than 2 values (zero, to be precise). 217 | 218 | On its own, arity might not seem very valuable, but it plays a large part 219 | in function overloads, partial application, function composition, etc. 220 | 221 | 222 | 223 | # Literals: Quite literally (a continuation) 224 | 225 | ## Functions 226 | 227 | Function literals are kind of like lambdas in other languages. 228 | In other words, they're anonymous functions. 229 | 230 | They are written as a series of values between `{` and `}`, 231 | and like arrays do not use commas as a delimiter. 232 | Their contents are not evaluated (nor even inspected) until/unless 233 | they are applied, whereupon their values are pushed to the stack 234 | in the order they are written. 235 | 236 | Valid function literals: 237 | ``` 238 | {1 2 3} ## Like a lazily-evaluated set of numbers 239 | {1 2 +} ## Becomes a 3 only after being applied 240 | {[1 2 3]} ## Arrays within functions are perfectly acceptable 241 | ``` 242 | 243 | (However, the real use of these literals is partial application, 244 | meaning giving a function only some of its arguments now, and the rest later, 245 | or encapsulating a procedure for use with higher-order functions) 246 | 247 | As an example, a function like this: 248 | ``` 249 | {2 +} 250 | ``` 251 | can represent "adding 2 to something not yet known", because 252 | the `+` function is missing one argument, which it will get from the stack later. The 253 | equivalent in Haskell for example would be `(+ 2)` 254 | (since the 2 in the Woden literal is the eventual second argument to `+`). 255 | It can then be applied to each element of a list for example, resulting in 256 | each value within (assuming the types are compatible with the `+` function) 257 | being incremented by 2. 258 | 259 | Since it is in general impossible to determine a fixed arity for such literals, 260 | they are all assumed to have an arity of 0, and any issues regarding their "true" 261 | arity are the responsibility of the programmer to handle, unfortunately. 262 | However, this has proven to be a non-issue in the majority of cases. 263 | 264 | One issue is that without function literals, functions themselves can only be put on the stack 265 | when the stack's length is less than the function's arity. That is a strange 266 | limitation to have, and so the same effect can be achieved (with total equivalence) 267 | by making a function literal containing one thing. 268 | 269 | For example, the following: 270 | ``` 271 | {+} 272 | ``` 273 | is exactly equivalent to an undecorated `+` when the stack is empty, but will also guarantee 274 | that the `+` function is not actually applied to whatever may be on the stack otherwise. 275 | 276 | This single-element function literal pattern is quite common because of this use case, 277 | and so Woden includes a second syntax for specifying a one-element anonymous function. 278 | Simply put a backtick: ` before a value/identifier of any kind, and it will be encapsulated in a function. 279 | 280 | Examples: 281 | ``` 282 | `+ ## Equivalent to {+} 283 | `[1 2 3] ## Equivalent to {[1 2 3]} 284 | ``- ## Equivalent to {{-}} 285 | `39.95 ## Equivalent to {39.95} 286 | `[`1 `2 `[3 4]] ## Nesting is OK, equivalent to {[{1} {2} {[3 4]}]} 287 | ``` 288 | 289 | 290 | 291 | # Names and identifiers 292 | 293 | >Literals are convenient, but allow for little 294 | >reuse of values or code. How can I name things to recall their values later? 295 | 296 | The syntax for doing this is discussed in the next section, "Named functions" (because there is 297 | no distinction between a function with 0 arguments and a constant value), but 298 | it is important to know the sort of name which is accepted in the first place. 299 | 300 | Names in Woden can either be made up entirely of symbol-like characters (e.g. `+/-$&!`), 301 | or entirely of letter-like characters and numbers (e.g. `aZ_39d`) followed by any number 302 | of single quotes (often read as `prime` in this context). Either type of name is 303 | acceptable, but the two types of symbols cannot be used in one identifier. Using a keyword 304 | as a name is illegal (and impossible, the parser won't understand it). The `#` character is 305 | also forbidden in identifiers, since it is used for commenting and type signatures. 306 | The particulars of valid identifiers are still subject to change. 307 | 308 | 309 | Sample valid names: 310 | ``` 311 | ## "word-like" identifiers: 312 | snake_case 313 | camelCase 314 | PascalCase 315 | trailingNUMBERS1999 316 | internal_9701_numbers 317 | ready 318 | primedAndReady'' 319 | 320 | ## "operator-like" identifiers: 321 | + 322 | != 323 | >>= 324 | <- 325 | <$> 326 | ??? 327 | ``` 328 | Next we will discuss using these names to reference values (and procedures). 329 | 330 | 331 | 332 | # Named functions 333 | 334 | Woden is (in many ways) purely functional, meaning one cannot change the value of a variable 335 | at runtime; all are effectively constants. 336 | Nevertheless it is possible to save a value (or sequence of values to function as a procedure) 337 | under a name to reference it later. 338 | 339 | This is done using 3 keywords present in Woden: 340 | ``` 341 | define 342 | -> 343 | end 344 | ``` 345 | ..in that order. 346 | 347 | The basic syntax for declaring a value looks something like this: 348 | ``` 349 | define -> end 350 | ``` 351 | 352 | The whitespace is not important, so the following means the same thing but usually looks nicer: 353 | ``` 354 | define -> 355 | ## The values can of course include other identifiers. 356 | end 357 | ``` 358 | 359 | The amount of values can actually be 0, in which case any reference to the name supplied has no effect. 360 | Thus, the simplest possible declaration is something of the form: 361 | ``` 362 | define -> end ## Elegant in a way, but ineffective. 363 | ``` 364 | ...but it isn't very useful for doing anything. 365 | 366 | 367 | If only one value is used, then the name serves effectively as a global constant. 368 | This can be useful in cases such as: 369 | ``` 370 | define PI -> 3.1415926 end ## No need to type out the value elsewhere, just use PI by name. 371 | ``` 372 | 373 | With two or more values, each value will be applied to the stack in order, 374 | resulting in essentially the same thing as a function literal, but named. 375 | Of notable difference between literals and named functions is when/how they are applied: 376 | Literals can only be applied using the built-in `apply` function, whereas 377 | named functions are automatically expanded whenever they are named (with some exceptions, 378 | which will be relevant only once more advanced features of named functions are discussed). 379 | 380 | In this way, one could make a function like the below: 381 | ``` 382 | define addTwo -> 2 + end 383 | ``` 384 | 385 | This function is equivalent to the literal `{2 +}` except it is always applied and can be referred 386 | to by name. To achieve true equivalence, simply do the same as was done for `+` earlier, and 387 | wrap the name in a literal so it isn't immediately applied: 388 | ``` 389 | {addTwo} ## Or, equally: `addTwo 390 | ``` 391 | 392 | Functions can be recursive simply by referring to themselves in their own definition. As of now, 393 | there is no optimization for recursive tail calls, but this is an eventual goal. 394 | A simple (and not recommended) recursive function could look like the below: 395 | ``` 396 | define addInfinity -> 397 | 1 + ## add one 398 | addInfinity ## Then call itself, repeating the addition forever (in theory) 399 | end 400 | ``` 401 | 402 | 403 | 404 | ## Function arguments 405 | 406 | A procedure can be more powerful if it has more control over values from the stack to use in computation. 407 | Thus, Woden allows functions to take arguments and reuse them in their definitions. 408 | The syntax for arguments is easy, just list more identifiers following the function name 409 | but preceeding the arrow, like so: 410 | ``` 411 | define function argument_1 argument_2 ... argument_n -> 412 | (# Do stuff with the inputs #) 413 | end 414 | ``` 415 | 416 | When `function` is called, `argument_1` will receive the value of the top of the stack, 417 | `argument_2` the next item, and so on. The value which is used as an argument does not remain on the stack when it 418 | is bound to the argument name. Just as when using the pre-defined functions (like `+`), if 419 | there are not enough arguments on the stack, a reference to the function will be pushed instead. 420 | 421 | After arguments have been declared in this way, they can be used by name in the function body. 422 | Arguments can have any valid identifier as a name (even symbol-y ones) and shadow the value with 423 | the same name at the global scope, if there is one. It should be noted that Woden currently has 424 | dynamic scoping (as opposed to lexical scoping), but since there are no nested functions or mutable 425 | variables this is not extremely widespread in its effects. A change in scoping rules may occur later in development, if 426 | it appears to be worthwhile. 427 | 428 | A function with arguments is also not prevented from accessing the stack for more data if necessary. 429 | The main motivation for named arguments in Woden was not encapsulation or purity, but a lessening 430 | of need to use functions dedicated solely to manipulating the position of items on the stack, and instead 431 | linking stack items to names so they can be reordered and reused in whichever way the programmer chooses. 432 | If preventing access to the overall stack is desired, surrounding the function body in parentheses achieves this. 433 | 434 | However, using arguments, it is trivial in Woden to define the most common stack-changing 435 | functions such as `dup`, `swap`, `drop`, `rot` etc. 436 | and as such they are omitted from the builtin library, 437 | which attempts to contain only the truly necessary primitives. 438 | Their definitions follow: 439 | ``` 440 | ## Duplicates an item 441 | define dup a -> 442 | a a ## Self-explanatory, I think 443 | end 444 | 445 | ## Swaps the top two items on the stack 446 | define swap a b -> 447 | a b ## Doesn't look swapped at a glance, but it is, since b is pushed second, making it the top. 448 | end 449 | 450 | ## Deletes the top item of the stack 451 | define drop a -> 452 | ## Doing nothing here simply consumes `a` 453 | end 454 | 455 | ## Brings the 3rd element to the top of the stack 456 | define rot a b c -> 457 | b a c 458 | end 459 | ``` 460 | 461 | Also of note regarding argument is that any function literal containing a reference to an argument 462 | forms a closure. This means that functions like... 463 | ``` 464 | define makeAdder x -> {x +} end ## Returns a function which adds x 465 | ``` 466 | ...work as expected, and the return function's behavior will vary depending on the input to the parent 467 | function. 468 | 469 | 470 | 471 | # Overloading 472 | 473 | Multiple versions of a function can be defined, which will be referred to as "overloads" from now on. 474 | These overloads MUST differ from each other in at least one of three ways, or else the 475 | interpreter will have no way to choose between them. 476 | 477 | ## Function overloads on arity 478 | 479 | The most basic way of overloading a function is 480 | making a version with a different arity (number of arguments). When calling the function, the 481 | overload with the most arguments which can be validly called is used. Here is an example to clarify: 482 | ``` 483 | ## Takes only one argument and adds 10 to it. 484 | define overloaded a -> 485 | a 10 + 486 | end 487 | 488 | ## Takes two arguments, and multiplies them. 489 | define overloaded a b -> 490 | a b * 491 | end 492 | 493 | ## Now, we can use the function `overloaded` in two ways: 494 | 495 | ## 1. 496 | (29 overloaded) ## Results in 39 by calling the version which only needs one argument. 497 | ## 2. 498 | (18 32 overloaded) (# Results in 576 by calling the version taking two arguments. 499 | # Although the one-argument version is also valid here, the overload 500 | # with the highest arity is always chosen if it applies. #) 501 | 502 | overloaded (# Results in 22464 by calling the two-argument overload on the past two results! 503 | # Implicit argument-passing is one benefit of a stack-based system. #) 504 | ``` 505 | 506 | Of course, when one function takes on multiple arities, it becomes important sometimes 507 | to control exactly how many values are being passed to it. Surrounding a function with parentheses as shown 508 | above evaluates it within a separate environment which does not contain the previous contents of the stack. 509 | This allows access to the lower-arity overloads whenever it is needed. 510 | 511 | This syntax with parentheses also allows evaluation of expressions inside arrays. 512 | Typically, writing `[1 2 +]` is equivalent to `[1 2 {+}]`, because each element of the array is 513 | evaluated in a separate and empty environment. However, writing `[(1 2 +)]` results in `[3]` because 514 | the parenthetical expression is evaluated first and pushes the resulting values to the array. Similarly, 515 | `[(1 2 + 3 4 + 5 6 +)]` is equivalent to `[3 7 11]`. 516 | 517 | 518 | 519 | ## Function overloads on types 520 | 521 | Overloading functions based on types is a key feature of Woden (and many other languages). 522 | It enables a basic level of type safety and polymorphism. 523 | In order to do this, parameters must have their types explicitly specified. 524 | Types are specified by the following symbol combinations: 525 | 526 | * `#` for Numeric types (all numbers) 527 | * `()` for Function types (literals & references caused by arity) 528 | * `[]` for Array types (all arrays) 529 | 530 | They are written following the parameter's name in the function definition, e.g. 531 | ``` 532 | ... number_parameter# array_parameter[] ... 533 | ^ here ^^ and here 534 | ``` 535 | Depending on the type of the parameter at runtime, 536 | a different version of the function will be called, 537 | or, if no version of the function is appropriate, the 538 | call will be rejected. If more than one overload can be 539 | used, the call will also be rejected, since it is ambiguous. 540 | 541 | However, if multiple overloads are possible but one has more 542 | matching type checks than the others, it will take priority. 543 | This is important to remember when writing your own functions 544 | or reading this documentation as parts may be confusing if this 545 | is forgotten. Two overloads with the same number of matching 546 | types will still be rejected as ambiguous. 547 | 548 | A basic type-finding function can be implemented like so: 549 | ``` 550 | ## Matches values of different types and returns an appropriate descriptive string 551 | define type a# -> "Number" end 552 | define type a() -> "Function" end 553 | define type a[] -> "Array" end 554 | 555 | (# Note above that the parameter a is never returned to the stack. 556 | # Because of this, it is consumed when `type` is called. 557 | # More on this will be discussed later. #) 558 | ``` 559 | 560 | 561 | ## Function overloads on values 562 | 563 | More rarely found in a programming language is the ability 564 | to overload a function based on values, rather than types. 565 | In Woden, this is done by appending a function literal to the 566 | end of the parameter, following the type if it is present, like so: 567 | ``` 568 | ... parameter_name # { .... } ... 569 | ^ here ^ 570 | ``` 571 | If the function applied to the parameter's runtime argument 572 | in an empty stack results in the top of the stack being truthy, 573 | it is considered a match. (Because of this, an empty check evaluates 574 | the truthiness of the value itself. In Woden, everything other than 0 575 | is considered true. There is no separate boolean type.) 576 | 577 | The following function implements the (recursive) fibonacci function 578 | using this ability. 579 | ``` 580 | (# The first two numbers are 0 and 1, so for a < 2, fib(a) == a! 581 | # This is of course flawed if a is negative, but the fibonacci numbers 582 | # are not traditionally defined in that range either, so no big deal. #) 583 | 584 | define fibonacci a#{2 <} -> 585 | a 586 | end 587 | 588 | 589 | (# Recursion is as simple as it gets; no special case here! 590 | # The opposite value check of {1 >} need not be supplied, 591 | # since the overload with the most matching value checks is prioritized. #) 592 | 593 | define fibonacci a# -> 594 | (a 2 - fibonacci) (a 1 - fibonacci) + 595 | end 596 | ``` 597 | 598 | ## Unnamed parameters 599 | 600 | Function parameters do not need to be named, in fact. 601 | If they are unnamed, the value they refer to is left intact on the stack instead of being 602 | consumed, but is still type- and value-checked. 603 | 604 | A function parameter can be unnamed _only_ if one specifies a type and/or 605 | value check for it, since otherwise its existence would not be evident. 606 | 607 | If the parameter does not need a name, 608 | type checking, or value checking, it can be simply left implicit 609 | as a result of the stack-based nature of computation in Woden (Modifying more stack values 610 | than your function has arguments should be frowned upon however, as it derails the usefulness of arity/purity). 611 | 612 | The exception to this is when one wants to check the type of an unnamed deeper 613 | value on the stack, but wants an unnamed unchecked first parameter as well. This is 614 | not an extremely common scenaraio, but since there is no syntax for an "unspecified" type as of yet, 615 | it is necessary to either 616 | * name the first parameter anyway, or 617 | * use a value check of `{1}` to accept all values 618 | 619 | in this case. 620 | Perhaps syntax will be introduced to resolve this issue. 621 | 622 | With unnamed parameters the following definition: 623 | ``` 624 | define add x# y# -> x y + end 625 | ``` 626 | can be transformed into this one, using unnamed parameters: 627 | ``` 628 | define add # # -> + end 629 | ``` 630 | This has the advantage of preserving type safety and arity information 631 | while not forcing the writer to repeat themselves. 632 | 633 | Also useful is the ability to use unnamed parameters to deliberately not consume 634 | values from the stack when doing so isn't necessary. The `type` function written previously could 635 | be made to do this: 636 | ``` 637 | ## Because of unnamed parameters, the types are checked but the values stay on the stack. 638 | define type # -> "Number" end 639 | define type () -> "Function" end 640 | define type [] -> "Array" end 641 | ``` 642 | 643 | 644 | 645 | # Example functions demonstrating use cases 646 | 647 | ## If 648 | With overloads of values and types, alongside function literals which act as closures, 649 | even the `if` control construct can be written as a function, rather than a keyword. 650 | Implementation and usage example: 651 | ``` 652 | (# If the condition is true (an empty value check effectively 653 | # evaluates the truthiness of the value itself), 654 | # apply the function representing the true path's contents. #) 655 | 656 | define if truepath() condition{} -> 657 | truepath apply 658 | end 659 | 660 | ## Otherwise, do nothing at all. 661 | define if truepath() condition -> end 662 | 663 | ## Using the function: 664 | 10 (2 3 >) { 3 + } if ## -> 10 665 | 10 (7 7 =) { 2 * } if ## -> 20 666 | ``` 667 | 668 | ## If/Else 669 | 670 | Along the same lines, here is 671 | `if` and `else` as a function, rather than a keyword: 672 | ``` 673 | define if_else f() t() cond{} -> 674 | t apply 675 | end 676 | 677 | define if_else f() t() cond -> 678 | f apply 679 | end 680 | 681 | ## Usage: 682 | 10 (2 3 >) { 3 + } { 3 - } if_else ## -> 7 683 | 10 (7 7 =) { 2 * } { 7 * } if_else ## -> 20 684 | ``` 685 | 686 | ## Max 687 | ``` 688 | ## A function to return the maximum of two numbers 689 | define max a# b# -> 690 | (a b >) { a } { b } if_else 691 | end 692 | 693 | ## An overload to return the maximum element of a list 694 | define max [] -> 695 | `max fold 696 | end 697 | 698 | 99 182 max ## -> 182 699 | [1 292 10 932 8 99] max ## -> 932 700 | max ## -> 932 701 | ``` 702 | 703 | ## Unlambda interpreter/DSL 704 | It is easy to implement the basic semantics of [Unlambda](http://www.madore.org/~david/programs/unlambda/) within Woden, as it turns out. 705 | Here is a simple way of doing so: 706 | ``` 707 | ## Convenience declarations (especially ! for apply) 708 | define ! -> apply end 709 | define . -> putc end 710 | 711 | ## "r" shortcut to print newline 712 | define r -> { 10 . } end 713 | 714 | ## ".*" shortcut to print asterisk (just for convenience, not in the spec) 715 | define .* -> { '* . } end 716 | 717 | ## Curried "k" combinator 718 | define k -> { k_1 } end 719 | define k_1 a -> { a k_2 } end 720 | define k_2 a b -> 721 | a 722 | end 723 | 724 | ## Curried "s" combinator 725 | define s -> { s_1 } end 726 | define s_1 a -> { a s_2 } end 727 | define s_2 a b -> { b a s_3 } end 728 | define s_3 a b c -> 729 | c b ! c a ! ! 730 | end 731 | 732 | ## Curried "v" function 733 | define v -> `v_1 end 734 | define v_1 a -> `v_1 end 735 | 736 | ## "i" function 737 | define i -> { } end ## the identity function is achieved by doing nothing to its arguments 738 | 739 | ``` 740 | The `d` construct specified in Unlambda would be harder to implement however, and has been left out of this sample. 741 | However, even with just these functions, we can execute the first example program on the Unlambda site successfully: 742 | 743 | ``` 744 | ## Fibonacci sequence printer (per unlambda website, albeit in reverse order here due to postfix notation) 745 | ## This is _extremely_ slow, but does work as expected. What is to blame for the speed (the interpreter or the calculation method) is unclear... 746 | k s k ! s ! ! k ! 747 | k k i s ! k ! s ! ! r k ! s ! k ! s ! ! s k ! s ! ! s ! ! s k ! s ! k ! s ! ! 748 | s k ! s ! ! s ! ! .* k ! 749 | i k ! i i s ! ! s ! ! s ! ! ! 750 | ``` 751 | 752 | More examples will be added here over time. 753 | 754 | # Final thoughts 755 | 756 | Woden is meant as an experimental first try at writing an interpreter for a fairly barebones language, 757 | and almost certainly contains several design flaws or bad practices resulting from my own naiveté. 758 | Improvements to the speed of interpretation above all are likely possible, but to what degree I do not know. 759 | Should you be interested enough to examine the code itself, or even offer suggestions regarding how 760 | to improve this project (including desirable features not yet present), I would be grateful. 761 | -------------------------------------------------------------------------------- /builtins.ls: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # 5 | # Utility functions, used only internally or by the interpreter 6 | # 7 | 8 | 9 | 10 | # main function to call to push anything to a stack 11 | export push = (item, arr) --> 12 | return arr if item is null 13 | if type(item) is \Function and arr.length >= (item.arity ? 0) 14 | if item.manualpush 15 | item arr 16 | else 17 | arr.push item ...[arr.pop! for til item.arity] 18 | else 19 | arr.push item 20 | arr 21 | 22 | cycle = (amount, arr) --> 23 | real_amt = amount % arr.length 24 | if real_amt is 0 25 | arr 26 | else if real_amt < 0 27 | cycle real_amt, arr.reverse! .reverse! 28 | else 29 | arr[real_amt to] ++ arr[til real_amt] 30 | 31 | rotate = (depth, amount, arr) --> 32 | if depth is 0 33 | arr 34 | else if depth < 0 35 | rotate -depth, -amount, arr.reverse! .reverse! 36 | else if depth >= arr.length 37 | cycle amount, arr 38 | else 39 | arr[til arr.length - depth] ++ cycle amount, arr[arr.length - depth to] 40 | 41 | # decorator-ish thing to set manual stack control 42 | manualpush = (f) -> 43 | f.manualpush = true 44 | f 45 | 46 | # decorator-ish thing to set arity of a function 47 | arity = (ar, f) -> 48 | f.arity = ar 49 | f 50 | 51 | # function for generating stack push sequences 52 | export fseq = (sequence) -> 53 | f = manualpush arity 0 (stack) !-> 54 | for item in sequence 55 | if !item.is-fseq # do not apply internal fseqs when pushing. 56 | push item, stack # this is to allow for proper nesting. 57 | else # otherwise, {x} is functionally identical to {{x}} 58 | stack.push item 59 | f.is-fseq = true 60 | f.seq = sequence 61 | f 62 | 63 | # function for getting some interpreter functionality on the stack. 64 | # it needs a reference to the evaluation function and the values to push 65 | export mega-fseq = (evaluate, values, AST, ENV) -> 66 | manualpush arity 0 (stack) !-> 67 | for node in values 68 | evaluate(node, stack, AST, ENV) # that oughta do it better 69 | 70 | flatten = (arr) -> 71 | res = [] 72 | for item in arr 73 | if type(item) is \Array 74 | res .= concat item 75 | else 76 | res.push item 77 | res 78 | 79 | deep-flatten = (arr) -> 80 | res = [] 81 | for item in arr 82 | if type(item) is \Array 83 | res .= concat deep-flatten item 84 | else 85 | res.push item 86 | res 87 | 88 | export truthy = -> 89 | switch it 90 | case 0 => false 91 | case null, false => false 92 | else => true 93 | 94 | 95 | # RUNES HAVE BEEN ABOLISHED IN THE NEW INTERPRETER 96 | # THEIR FUNCTIONALITY WILL REMAIN IN THE FORM OF OTHER BUILT-INS OR REGULAR FUNCTIONS 97 | 98 | 99 | 100 | # 101 | # Functions which can be pushed to the stack 102 | # 103 | 104 | 105 | # Any 106 | export type = arity 1 (a) -> 107 | a?.constructor.name ? \Null 108 | 109 | # Number, Number 110 | add = arity 2 (a, b) -> a + b 111 | 112 | # Function, Array 113 | scanr = arity 2 (a, b) -> 114 | b[til b.length - 1].reverse!reduce ((previous, nextitem) -> 115 | previous.concat push a, [previous[*-1], nextitem]), [b[*-1]] 116 | 117 | # Function, Array 118 | scanl = arity 2 (a, b) -> 119 | b[1 to].reduce ((previous, nextitem) -> 120 | previous.concat push a, [previous[*-1], nextitem]), [b[0]] 121 | 122 | # Number, Number 123 | sub = arity 2 (a, b) -> b - a 124 | 125 | # Function, Array 126 | filter = arity 2 (a, b) -> 127 | b.filter -> 128 | truthy(push a, [it] .0) # only first element of result is checked 129 | 130 | # Number, Number 131 | mul = arity 2 (a, b) -> b * a 132 | 133 | # Function, Array 134 | fmap = arity 2 (a, b) -> 135 | flatten b.map -> 136 | push a, [it] 137 | 138 | # Number, Number 139 | div = arity 2 (a, b) -> b / a 140 | 141 | # Function, Array 142 | foldr = arity 2 (a, b) -> 143 | b[til b.length - 1].reverse!reduce((prev, val) -> 144 | push a, push val, prev 145 | , [b[*-1]])[*-1] # TODO: something about this, maybe 146 | 147 | # Function, Array 148 | foldl = arity 2 (a, b) -> 149 | b[1 to].reduce((prev, val) -> 150 | push a, push val, prev 151 | , [b[0]])[*-1] # TODO: something about this, maybe 152 | 153 | # Number, Number 154 | mod = arity 2 (a, b) -> b % a 155 | 156 | # Number, Number 157 | exp = arity 2 (a, b) -> b ^ a 158 | 159 | # Number, Number 160 | # Array, Array ? 161 | gt = arity 2 (a, b) -> 162 | if b > a then 1 else 0 163 | 164 | # Number, Number 165 | # Array, Array ? 166 | lt = arity 2 (a, b) -> 167 | if b < a then 1 else 0 168 | 169 | # Any, Any 170 | eq = arity 2 (a, b) -> 171 | return 1 if a is b 172 | return 0 if type(a) != type(b) 173 | if type(a) is \Array 174 | return 0 if a.length != b.length 175 | for i til a.length 176 | return 0 unless eq(a[i], b[i]) 177 | return 1 178 | else if type(a) is \Function 179 | return 0 unless a.is-fseq and b.is-fseq 180 | return eq(a.seq, b.seq) 181 | return 0 182 | 183 | # Any 184 | export apply = manualpush arity 1 (stack) !-> 185 | # re-push an item using the push function. 186 | # this means an fseq will be guaranteed to be applied. 187 | push stack.pop!, stack 188 | 189 | # Number, Number 190 | incl-range = arity 2 (a, b) -> 191 | if a > b 192 | [b to a] 193 | else 194 | [b to a by -1] 195 | 196 | # Number 197 | unary-range = arity 1 (a) -> 198 | if a >= 0 199 | [0 til a] 200 | else 201 | [0 til a by -1] 202 | 203 | # 204 | pack = manualpush arity 0 (stack) !-> 205 | # packs up the current stack and pushes it as an array 206 | s = [stack.shift! for til stack.length] 207 | stack.push s 208 | 209 | # Array 210 | unpack = manualpush arity 1 (stack) !-> 211 | a = stack.pop! # an array whose contents you want on the stack 212 | for item in a 213 | stack.push item # note that nothing is applied 214 | 215 | # Number 216 | abs = arity 1 (a) -> Math.abs a 217 | 218 | # Array 219 | length = arity 1 (a) -> a.length 220 | 221 | # Function 222 | get-arity = arity 1 (a) -> a.arity 223 | 224 | # Any, Any 225 | concat = arity 2 (a, b) -> 226 | if type(a) isnt \Array and type(b) isnt \Array 227 | [b, a] 228 | else if type(a) is \Array and type(b) isnt \Array 229 | [b, ...a] 230 | else if type(a) isnt \Array and type(b) is \Array 231 | [...b, a] 232 | else if type(a) == type(b) == \Array 233 | b ++ a 234 | 235 | # Number, Any 236 | repeat = manualpush arity 2 (stack) !-> 237 | a = stack.pop! # number of times to repeat 238 | b = stack.pop! # the thing to repeat 239 | for til a 240 | push b, stack # note that `b` is applied each time 241 | 242 | # Number, Array 243 | take = arity 2 (a, b) -> 244 | # returns the first `a` elements of `b`, or b[0..a] if a > b.length 245 | return [] if a == 0 246 | return b if Math.abs(a) >= b.length 247 | return drop(b.length + a, b) if a < 0 248 | b[til Math.floor(a)] 249 | 250 | # Number, Array 251 | drop = arity 2 (a, b) -> 252 | # removes the first `a` elements of `b`, returning b or [] if a > b.length 253 | return b if a == 0 254 | return [] if Math.abs(a) >= b.length 255 | return take(b.length + a, b) if a < 0 256 | b[Math.floor(a) to] 257 | 258 | # Number, Array 259 | elem = arity 2 (a, b) -> 260 | # takes the `a`-th element of `b` 261 | if type(b) is \Array 262 | b[a] 263 | 264 | # Number 265 | neg = arity 1 (a) -> -a 266 | 267 | # Array 268 | reverse = arity 1 (a) -> a.reverse! 269 | 270 | # Number 271 | putchar = manualpush arity 1 (stack) !-> 272 | # converts top of the stack to a character using UTF-8, and prints it 273 | process.stdout.write(String.from-char-code(stack.pop!)) 274 | 275 | 276 | 277 | # 278 | # Functions to help interfacing with the interpreter environment 279 | # 280 | 281 | 282 | 283 | # decorator-ish thing to set input types of a core function. 284 | # this only has an effect when generating the builtin core 285 | # if a certain type shouldn't be checked, use `null` in the list. 286 | # this function allows builtin functions to be interpreted with checked types. 287 | # overloads are not strictly possible with `takes`, but trivial to make in the language itself. 288 | takes = (typelist, f) --> 289 | f.type-req = typelist 290 | f 291 | 292 | 293 | # function to create the nesting structure needed to import 294 | # builtin functions into the interpreter. 295 | # ENV can be supplied to supplement an existing environment 296 | make-environment = (env-dict, ENV={}) -> 297 | for key of env-dict 298 | builtin-f = env-dict[key] # the builtin function 299 | arity = builtin-f.arity ? 0 300 | types = builtin-f.type-req ? [null for til arity] 301 | param-counter = 0 302 | params = for let type in types 303 | { 304 | anonymous: true 305 | type: type # set to null for no checking 306 | name: param-counter++ # all parameters are anonymous of course 307 | } 308 | func = { 309 | type: \function 310 | name: key 311 | params: params 312 | typechecks: types.filter((x) -> x != null).length 313 | valuechecks: 0 314 | arity: builtin-f.arity 315 | value: [{ 316 | type: \native-function 317 | value: builtin-f 318 | }] 319 | } 320 | if key of ENV 321 | ENV[key].overloads.push func 322 | else 323 | ENV[key] = { 324 | type: \function 325 | name: key 326 | overloads: [func] 327 | } 328 | return ENV 329 | 330 | export get-environment = (extended=false, ENV={}) -> 331 | make-environment(core-basic, ENV) 332 | if extended 333 | make-environment(core-extra, ENV) 334 | return ENV 335 | 336 | # the most basic functionality, not able to be implemented within Woden 337 | core-basic = { 338 | "+": takes ["Number", "Number"], add 339 | "-": takes ["Number", "Number"], sub 340 | "*": takes ["Number", "Number"], mul 341 | "/": takes ["Number", "Number"], div 342 | "%": takes ["Number", "Number"], mod 343 | "^": takes ["Number", "Number"], exp 344 | ">": takes ["Number", "Number"], gt 345 | "=": eq 346 | "apply": apply 347 | "putc": takes ["Number"], putchar 348 | "join": concat 349 | "elem": takes ["Number", "Array"], elem 350 | } 351 | 352 | # an extension dictionary of more functionality for convenience. 353 | # these functions can mostly be written within Woden, but these versions 354 | # are potentially faster (and easier to start using, of course) 355 | core-extra = { 356 | "<": takes ["Number", "Number"], lt 357 | "range": takes ["Number", "Number"], incl-range 358 | "iota": takes ["Number"], unary-range 359 | "scanr": takes ["Function", "Array"], scanr 360 | "scan": takes ["Function", "Array"], scanl 361 | "filter": takes ["Function", "Array"], filter 362 | "fmap": takes ["Function", "Array"], fmap 363 | "neg": takes ["Number"], neg 364 | "abs": takes ["Number"], abs 365 | "foldr": takes ["Function", "Array"], foldr 366 | "fold": takes ["Function", "Array"], foldl 367 | "repeat": takes ["Number", null], repeat 368 | "pack": pack 369 | "unpack": takes ["Array"], unpack 370 | "drop": takes ["Number", "Array"], drop 371 | "take": takes ["Number", "Array"], take 372 | "length": takes ["Array"], length 373 | "reverse": takes ["Array"], reverse 374 | } -------------------------------------------------------------------------------- /deprecated/README-old.md: -------------------------------------------------------------------------------- 1 | # Woden 2 | A simple, stack-based, interpreted language implemented in [livescript](http://livescript.net/) (previously python). 3 | It is not purposefully esoteric, but functions/operators have one-character identifiers (assigned within reason). 4 | 5 | ## 1. Stack-based? 6 | Stack-based means that every value and function are operations or transformations performed on a "stack," which acts as a list of values for later 7 | functions to work with. Values are "pushed" and "popped" from the stack to be used as parameters. To demonstrate this, a simple 8 | program in pseudocode to calculate `(4 + 3) * 9` might look something like this: 9 | ```js 10 | push 4 11 | push 3 12 | add 13 | push 9 14 | multiply 15 | ``` 16 | The basic steps followed in this program (along with the current state of the stack) are as follows: 17 | * `push` the value `4` to the stack; the stack is now `[4]` 18 | * `push` the value `3` to the stack; the stack is now `[4, 3]` 19 | * pop two items off the stack and `add` them, then push the result; the stack is now `[7]` 20 | * `push` the value `9` to the stack; the stack is now `[7, 9]` 21 | * pop two items off the stack and `multiply` them, then push the result; the final stack is `[63]` 22 | This is the expected result of the expression. Success! 23 | 24 | 25 | ## 2. Basic Syntax 26 | 27 | Because Woden is stack-based, it is more natural for functions to be written _after_ their parameters, essentially in [reverse polish notation](https://en.wikipedia.org/wiki/Reverse_Polish_notation). 28 | This exposes the stack-based nature of the calculations and so causes less friction with the implementation. 29 | As an example, in order to calculate the value of 2 + 3, one would write `2 3 +`. 30 | One interesting and useful property of writing expressions this way is the elimination of the need for parentheses to reorder operations and their precedences. 31 | This can be demonstrated by converting from infix to postfix notation: 32 | ```js 33 | INFIX POSTFIX 34 | 1 + 2 / 3 - 4 * 5 1 2 3 / + 4 5 * - 35 | (1 + 2) / (3 - 4) * 5 1 2 + 3 4 - / 5 * 36 | 1 + 2 / (3 - 4 * 5) 1 2 3 4 5 * - / + 37 | ``` 38 | 39 | This soon becomes second nature to deal with, and also carries with it some other benefits, 40 | such as function currying/partial application for free and high levels of composability. 41 | 42 | ## 3. Numbers 43 | 44 | Integer number literals can be written as you would expect in many other languages, with the exception 45 | that the `-` operator cannot be used in prefix form to specify a negative number. Currently the 46 | only method of obtaining a negative number is subtracting from zero. 47 | Floating-point literals are also not present yet, but will be at some point. 48 | Eventually, there is planned support for numbers in higher bases as well for more concise literals. 49 | It is likely that the base will be one of 120, 180, etc. as they are highly divisible and so also allow the finite 50 | expression of some fractional numbers which are non-terminating in decimal (such as 1/3). 51 | 52 | ## 4. Lists 53 | 54 | Lists of numbers in Woden can be created in much the same way as in some other languages, 55 | by surrounding the values with brackets. However, they are not separated by commas (as the comma is the name of another function). 56 | What is written `[1, 2, 3, 4]` in many languages is simply `[1 2 3 4]` in Woden. The contents of lists are evaluated when the 57 | list is pushed to the stack, thus `[1 2 3 +]` is equivalent to `[1 5]`. If you want to have a function as an element of a normal list, 58 | you should use either a function block or an unevaluated list (see extra notes). Lists can be nested to any depth. 59 | 60 | 61 | ## 5. "Strings" 62 | 63 | Because Woden does not have a true string or character type, these data types are represented 64 | as lists of numbers or single numbers respectively. To make it easier to enter this kind of list, it is possible to surround text 65 | with double quotes to generate a list of numbers (thus `[116 104 105 115]` can be represented like `"this"` instead). 66 | This also allows quick construction of lists of small numbers (up to 255, since each character is one byte). Similarly, 67 | single characters are represented as letters/numbers preceeded by an apostrophe (`'`). For all printable ASCII characters 68 | the output should be what is expected, hence `'a` is equal to `97`. 69 | 70 | ## 6. Functions 71 | 72 | Each function in Woden has a specified *arity*, that is, the number of parameters it needs to operate. This means no function 73 | in the language uses a variable number of arguments for calculation (though some may operate on the whole stack). This 74 | property is crucial to determine whether the function will be applied or simply placed on the stack as a value, leading 75 | to the next important point: 76 | 77 | In Woden, functions are *first-class values*, meaning 78 | they can themselves be inputs to other functions. This enables a functional style of programming, using higher-order 79 | functions such as `map` or `fold` rather than explicit iteration. In order to facilitate this further, function blocks can be made 80 | which contain unevauated sequences of items to be pushed to a stack one after the other, written between curly braces. 81 | A block which adds 3 to a number is therefore simply written `{3 +}`. To the interpreter, 82 | this is seen as an inseperable function of its own, indistinguishable in that regard from basic operations like `+`, 83 | and can be used in the same ways as an argument. This allows both composition and, in effect, currying of functions. 84 | 85 | Many functions in Woden examine the type of their arguments to choose a suitable behavior. For example, the `*` function, which 86 | acts as multiplication on numbers but as the `map` function when used on a list and another function. Sample usage: 87 | ```js 88 | 2 3 * // 6 89 | [1 2 3] {3 +} * // [4, 5, 6] 90 | ``` 91 | 92 | ## 7. Sequences 93 | 94 | Sequences in Woden aim to provide a way to interact with infinite sequences of values without sacrificing 95 | the ease of mapping, filtering, etc. which lists enable. Any operation which makes sense to perform on infinite 96 | lists will eventually be made to work as expected on both lists and sequences where possible. With this goal in mind, 97 | mapping and filtering are already functional (folds are impractical/impossible however). 98 | Sample usage: 99 | ```js 100 | {1 +} s // create a sequence which generates each next term by adding one 101 | {1 +} s 5 t // [0, 1, 2, 3, 4] (take the first five values; default starting value is 0) 102 | [1 2 3] {1 +} S // the same sequence, starting with a predefined list instead 103 | [0 1] {+} S // a sequence starting with [0, 1] and generating next terms from the sum of the two previous 104 | [0 1] {+} S 10 t // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34] (the beginning of the fibonacci numbers!) 105 | {1 +} s {3 *} * // all whole numbers >= 0, then multiplied by 3 106 | ``` 107 | As you can see, Sequences are already fairly powerful, and can be used to implement certain kinds of 108 | recursive algorithms. 109 | 110 | ## 8. Input 111 | 112 | Woden currently has no form of input during execution in its newest iteration, just a commandline repl. 113 | Consider the version currently available a sort of desktop calculator, albeit with some cool abilities. 114 | Input of several kinds will definitely be supported in the future, likely through 115 | a more convenient html-based interface which has close interaction with the interpreter. 116 | 117 | ## 9. Extra Notes 118 | 119 | The syntax for lists and function blocks actually allows for the use of `]` or `}` as the closing 120 | character for either. Which is used impacts whether the contents will be evaluated or not. Through this 121 | usage, one can create a list of functions for example, or (perhaps less usefully) have the contents of 122 | a function block evaluated before being pushed to the stack. 123 | Using `]` as the closing character prompts evaluation of the list/block, 124 | while `}` prevents it: 125 | ```js 126 | [1 2 3 +] // [1 5] 127 | [1 2 3 +} // [1 2 3 <+>] (the function itself is in the list) 128 | {[1 2] + /} // {[1 2] <+> } 129 | {[1 2] + /] // {3} (this is the usage of `/` as the fold/reduce operator) 130 | ``` 131 | -------------------------------------------------------------------------------- /deprecated/builtins-old.ls: -------------------------------------------------------------------------------- 1 | 2 | 3 | # 4 | # Utility functions, used only internally or by the interpreter 5 | # 6 | 7 | 8 | # main function to call to push anything to a stack 9 | export push = (item, arr) --> 10 | return arr if item is null 11 | if type(item) is \Function and arr.length >= (item.arity ? 0) 12 | if item.manualpush 13 | item arr 14 | else 15 | arr.push item ...[arr.pop! for til item.arity] 16 | else 17 | arr.push item 18 | arr 19 | 20 | cycle = (amount, arr) --> 21 | real_amt = amount % arr.length 22 | if real_amt is 0 23 | arr 24 | else if real_amt < 0 25 | cycle real_amt, arr.reverse! .reverse! 26 | else 27 | arr[real_amt to] ++ arr[til real_amt] 28 | 29 | rotate = (depth, amount, arr) --> 30 | if depth is 0 31 | arr 32 | else if depth < 0 33 | rotate -depth, -amount, arr.reverse! .reverse! 34 | else if depth >= arr.length 35 | cycle amount, arr 36 | else 37 | arr[til arr.length - depth] ++ cycle amount, arr[arr.length - depth to] 38 | 39 | # decorator-ish thing to set manual stack control 40 | manualpush = (f) -> 41 | f.manualpush = true 42 | f 43 | 44 | # decorator-ish thing to set arity of a function 45 | arity = (ar, f) --> 46 | f.arity = ar ? 0 47 | f 48 | 49 | # function for generating stack push sequences 50 | export fseq = (sequence) -> 51 | f = manualpush arity(0) (stack) !-> 52 | for item in sequence 53 | if !item.is-fseq # do not apply internal fseqs when pushing. 54 | push item, stack # this is to allow for proper nesting. 55 | else # otherwise, {x} is functionally identical to {{x}} 56 | stack.push item 57 | f.is-fseq = true 58 | f.seq = sequence 59 | f 60 | 61 | # function for getting some interpreter functionality on the stack. 62 | # it needs a reference to the evaluation function and the block to push. 63 | # it also needs a copy of the ast to allow for deeper recursion. 64 | export mega-fseq = (evaluate, block, ast) -> 65 | manualpush arity(0) (stack) !-> 66 | evaluate(block, stack, ast) # that oughta do it 67 | 68 | flatten = (arr) -> 69 | res = [] 70 | for item in arr 71 | if type(item) is \Array 72 | res .= concat item 73 | else 74 | res.push item 75 | res 76 | 77 | deep-flatten = (arr) -> 78 | res = [] 79 | for item in arr 80 | if type(item) is \Array 81 | res .= concat deep-flatten item 82 | else 83 | res.push item 84 | res 85 | 86 | truthy = -> 87 | switch it 88 | case 0 => false 89 | case null, false => false 90 | else => true 91 | 92 | 93 | # the special 'rune' functions (prefixed functions). 94 | # they have the option of accepting a reference to the evaluation function and ast if needed. 95 | export runes = { 96 | # syntactic sugar for a fseq, ex. `+ for {+} and ``+ for {{+}} 97 | fseq: (a) -> 98 | fseq [a] 99 | 100 | # the `on` combinator. (f ᚶ g)(a, b) = f(g(a), g(b)) 101 | on-combinator: (a, b) -> 102 | manualpush arity((a.arity ? 0) * (b.arity ? 0)) (stack) -> 103 | stack.push (push a, flatten [push b, [stack.pop! for til b.arity ? 0] for til a.arity ? 0]) 104 | 105 | # apply function but leave input on the stack 106 | non-consuming-apply: (a) -> 107 | manualpush arity(a.arity) (stack) -> 108 | stack.push a ...take(-a.arity, stack) 109 | 110 | # apply function but leave input on the stack, and on top 111 | non-consuming-apply-swap: (a) -> 112 | manualpush arity(a.arity) (stack) -> 113 | args = [stack.pop! for til a.arity] 114 | stack.push a ...args 115 | for item in args.reverse! 116 | stack.push item 117 | 118 | # reverse application. planned to be changed, since it's equal to ":!" 119 | reverse-apply: (a) -> 120 | manualpush arity(0) (stack) !-> 121 | top = stack.pop! 122 | stack.push a 123 | push top, stack 124 | 125 | # conditional branch. (a ? b c) is similar to (a ? b : c) in c-like languages. 126 | conditional1: (b, c, evaluate, ast) -> 127 | manualpush arity(1) (stack) !-> 128 | a = stack.pop! 129 | if truthy a 130 | push evaluate(b, stack, ast), stack 131 | else 132 | push evaluate(c, stack, ast), stack 133 | } 134 | 135 | 136 | # 137 | # Functions which can be pushed to the stack 138 | # 139 | 140 | 141 | export type = arity(1) (a) -> 142 | a?.constructor.name ? \Null 143 | 144 | export id = manualpush arity(1) (stack) !-> 145 | 146 | export add = arity(2) (a, b) -> 147 | # addition on numbers 148 | if type(a) == type(b) == \Number 149 | b + a 150 | # overload: scanr on function, array 151 | else if type(a) is \Function and type(b) is \Array 152 | b[til b.length - 1].reverse!reduce ((previous, nextitem) -> 153 | previous.concat push a, [previous[*-1], nextitem]), [b[*-1]] 154 | # overload: scanl on array, function 155 | else if type(a) is \Array and type(b) is \Function 156 | a[1 to].reduce ((previous, nextitem) -> 157 | previous.concat push b, [previous[*-1], nextitem]), [a[0]] 158 | 159 | export sub = arity(2) (a, b) -> 160 | # subtraction on numbers 161 | if type(a) == type(b) == \Number 162 | b - a 163 | # overload: filter on function, array 164 | else if type(a) is \Function and type(b) is \Array 165 | b.filter -> 166 | truthy(push a, [it] .0) # only first element of result is checked 167 | # overload: filter on array, function 168 | else if type(a) is \Array and type(b) is \Function 169 | a.filter -> 170 | truthy(push b, [it] .0) # only first element of result is checked 171 | # overload: filter on function, sequence 172 | else if type(a) is \Function and type(b) is \Sequence 173 | b.add-transform { 174 | type: \filter 175 | func: a 176 | } 177 | b 178 | 179 | export mul = arity(2) (a, b) -> 180 | # multiplication on numbers 181 | if type(a) == type(b) == \Number 182 | b * a 183 | # overload: fmap on function, array 184 | else if type(a) is \Function and type(b) is \Array 185 | flatten b.map -> 186 | push a, [it] 187 | # overload: fmap on array, function 188 | else if type(a) is \Array and type(b) is \Function 189 | flatten a.map -> 190 | push b, [it] 191 | # overload: fmap on function, sequence 192 | else if type(a) is \Function and type(b) is \Sequence 193 | b.add-transform { 194 | type: \map 195 | func: a 196 | } 197 | b 198 | 199 | export div = arity(2) (a, b) -> 200 | # division on numbers 201 | if type(a) == type(b) == \Number 202 | b / a 203 | # overload: foldr on function, array 204 | else if type(a) is \Function and type(b) is \Array 205 | b[til b.length - 1].reverse!reduce((prev, val) -> 206 | push a, push val, prev 207 | , [b[*-1]])[*-1] # TODO: something about this, maybe 208 | # overload: foldl on array, function 209 | else if type(a) is \Array and type(b) is \Function 210 | a[1 to].reduce((prev, val) -> 211 | push b, push val, prev 212 | , [a[0]])[*-1] # TODO: something about this, maybe 213 | 214 | export mod = arity(2) (a, b) -> b % a 215 | 216 | export exp = arity(2) (a, b) -> b ^ a 217 | 218 | export gt = arity(2) (a, b) -> 219 | if b > a then 1 else 0 220 | 221 | export lt = arity(2) (a, b) -> 222 | if b < a then 1 else 0 223 | 224 | export eq = arity(2) (a, b) -> 225 | return 1 if a is b 226 | return 0 if type(a) != type(b) 227 | if type(a) is \Array 228 | return 0 if a.length != b.length 229 | for i til a.length 230 | return 0 unless eq(a[i], b[i]) 231 | return 1 232 | else if type(a) is \Function 233 | return 0 unless a.is-fseq and b.is-fseq 234 | return eq(a.seq, b.seq) 235 | else if type(a) is \Sequence 236 | return 0 unless eq(a.get-next, b.get-next) 237 | len = Math.max(a.length, b.length) 238 | a.ensure-length len 239 | b.ensure-length len 240 | return eq(a.base-seq, b.base-seq) 241 | return 0 242 | 243 | export dup = manualpush arity(1) (stack) !-> 244 | top = stack.pop! 245 | stack.push top 246 | stack.push top 247 | 248 | export drop1 = manualpush arity(1) (stack) !-> 249 | stack.pop! 250 | 251 | export swap = manualpush arity(2) (stack) !-> 252 | a = stack.pop! 253 | b = stack.pop! 254 | stack.push a 255 | stack.push b 256 | 257 | export apply = manualpush arity(1) (stack) !-> 258 | # re-push an item using the push function. 259 | # this means an fseq will be guaranteed to be applied. 260 | push stack.pop!, stack 261 | 262 | export incl-range = arity(2) (a, b) -> 263 | if a > b 264 | [b to a] 265 | else 266 | [b to a by -1] 267 | 268 | export unary-range = arity(1) (a) -> 269 | if type(a) is \Number 270 | if a >= 0 271 | [0 til a] 272 | else 273 | [0 til a by -1] 274 | 275 | export pack = manualpush arity(0) (stack) !-> 276 | # packs up the current stack and pushes it as an array 277 | s = [stack.pop! for til stack.length] 278 | stack.push s.reverse! 279 | 280 | export unpack = manualpush arity(1) (stack) !-> 281 | a = stack.pop! # an array whose contents you want on the stack 282 | return a if type(a) isnt \Array 283 | for item in a 284 | stack.push item # note that nothing is applied 285 | 286 | export rot = manualpush arity(0) (stack) !-> 287 | # cycles the third item from the top to the front of the stack 288 | stack.splice 0, stack.length, ...rotate 3 1 stack 289 | 290 | export length = arity(1) (a) -> 291 | if type(a) is \Number 292 | Math.abs a 293 | else if type(a) is \Array or type(a) is \Sequence 294 | a.length 295 | else if type(a) is \Function 296 | a.arity 297 | 298 | export concat = arity(2) (a, b) -> 299 | if type(a) isnt \Array and type(b) isnt \Array 300 | [b, a] 301 | else if type(a) is \Array and type(b) isnt \Array 302 | [b, ...a] 303 | else if type(a) isnt \Array and type(b) is \Array 304 | [...b, a] 305 | else if type(a) == type(b) == \Array 306 | b ++ a 307 | 308 | export repeat = manualpush arity(2) (stack) !-> 309 | a = stack.pop! # number of times to repeat 310 | b = stack.pop! # the thing to repeat 311 | for til a 312 | push b, stack 313 | 314 | export while-loop = manualpush arity(2) (stack) !-> 315 | a = stack.pop! # loop condition 316 | b = stack.pop! # item to push while the condition is true 317 | loop 318 | push a, stack 319 | result = stack.pop! # note that the result is consumed 320 | if result 321 | push b, stack 322 | else break 323 | 324 | export take = arity(2) (a, b) -> 325 | if type(b) is \Array 326 | return [] if a == 0 327 | return b if Math.abs(a) >= b.length 328 | return drop(b.length + a, b) if a < 0 329 | b[til Math.floor(a)] 330 | else if type(b) is \Sequence 331 | b.ensure-length(Math.floor(a)) 332 | b.seq[til Math.floor(a)] 333 | 334 | export drop = arity(2) (a, b) -> 335 | if type(b) is \Array 336 | return b if a == 0 337 | return [] if Math.abs(a) >= b.length 338 | return take(b.length + a, b) if a < 0 339 | b[Math.floor(a) to] 340 | else if type(b) is \Sequence 341 | b.ensure-length(Math.floor(a)) 342 | b.seq = drop(a, b.seq) 343 | b 344 | 345 | export bit-select = arity(2) (a, b) -> 346 | # selects particular indices of an array using an array of indices or a number's bits 347 | if type(a) is \Number 348 | a = Math.floor(a).to-string(2).split("").reverse!reduce((prev, val, i) -> 349 | prev.push i if val != "0" 350 | prev 351 | , []) 352 | if type(b) is \Number 353 | b = Math.floor(b).to-string(2).split("").reverse!reduce((prev, val, i) -> 354 | prev.push i if val != "0" 355 | prev 356 | , []) 357 | if type(b) is \Array 358 | [b[i] for i in a when i < b.length] 359 | else if type(b) is \Sequence 360 | [b.get(i) for i in a] 361 | 362 | # a data structure representing lazily-generated infinite sequences 363 | export class Sequence 364 | (@get-next, @base-seq = [0]) ~> 365 | @transforms = [] # should contain {type, func} objects 366 | @seq = [] # the sequence, filtered and mapped 367 | @base-pos = 0 # the element being accessed by the transform processor 368 | 369 | get: (index) -> 370 | if @ensure-length(index + 1) 371 | @seq[index] 372 | else 373 | null 374 | 375 | lengthen-base-seq: -> 376 | #console.log "base: #{JSON.stringify @base-seq}" 377 | #console.log "seq: #{JSON.stringify @seq}" 378 | @base-seq.push (push @get-next, ^^@base-seq)[*-1] 379 | 380 | add-transform: (transform) -> 381 | # adds and applies a new map or filter operation over the sequence; 382 | # this affects all future values of the sequence as you'd expect 383 | switch transform.type 384 | case \filter 385 | @seq = sub(transform.func, @seq) 386 | @length = @seq.length 387 | @transforms.push transform 388 | case \map 389 | @seq = mul(transform.func, @seq) 390 | @length = @seq.length 391 | @transforms.push transform 392 | 393 | ensure-length: (desired-length, give-up-after = 100) -> 394 | # ensures that the sequence contains at least `desired-length` elements 395 | times-tried = 0 396 | :main until @seq.length >= desired-length 397 | @lengthen-base-seq! 398 | item = [@base-seq[@base-pos]] 399 | for transform in @transforms 400 | switch transform.type 401 | case \filter 402 | unless truthy (push transform.func, ^^item)[*-1] 403 | @base-pos++ 404 | return false if (times-tried++ > give-up-after) # notify failure 405 | continue main 406 | times-tried = 0 407 | case \map 408 | item = push transform.func, item 409 | @seq .= concat item 410 | @base-pos++ 411 | return true # notify success 412 | 413 | export sequence1 = arity(1) (a) -> 414 | # constructs a sequence with initial base sequence of [0] by default 415 | Sequence a 416 | 417 | export sequence2 = arity(2) (a, b) -> 418 | # constructs a sequence using `b` as the initial base sequence 419 | Sequence a, b 420 | 421 | export elem = arity(2) (a, b) -> 422 | # takes the `a`-th element of `b` 423 | if type(b) is \Array 424 | b[a] 425 | else if type(b) is \Sequence 426 | b.get(a) 427 | 428 | export neg = arity(1) (a) -> 429 | if type(a) is \Number 430 | -a - 1 431 | else if type(a) is \Array 432 | a.reverse! 433 | -------------------------------------------------------------------------------- /deprecated/interpreter-old.ls: -------------------------------------------------------------------------------- 1 | require! "./parser-old.js" 2 | require! "./builtins-old.js" 3 | 4 | # slightly configured stringifier for nicer output 5 | stringify = (a) -> 6 | JSON.stringify a, (k, v)-> 7 | return \F if builtins.type(v) is \Function 8 | return \S if builtins.type(v) is \Sequence 9 | return v 10 | .replace(/"F"/g, "ƒ") # show functions more clearly 11 | .replace(/"S"/g, "[...]") # show sequences more clearly 12 | .replace(/,/g, " ") # separate with space instead of commas, for more homoiconicity 13 | 14 | 15 | interpret = (ast, flags={}) -> 16 | 17 | if flags.verbose or flags.timer 18 | time = process.hrtime! # start time of interpretation 19 | 20 | if typeof! ast is \String 21 | if flags.verbose 22 | console.log "Input: #{ast}" 23 | ast = parser.parse(ast, flags) 24 | 25 | # evaluate a token recursively. 26 | # input is a token, a stack to push things to, and a copy of the abstract syntax tree. 27 | evaluate = (token, stack=[], ast) -> 28 | console.log "Evaluating Token: #{JSON.stringify token}" if flags.verbose 29 | switch token.type 30 | case \number \operator 31 | token.value 32 | case \list 33 | mystack = [] 34 | for item in token.value 35 | if not token.evaluate or item.type is \function 36 | mystack.push evaluate(item, stack, ast) 37 | else 38 | builtins.push evaluate(item, stack, ast), mystack 39 | return mystack 40 | case \function 41 | mystack = [] 42 | for item in token.value 43 | if not token.evaluate or item.type is \function 44 | mystack.push evaluate(item, stack, ast) 45 | else 46 | builtins.push evaluate(item, stack, ast), mystack 47 | return builtins.fseq mystack 48 | case \prefix-operator 49 | args = [] 50 | if token.evaluate 51 | for item in token.arguments 52 | args.push evaluate(item, stack, ast) 53 | else 54 | args = token.arguments 55 | return token.value ...args.concat(evaluate, ast) 56 | case \block 57 | for item in token.value 58 | if item.type is \function 59 | stack.push evaluate(item, stack, ast) 60 | else 61 | res = evaluate(item, stack, ast) 62 | if res.is-fseq # this is disgusting to me, but it works. :/ 63 | stack.push res # if it didn't require special treatment, it'd be better. 64 | else 65 | builtins.push res, stack 66 | return stack 67 | case \block-reference 68 | return builtins.mega-fseq(evaluate, ast.value[token.value], ast) 69 | case \program 70 | mainblock = token.value[0] 71 | progstack = [] 72 | evaluate(mainblock, progstack, ast) 73 | if flags.verbose or flags.timer 74 | [secs, usecs] = process.hrtime(time) 75 | console.log "Total time taken: #{secs + usecs * 1e-9} seconds" 76 | console.log stringify progstack 77 | console.log! 78 | 79 | evaluate(ast, [], ast) # evaluate the program 80 | 81 | 82 | # start the interpreter 83 | process.stdout.write ">> " 84 | process.stdin.set-encoding \utf8 85 | process.stdin.on \data (text) -> 86 | if text is \quit 87 | process.exit! 88 | interpret text, { 89 | verbose: '-v' in process.argv 90 | timer: '-t' in process.argv 91 | +multi-digit-numbers 92 | -multi-digit-references 93 | } 94 | process.stdout.write ">> " 95 | -------------------------------------------------------------------------------- /deprecated/interpreter-older.ls: -------------------------------------------------------------------------------- 1 | b = require "./builtins-old.js" 2 | 3 | # the operators which the interpreter will recognize. 4 | # multicharacter operators are disabled. 5 | operators = 6 | "+": b.add 7 | "-": b.sub 8 | "*": b.mul 9 | "/": b.div 10 | "%": b.mod 11 | "^": b.exp 12 | "<": b.lt 13 | ">": b.gt 14 | "=": b.eq 15 | ":": b.swap 16 | ";": b.dup 17 | "_": b.incl-range 18 | "!": b.apply 19 | ")": b.pack 20 | "(": b.unpack 21 | "@": b.rot 22 | "l": b.length 23 | "r": b.repeat 24 | ",": b.concat 25 | "w": b.while-loop 26 | "t": b.take 27 | "d": b.drop 28 | "|": b.bit-select 29 | "s": b.sequence1 30 | "S": b.sequence2 31 | "e": b.elem 32 | "T": b.type 33 | "i": b.unary-range 34 | "~": b.neg 35 | 36 | # special-meaning characters which are not operators. 37 | specials = [ 38 | "{" "}" "[" "]" "`" "'" "\"" 39 | ] 40 | 41 | # custom encoding with 256 distinguishable printable characters 42 | # actually it's kind of 257, newline and ¦ are equivalent. 43 | character-encoding = [ 44 | '''Ø∑∏Δᚶᚽᛑᛙ⁻⁺¦¬£€¢¥''' 45 | '''⁰¹²³⁴⁵⁶⁷⁸⁹¶§«◊»¿''' 46 | ''' !"#$%&'()*+,-./''' 47 | '''0123456789:;<=>?''' 48 | '''@ABCDEFGHIJKLMNO''' 49 | '''PQRSTUVWXYZ[\]^_''' 50 | '''`abcdefghijklmno''' 51 | '''pqrstuvwxyz{|}~¤''' 52 | '''ĄÁÂÀÄÅÆḂÇĆČĊĎḊĐĘ''' 53 | '''ÉÊÈËḞĠÍÎÌÏĹĽŁṀŃŇ''' 54 | '''ÑÓŐÔÒÖṖŔŘŞŚŠṠŤṪÚ''' 55 | '''ŰÛÙÜŮẂŴẀẄÝŶỲŸŹŽŻ''' 56 | '''ąáâàäåæḃçćčċďḋđę''' 57 | '''éêèëḟġíîìïĺľłṁńň''' 58 | '''ñóőôòöṗŕřşśšṡťṫú''' 59 | '''űûùüůẃŵẁẅýŷỳÿźžż''' 60 | ].join "" 61 | 62 | to-ascii-char-code = (char) -> char.char-code-at 0 63 | to-char-code = (char) -> character-encoding.index-of char 64 | from-char-code = (code) -> character-encoding[code] 65 | 66 | # slightly configured stringifier 67 | stringify = (a) -> 68 | JSON.stringify a, (k, v)-> 69 | return \F if b.type(v) is \Function 70 | return \S if b.type(v) is \Sequence 71 | return v 72 | .replace(/"F"/g, "ƒ") # show functions more clearly 73 | .replace(/"S"/g, "[...]") # show sequences more clearly 74 | .replace(/,/g, " ") # separate with space instead of commas 75 | 76 | interpret = (code, flags={}) -> 77 | 78 | ops = ^^operators # clone so runtime modifications are OK if needed 79 | stacks = [[]] 80 | currentstack = 0 81 | stackoffset = 0 82 | applypushes = [true] 83 | pos = 0 84 | 85 | # 86 | # functions to help parsing; they return null on failure 87 | # 88 | 89 | # parse one number 90 | accept-number = -> 91 | char = code[pos] 92 | return null unless char in ["0" to "9"] 93 | if flags.multi-digit-numbers 94 | while ++pos < code.length and code[pos] in ["0" to "9"] 95 | char += code[pos] 96 | pos -- # this is to make sure the non-number char is still used 97 | +char 98 | 99 | # parse one operator 100 | accept-operator = -> 101 | char = code[pos] 102 | return null unless char of ops 103 | if flags.multi-character-operators 104 | while ++pos < code.length and char + code[pos] of ops 105 | char += code[pos] 106 | pos -- # this is to make sure the non-op char is still used 107 | ops[char] 108 | 109 | # parse one thing of any kind 110 | accept-anything = -> 111 | accept-operator! ? accept-number! 112 | 113 | # 114 | # functions to help interpreting 115 | # 116 | 117 | # decrease stack nesting 118 | nesting-decrease = -> 119 | applypushes.pop! if applypushes.length > 1 120 | currentstack -- 121 | if currentstack < 0 122 | while stackoffset + currentstack < 0 123 | stackoffset++ 124 | stacks.unshift [] 125 | 126 | # increase stack nesting 127 | nesting-increase = (next-push-setting) -> 128 | applypushes.push next-push-setting 129 | currentstack++ 130 | if stacks.length - stackoffset < currentstack + 1 131 | stacks.push [] 132 | 133 | while pos < code.length 134 | char = code[pos] 135 | apply = applypushes[*-1] 136 | stack = stacks[currentstack + stackoffset] 137 | 138 | # if it's an operator, apply it. 139 | if (op = accept-operator!) isnt null 140 | if apply 141 | b.push op, stack 142 | else 143 | stack.push op 144 | console.log stringify stack if flags.verbose 145 | 146 | # if it's a number, put it on the stack. 147 | else if (num = accept-number!) isnt null 148 | stack.push num 149 | 150 | # handle other characters which have special meanings 151 | else if char in specials 152 | switch char 153 | case "{" 154 | nesting-increase false 155 | case "}" 156 | nesting-decrease! 157 | stacks[currentstack + stackoffset].push b.fseq stack 158 | stacks[currentstack + stackoffset + 1] = [] 159 | case "[" 160 | nesting-increase true 161 | case "]" 162 | nesting-decrease! 163 | stacks[currentstack + stackoffset].push stack 164 | stacks[currentstack + stackoffset + 1] = [] 165 | case "`" # sugar for a one-item fseq 166 | if ++pos < code.length 167 | if (thing = accept-anything!) isnt null 168 | stack.push b.fseq [thing] 169 | 170 | if flags.verbose 171 | console.log "stack: #{currentstack}, offset: #{stackoffset}, apply: #{applypushes[*-1]}" 172 | console.log "all stacks:", stringify stacks 173 | console.log! 174 | pos++ # since it isn't a for loop after all 175 | 176 | # finally, print the result 177 | console.log stringify stacks[currentstack + stackoffset] unless flags.verbose 178 | console.log! 179 | 180 | 181 | # start the interpreter 182 | process.stdin.resume! 183 | process.stdin.set-encoding \utf8 184 | process.stdout.write ">> " 185 | process.stdin.on \data (text) -> 186 | if text is \quit 187 | process.exit! 188 | interpret text, { 189 | -verbose 190 | +multi-digit-numbers 191 | -multi-character-operators 192 | } 193 | process.stdout.write ">> " -------------------------------------------------------------------------------- /deprecated/parser-old.ls: -------------------------------------------------------------------------------- 1 | b = require "./builtins.js" 2 | 3 | # the operators which the interpreter will recognize 4 | ops = 5 | "+": b.add 6 | "-": b.sub 7 | "*": b.mul 8 | "/": b.div 9 | "%": b.mod 10 | "^": b.exp 11 | "<": b.lt 12 | ">": b.gt 13 | "=": b.eq 14 | ":": b.swap 15 | ";": b.dup 16 | "_": b.incl-range 17 | "!": b.apply 18 | ")": b.pack 19 | "(": b.unpack 20 | "@": b.rot 21 | "l": b.length 22 | "r": b.repeat 23 | ",": b.concat 24 | "w": b.while-loop 25 | "t": b.take 26 | "d": b.drop 27 | "|": b.bit-select 28 | "s": b.sequence1 29 | "S": b.sequence2 30 | "e": b.elem 31 | "T": b.type 32 | "i": b.unary-range 33 | "~": b.neg 34 | ".": b.id 35 | 36 | # special meaning prefix operators. 37 | # the value of each key is its function, number of arguments and whether its args should be evaluated. 38 | prefixes = 39 | "`": 40 | value: b.runes.fseq 41 | arity: 1 42 | evaluate: true 43 | "ᚶ": 44 | value: b.runes.on-combinator 45 | arity: 2 46 | evaluate: true 47 | "ᚽ": 48 | value: b.runes.non-consuming-apply 49 | arity: 1 50 | evaluate: true 51 | "ᛑ": 52 | value: b.runes.non-consuming-apply-swap 53 | arity: 1 54 | evaluate: true 55 | "ᛙ": 56 | value: b.runes.reverse-apply 57 | arity: 1 58 | evaluate: true 59 | "?": 60 | value: b.runes.conditional1 61 | arity: 2 62 | evaluate: false 63 | 64 | # custom encoding with 256 distinguishable printable characters. 65 | # actually it's kind of 257, but newline and ¦ are equivalent. 66 | character-encoding = [ 67 | '''Ø∑∏Δᚶᚽᛑᛙ⁻⁺¦¬£€¢¥''' 68 | '''⁰¹²³⁴⁵⁶⁷⁸⁹¶§«◊»¿''' 69 | ''' !"#$%&'()*+,-./''' 70 | '''0123456789:;<=>?''' 71 | '''@ABCDEFGHIJKLMNO''' 72 | '''PQRSTUVWXYZ[\\]^_''' 73 | '''`abcdefghijklmno''' 74 | '''pqrstuvwxyz{|}~¤''' 75 | '''ĄÁÂÀÄÅÆḂÇĆČĊĎḊĐĘ''' 76 | '''ÉÊÈËḞĠÍÎÌÏĹĽŁṀŃŇ''' 77 | '''ÑÓŐÔÒÖṖŔŘŞŚŠṠŤṪÚ''' 78 | '''ŰÛÙÜŮẂŴẀẄÝŶỲŸŹŽŻ''' 79 | '''ąáâàäåæḃçćčċďḋđę''' 80 | '''éêèëḟġíîìïĺľłṁńň''' 81 | '''ñóőôòöṗŕřşśšṡťṫú''' 82 | '''űûùüůẃŵẁẅýŷỳÿźžż''' 83 | ].join "" 84 | 85 | to-char-code = (char) -> character-encoding.index-of char 86 | from-char-code = (code) -> character-encoding[code] 87 | to-char-code-UTF8 = (char) -> char.char-code-at 0 88 | from-char-code-UTF8 = (code) -> String.from-char-code code 89 | 90 | /* 91 | The grammar as implemented currently: 92 | 93 | ::= { "¦" } 94 | 95 | ::= { } 96 | 97 | ::= 98 | | 99 | | 100 | | 101 | | 102 | 103 | ::= + 104 | | "'" ([#character-encoding]) 105 | ::= "0" | "1" | ... | "8" | "9" 106 | 107 | ::= "[" { } "]" 108 | | "[" { } "}" 109 | | '"' { ([^"]) } '"' 110 | 111 | ::= "{" { } "]" 112 | | "{" { } "}" 113 | 114 | ::= "+" | "-" | ... | "i" | "~" 115 | 116 | ::= 117 | | 118 | ::= "`" | "ᚽ" | "ᛑ" | "ᛙ" 119 | ::= "ᚶ" | "?" 120 | */ 121 | 122 | # a new-and-improved parser using recursive descent. 123 | # this allows for far better parsing of nested expressions than before. 124 | export parse = (string, flags={}) -> 125 | 126 | pos = 0 127 | codestr = string.trim! 128 | .replace "\n", "¦" 129 | .replace //[^#{JSON.stringify character-encoding}]//g, "" 130 | 131 | # facilities for printing better verbose-mode stuff 132 | depth = 0 133 | indentation = -> " " * depth 134 | 135 | # abort parsing, if error encountered 136 | abort = (error-msg) -> 137 | console.log "Warning! Parse error: #error-msg" 138 | process.exit! 139 | 140 | # test whether the code has ended 141 | code-end = -> pos >= codestr.length 142 | 143 | # get one character (and advance) 144 | get-char = -> 145 | console.log indentation! + "Character consumed: <#{peek-char!}>" if flags.verbose 146 | if not code-end! 147 | codestr[pos++] 148 | else 149 | null 150 | 151 | # peek one character 152 | peek-char = -> 153 | if not code-end! 154 | codestr[pos] 155 | else 156 | null 157 | 158 | # advance one character 159 | advance-char = -> 160 | if not code-end! 161 | console.log indentation! + "Position advanced past <#{peek-char!}>" if flags.verbose 162 | pos++ 163 | 164 | # accept one of the given options 165 | accept-one-of = (...accept-options) -> 166 | for accept-func in accept-options 167 | if (result = accept-func!) isnt null 168 | return result 169 | return null 170 | 171 | # parse whitespace 172 | accept-whitespace = -> 173 | while peek-char! == " " 174 | advance-char! 175 | 176 | # parse one number 177 | accept-number = -> 178 | return null unless peek-char! in "0123456789" 179 | if flags.verbose 180 | console.log indentation! + "Accepting Number..." 181 | depth++ 182 | numstr = get-char! 183 | if flags.multi-digit-numbers 184 | while not code-end! and peek-char! in "0123456789" 185 | numstr += get-char! 186 | if flags.verbose 187 | depth -- 188 | console.log indentation! + "...Number accepted." 189 | return { 190 | type: \number 191 | value: +numstr 192 | } 193 | 194 | # parse one quoted number 195 | accept-quoted-number = -> 196 | return null unless peek-char! is "'" 197 | advance-char! # to pass over the ' character 198 | next-char = get-char! 199 | return null if next-char is null 200 | return { 201 | type: \number 202 | value: to-char-code next-char 203 | } 204 | 205 | # parse one block reference 206 | accept-block-reference = -> 207 | return null unless peek-char! is "$" 208 | if flags.verbose 209 | console.log indentation! + "Accepting Block Reference..." 210 | depth++ 211 | advance-char! # to pass over the $ character 212 | refnum = accept-number! 213 | return null if refnum is null 214 | if flags.verbose 215 | depth -- 216 | console.log indentation! + "...Block Reference accepted." 217 | return { 218 | type: \block-reference 219 | value: refnum.value 220 | } 221 | 222 | /* The very concise but annoying to test way 223 | accept-block-reference = -> 224 | return null unless peek-char! in "⁰¹²³⁴⁵⁶⁷⁸⁹" 225 | if flags.verbose 226 | console.log indentation! + "Accepting Block Reference..." 227 | depth++ 228 | refstr = get-char! 229 | if flags.multi-digit-references 230 | while not code-end! and peek-char! in "⁰¹²³⁴⁵⁶⁷⁸⁹" 231 | refstr += get-char! 232 | if flags.verbose 233 | depth -- 234 | console.log indentation! + "...Block Reference accepted." 235 | real-value = +(refstr.split('').map(to-char-code >> (+ 32) >> from-char-code).join('')) 236 | return { 237 | type: \block-reference 238 | value: real-value 239 | } 240 | */ 241 | 242 | # parse one operator (one-character operators only for now) 243 | accept-operator = -> 244 | return null unless peek-char! of ops 245 | console.log indentation! + "Operator accepted." if flags.verbose 246 | return { 247 | type: \operator 248 | name: peek-char! 249 | value: ops[get-char!] 250 | } 251 | 252 | # parse one list literal 253 | accept-list = -> 254 | return null unless peek-char! == "[" 255 | if flags.verbose 256 | console.log indentation! + "Accepting List..." 257 | depth++ 258 | advance-char! # to pass over the [ character 259 | contents = [] 260 | while not code-end! and peek-char! not in "]}" 261 | next = accept-atom! 262 | break if next is null 263 | contents.push next 264 | end-char = get-char! 265 | should-eval = if code-end! then true else end-char == "]" 266 | if flags.verbose 267 | depth -- 268 | console.log indentation! + "...List accepted." 269 | return { 270 | type: \list 271 | evaluate: should-eval 272 | value: contents 273 | } 274 | 275 | # parse one quoted list literal 276 | accept-quoted-list = -> 277 | return null unless peek-char! == "\"" 278 | if flags.verbose 279 | console.log indentation! + "Accepting Quoted List..." 280 | depth++ 281 | advance-char! # to pass over the " character 282 | contents = [] 283 | while not code-end! and peek-char! isnt "\"" 284 | next-char = get-char! 285 | break if next-char is null 286 | contents.push { 287 | type: \number 288 | value: to-char-code next-char 289 | } 290 | advance-char! # to pass over the other " character 291 | if flags.verbose 292 | depth -- 293 | console.log indentation! + "...Quoted List accepted." 294 | return { 295 | type: \list 296 | evaluate: false # no point anyway 297 | value: contents 298 | } 299 | 300 | # parse one function block 301 | accept-fseq = -> 302 | return null unless peek-char! == "{" 303 | if flags.verbose 304 | console.log indentation! + "Accepting Fseq..." 305 | depth++ 306 | advance-char! # to pass over the { character 307 | contents = [] 308 | while not code-end! and peek-char! not in "]}" 309 | next = accept-atom! 310 | break if next is null 311 | contents.push next 312 | end-char = get-char! 313 | should-eval = if code-end! then false else end-char == "]" 314 | if flags.verbose 315 | depth -- 316 | console.log indentation! + "...Fseq accepted." 317 | return { 318 | type: \function 319 | evaluate: should-eval 320 | value: contents 321 | } 322 | 323 | # parse one prefix operator 324 | accept-prefix-operator = -> 325 | return null unless peek-char! of prefixes 326 | if flags.verbose 327 | console.log indentation! + "Accepting Prefix Operator..." 328 | depth++ 329 | prefix-op = get-char! 330 | contents = [] 331 | for til prefixes[prefix-op].arity 332 | next = accept-atom! 333 | if next is null 334 | console.log "...Prefix Operator parse failure!" if flags.verbose 335 | return null 336 | contents.push next 337 | if flags.verbose 338 | depth -- 339 | console.log indentation! + "...Prefix Operator accepted." 340 | return { 341 | type: \prefix-operator 342 | name: prefix-op 343 | value: prefixes[prefix-op].value 344 | evaluate: prefixes[prefix-op].evaluate 345 | arguments: contents 346 | } 347 | 348 | # parse one token of any kind 349 | accept-atom = -> 350 | accept-whitespace! 351 | return accept-one-of( 352 | accept-number, 353 | accept-quoted-number, 354 | accept-block-reference, 355 | accept-operator, 356 | accept-list, 357 | accept-quoted-list, 358 | accept-fseq, 359 | accept-prefix-operator 360 | ) 361 | 362 | # parse one woden program 363 | parse-program = -> 364 | blocks = [{ 365 | type: \block 366 | value: [] 367 | }] 368 | while not code-end! 369 | accept-whitespace! 370 | if peek-char! in "¦|" 371 | console.log "Beginning new block." if flags.verbose 372 | blocks.push { 373 | type: \block 374 | value: [] 375 | } 376 | advance-char! 377 | else 378 | next = accept-atom! 379 | if next is null 380 | advance-char! 381 | continue 382 | console.log "Atom parsed: #{JSON.stringify next}" if flags.verbose 383 | blocks[*-1].value.push next 384 | result = { 385 | type: \program 386 | value: blocks 387 | } 388 | if flags.verbose 389 | console.log JSON.stringify result, null, " " 390 | console.log "Parsing complete!" 391 | console.log! 392 | return result 393 | 394 | return parse-program! 395 | -------------------------------------------------------------------------------- /interpreter.ls: -------------------------------------------------------------------------------- 1 | # A new interpreter for Woden, which is not reliant on one-character preset names. 2 | # It also brings new fun features like closures, variables, context-sensitive function 3 | # overloads, and more, making it vastly more powerful than past versions. 4 | # This is strictly incompatible with the old interpreter, of course. 5 | 6 | # regular expressions matching different tokens 7 | identifier = 8 | type: \identifier 9 | regex: /^[a-zA-Z_](?:\w|_)*'*/ 10 | # matches identifiers like the following: 11 | # name 12 | # nameWithNumber123 13 | # name_with_underscores 14 | # _with_underscores_ 15 | # MultiCaseName 16 | # nameWithTrailingQuote' 17 | # evenTHIS__15_ok''' 18 | # but not: 19 | # 'beginningQuote 20 | # intermediate'quote 21 | # 123beginningNumbers 22 | 23 | operator-identifier = 24 | type: \identifier 25 | regex: /^[-~!@$%^&*+=;:<>,.?\/\\]+/ 26 | # matches operator-like idenifiers like the following: 27 | # + 28 | # - 29 | # <$> 30 | # != 31 | # >>= 32 | # ** 33 | # but not: 34 | # <_> (no underscores in this kind of identifier) 35 | # +a 36 | # <'-" 37 | # -> (since that's a keyword) 38 | 39 | keyword = 40 | type: \keyword 41 | regex: /^(?:define|end|->)(?=\s|$)/ 42 | # matches any of the keywords: 43 | # define 44 | # end 45 | # -> 46 | # but not: 47 | # defined 48 | # not-keyword-define 49 | # end- 50 | # end' 51 | # <-> 52 | # ->> 53 | 54 | number = 55 | type: \number 56 | regex: /^\d(?:[_\d]*(?:\.[_\d]*)?\d)?/ 57 | # matches number literals like the following: 58 | # 1 59 | # 1002392 60 | # 400_000_000 61 | # 123.0 62 | # 100_000.921 63 | # 0.09 64 | # but not: 65 | # .120 66 | # 2319. 67 | # _3 68 | # 10.9_ 69 | 70 | string = 71 | type: \string 72 | regex: /^"((?:\\"|[^"])*)"/ 73 | # matches string literals like the following: 74 | # "wow" 75 | # "string" 76 | # "quote inside: \" " 77 | # "" 78 | # but not: 79 | # """ 80 | # "he said "hello!" to me" 81 | # 'single-quotes' 82 | 83 | character = 84 | type: \character 85 | regex: /^'(.)/ 86 | # matches character literals like the following: 87 | # 'a 88 | # 'b 89 | # '' 90 | # '\ 91 | # '" 92 | # but not: 93 | # a' 94 | # 95 | 96 | special = 97 | type: \special 98 | regex: /^[\{\}\(\)\[\]#`]/ 99 | # matches characters which cannot be present in valid identifiers: 100 | # [ and ] 101 | # { and } 102 | # ( and ) 103 | # # (since it is used as a type signature) 104 | # ` (analogous to {}, as a function constructor) 105 | 106 | whitespace = /^\s+/ 107 | # matches whitespace characters: 108 | # space 109 | # tab 110 | # newline 111 | 112 | # the regexes ordered by precedence 113 | # for example, "end" is a valid identifier, 114 | # but it must be treated as a keyword instead. 115 | # thus, `keyword` is before `identifier` in the list. 116 | token-expressions = [ 117 | special, 118 | keyword, 119 | string, 120 | character, 121 | number, 122 | identifier, 123 | operator-identifier 124 | ] 125 | 126 | 127 | # lexer and for the new syntax. 128 | # previously, the 'parser' had no discrete lexing phase. 129 | # hopefully adding one will prove to be a boon. 130 | # perhaps eventually a pipeline will be made which does things lazily/concurrently 131 | lex = (codestr) -> 132 | 133 | codestr = codestr.trim! # remove unnecessary whitespace 134 | # remove comments of the form (# ... #), (## ... ##), etc. 135 | # this uses (?=(exp))\1 to emulate (?>exp) atomic groups 136 | .replace(/\((?=(#+))\1[^#\)](?:[\s\S]*?[^#])?\1\)/gm, "") 137 | # remove comments of the form ## ... 138 | .replace(/##.*$/gm, "") 139 | 140 | tokens = [] # the eventual return value of the lexer 141 | 142 | all-matches-failed = false # flag to determine if lexing can't continue 143 | 144 | while !all-matches-failed and codestr.length > 0 145 | 146 | # if whitespace exists before/between tokens... 147 | if (leading-whitespace = whitespace.exec codestr) 148 | # slice the codestring to get rid of it 149 | codestr = codestr.substr(leading-whitespace[0].length) 150 | 151 | break if codestr.length is 0 152 | 153 | 154 | # set it to true, to be changed during the loop 155 | all-matches-failed = true 156 | 157 | for expression in token-expressions 158 | # test the expression and get a result at one time 159 | if (result = expression.regex.exec codestr) 160 | all-matches-failed = false 161 | tokens.push { 162 | type: expression.type 163 | value: result 164 | } 165 | # slice the codestring to advance through it 166 | codestr = codestr.substr(result[0].length) 167 | break 168 | 169 | if all-matches-failed 170 | process.stdout.write ("Error when tokenizing: unexpected character: " + codestr[0] + "\n") 171 | 172 | return tokens 173 | 174 | # parser for the new syntax. 175 | # returns an AST object meant to be interpreted. 176 | # this parser's resultant AST is not compatible with the older parser's. 177 | parse = (tokens, ENV={}) -> 178 | 179 | # helper functions to parse the code via recursive descent 180 | 181 | accept-one-of = (...accept-options) -> 182 | for accept-function in accept-options 183 | if (result = accept-function!) isnt null 184 | return result 185 | return null 186 | 187 | accept-number = -> 188 | [token, ...rest] = tokens 189 | return null if token is undefined 190 | if token.type is \number 191 | tokens := rest # advance past token 192 | return { 193 | type: \number 194 | value: +(token.value[0].replace(/_+/g, "")) # convert value after removing underscores 195 | } 196 | else 197 | return null 198 | 199 | accept-identifier = -> 200 | [token, ...rest] = tokens 201 | return null if token is undefined 202 | if token.type is \identifier 203 | tokens := rest 204 | return { 205 | type: \identifier 206 | value: token.value[0] 207 | } 208 | else 209 | return null 210 | 211 | accept-keyword = (keyword) -> 212 | [token, ...rest] = tokens 213 | return null if token is undefined 214 | if token.type is \keyword 215 | if keyword and token.value[0] != keyword # allow matching a particular keyword 216 | return null 217 | tokens := rest 218 | return { 219 | type: \keyword 220 | value: token.value[0] 221 | } 222 | else 223 | return null 224 | 225 | accept-string = -> 226 | [token, ...rest] = tokens 227 | return null if token is undefined 228 | if token.type is \string 229 | tokens := rest 230 | vals = [] 231 | for char in token.value[1] 232 | vals.push { 233 | type: \number 234 | value: char.char-code-at 0 235 | } 236 | return { 237 | type: \list 238 | value: vals 239 | } 240 | else 241 | return null 242 | 243 | accept-character = -> 244 | [token, ...rest] = tokens 245 | return null if token is undefined 246 | if token.type is \character 247 | tokens := rest 248 | return { 249 | type: \number 250 | value: token.value[1].char-code-at 0 251 | } 252 | else 253 | return null 254 | 255 | accept-special = (special) -> 256 | [token, ...rest] = tokens 257 | return null if token is undefined 258 | if token.type is \special 259 | if special and special != token.value[0] 260 | return null 261 | tokens := rest 262 | return { 263 | type: \special 264 | value: token.value[0] 265 | } 266 | else 267 | return null 268 | 269 | accept-list = -> 270 | return null unless accept-special("[") 271 | vals = [] 272 | while (next = accept-atom!) 273 | vals.push next 274 | return null unless accept-special("]") 275 | return { 276 | type: \list 277 | value: vals 278 | } 279 | 280 | accept-fseq = -> 281 | return null unless accept-special("{") 282 | vals = [] 283 | while (next = accept-atom!) 284 | vals.push next 285 | return null unless accept-special("}") 286 | return { 287 | type: \fseq # distinct from a named function 288 | value: vals 289 | } 290 | 291 | accept-quoted-fseq = -> 292 | return null unless accept-special("`") 293 | vals = [] 294 | if (next = accept-atom!) is null 295 | return null 296 | else 297 | vals.push next 298 | return { 299 | type: \fseq 300 | value: vals 301 | } 302 | 303 | accept-expression = -> 304 | return null unless accept-special("(") 305 | vals = [] 306 | while (next = accept-atom!) 307 | vals.push next 308 | return null unless accept-special(")") 309 | return { 310 | type: \expression 311 | value: vals 312 | } 313 | 314 | accept-type-restriction = -> 315 | if accept-special("#") 316 | return { 317 | type: \type-restriction 318 | value: \Number 319 | } 320 | else if accept-special("(") 321 | return null unless accept-special(")") 322 | return { 323 | type: \type-restriction 324 | value: \Function 325 | } 326 | else if accept-special("[") 327 | return null unless accept-special("]") 328 | return { 329 | type: \type-restriction 330 | value: \Array 331 | } 332 | else 333 | return null 334 | 335 | accept-function = -> 336 | return null unless accept-keyword("define") 337 | return null if (name = accept-identifier!) is null 338 | 339 | params = [] 340 | paramnumber = 0 341 | type-restrictions = 0 342 | value-restrictions = 0 343 | until accept-keyword("->") 344 | param = {} 345 | leftout = 0 346 | if (paramname = accept-identifier!) 347 | param.name = paramname.value 348 | param.anonymous = false 349 | else 350 | leftout++ 351 | param.name = paramnumber 352 | param.anonymous = true 353 | if (paramtype = accept-type-restriction!) 354 | param.type = paramtype.value 355 | type-restrictions++ 356 | else 357 | leftout++ 358 | if (paramcheck = accept-fseq!) 359 | param.check = paramcheck 360 | value-restrictions++ 361 | else 362 | leftout++ 363 | 364 | if leftout == 3 # no valid parameters, but also no -> keyword... 365 | return null # can't properly parse a function 366 | 367 | paramnumber++ 368 | params.push param 369 | 370 | arity = paramnumber 371 | 372 | vals = [] # list of values the function pushes when called 373 | until accept-keyword("end") 374 | if (next = accept-atom!) is null 375 | return null # because it must be malformed in this case 376 | vals.push next 377 | 378 | return { 379 | type: \function 380 | name: name.value 381 | params: params 382 | typechecks: type-restrictions 383 | valuechecks: value-restrictions 384 | arity: arity 385 | value: vals 386 | } 387 | 388 | accept-atom = -> 389 | # check all values which have meaning on their own 390 | return accept-one-of( 391 | accept-number, 392 | accept-identifier, 393 | accept-string, 394 | accept-character, 395 | accept-list, 396 | accept-fseq, 397 | accept-quoted-fseq, 398 | accept-expression 399 | ) 400 | 401 | accept-program = (ENV = {}) -> 402 | 403 | # ENV is the top-level namespace 404 | vals = [] # top-level values/invocations, etc. 405 | 406 | while tokens.length > 0 407 | if (func = accept-function!) 408 | if func.name of ENV 409 | ENV[func.name].overloads.push func 410 | else 411 | ENV[func.name] = { 412 | type: \function 413 | name: func.name 414 | overloads: [func] # representing a list of overloads 415 | } 416 | else if (next = accept-atom!) 417 | vals.push next 418 | else # the rest of input may be malformed if neither match... 419 | break 420 | 421 | return { 422 | type: \program 423 | value: vals 424 | environment: ENV 425 | } 426 | 427 | return accept-program(ENV) 428 | 429 | 430 | # this import is basically just for the `push`, `mega-fseq`, `truthy`, and `type` functions 431 | require! "./builtins" 432 | 433 | # slightly configured stringifier for nicer output 434 | stringify = (a) -> 435 | JSON.stringify a, (k, v)-> 436 | return \F if builtins.type(v) is \Function 437 | return \S if builtins.type(v) is \Sequence 438 | return v 439 | .replace(/"F"/g, "ƒ") # show functions more clearly 440 | .replace(/"S"/g, "[...]") # show sequences more clearly 441 | .replace(/,/g, " ") # separate with space instead of commas, for more homoiconicity 442 | 443 | 444 | # interpreter for the new syntax 445 | interpret = (AST, ENV={}, ext_stack=[]) !-> 446 | 447 | # it has so many arguments because they might all be needed at some point 448 | # but it has to also pass them down to all recursive calls... 449 | # ENV is a list of environments, to allow shadowing parameter names within functions 450 | evaluate = (node, stack, AST, ENV) -> 451 | switch node.type 452 | case \number 453 | stack.push node.value 454 | case \list 455 | list = [] 456 | for subnode in node.value 457 | mystack = [] # each gets an empty stack, preventing application 458 | evaluate(subnode, mystack, AST, ENV) 459 | list .= concat mystack 460 | stack.push list 461 | case \expression 462 | mystack = [] 463 | for subnode in node.value 464 | evaluate(subnode, mystack, AST, ENV) 465 | for result in mystack 466 | stack.push result # do not apply results, only push them 467 | case \fseq 468 | # lazy evaluation is desirable, so a mega-fseq is used to defer interpretation 469 | stack.push builtins.mega-fseq(evaluate, node.value, AST, ENV) 470 | case \runtime-value # an environmental value created at runtime (such as a function parameter) 471 | stack.push node.value 472 | case \identifier 473 | found = false # whether the identifier exists at all 474 | for env in ENV 475 | if node.value of env # if the identifier is present in the environment... 476 | evaluate(env[node.value], stack, AST, ENV) # evaluate it on the stack 477 | found = true # and flag that it was found in the current environment 478 | break # so we don't evaluate another environment's variable too 479 | if not found 480 | process.stdout.write "Error: unknown identifier: #{node.value}\n" 481 | return # no way to recover from this problem, really, so just do nothing 482 | case \native-function # a function whose definition is supplied in livescript/javascript directly 483 | builtins.push node.value, stack 484 | 485 | # this is by far the most complicated part, since overloads need to be resolved 486 | # a new environment also needs to be made 487 | # this also means making sure values moved to the environment if they are named 488 | case \function 489 | overloads = node.overloads 490 | # filter the overloads out which need too many arguments 491 | overloads .= filter (overload) -> overload.arity <= stack.length 492 | # if no overloads with low enough arity remain... 493 | if overloads.length is 0 494 | # then save it on the stack as a value for later instead 495 | stack.push builtins.mega-fseq(evaluate, [node], AST, ENV) 496 | return # end evaluation 497 | # filter the overloads which have non-matching type or value requirements 498 | overloads .= filter (overload) -> 499 | param-counter = 1 # used to get the right index in the stack 500 | for param in overload.params 501 | if param.type and builtins.type(stack[*-param-counter]) != param.type 502 | return false 503 | if param.check 504 | mystack = [stack[*-param-counter]] # the would-be parameter, in an empty stack 505 | evaluate(param.check, mystack, AST, ENV) 506 | builtins.push builtins.apply, mystack # apply the check function to the item 507 | return false if not builtins.truthy(mystack[*-1]) 508 | param-counter++ 509 | return true 510 | # if no overloads with matching type/value requirements remain... 511 | if overloads.length is 0 512 | # then none of the overloads are suitable. throw an error! 513 | process.stdout.write "Error: function #{node.name} has no suitable overload for the arguments: " 514 | process.stdout.write "#{stringify stack}\n" 515 | return # end execution 516 | # at this stage all overloads should be capable of being applied. 517 | # now they must be sorted according to the inbuilt rules (in order): 518 | # higher arity > lower arity 519 | # more type specifiers > less type specifiers 520 | # more value specifiers > less value specifiers 521 | 522 | loop # fake loop just so we can break out of it 523 | # filter out lower-arity overloads 524 | max-arity = Math.max.apply(null, overloads.map (a) -> a.arity) 525 | overloads .= filter (overload) -> 526 | overload.arity == max-arity 527 | break if overloads.length is 1 528 | 529 | # filter out overloads with fewer typechecks 530 | max-typechecks = Math.max.apply(null, overloads.map (a) -> a.typechecks) 531 | overloads .= filter (overload) -> 532 | overload.typechecks == max-typechecks 533 | break if overloads.length is 1 534 | 535 | # filter out overloads with fewer value checks 536 | max-valuechecks = Math.max.apply(null, overloads.map (a) -> a.valuechecks) 537 | overloads .= filter (overload) -> 538 | overload.valuechecks == max-valuechecks 539 | break if overloads.length is 1 540 | 541 | # if more than one overload remains, it is impossible to decide between them. 542 | process.stdout.write "Error: function #{node.name} has more than one suitable overload for the arguments: " 543 | process.stdout.write "#{stringify stack}\n" 544 | return # end execution 545 | 546 | # otherwise, we know we can use the overload which is left! 547 | main-overload = overloads[0] 548 | # now we create the environment we need 549 | env = {} 550 | param-counter = 1 551 | for param in main-overload.params 552 | if not param.anonymous 553 | env[param.name] = { 554 | type: \runtime-value 555 | value: stack.splice(stack.length - param-counter, 1)[0] 556 | } 557 | else 558 | param-counter++ # only incremented in the else, 559 | # since taking the parameters decreases the stack's length 560 | 561 | new-ENV = [env] ++ ENV # add `env` to the list of environments 562 | # finally, the actual code to interpret the overload itself 563 | for subnode in main-overload.value 564 | evaluate(subnode, stack, AST, new-ENV) # that's all it takes, but so much buildup :| 565 | 566 | case \program 567 | for subnode in node.value 568 | evaluate(subnode, stack, AST, [node.environment].concat ENV) # it's that simple 569 | process.stdout.write "#{stringify stack}\n\n" 570 | 571 | 572 | evaluate(AST, ext_stack, AST, [ENV]) # start the ball rolling 573 | 574 | 575 | 576 | # 577 | # Functions dealing with user interaction 578 | # 579 | 580 | flags = require \yargs # uses yargs to parse CLI arguments 581 | .usage "Usage: $0 [filepaths] [options]" 582 | # -v for verbose 583 | .count \v # more than one invocation increases verbosity 584 | .alias \v \verbose 585 | .describe \v "Enable verbose mode" 586 | # -b for basic (reduced default imports) 587 | .count \b # more than one invocation means ZERO imports 588 | .alias \b \basic 589 | .describe \b "Reduce imports enabled by default" 590 | # -r for REPL (instead of running from a file) 591 | .boolean \r 592 | .alias \r \repl 593 | .describe \r "Launch REPL environment" 594 | # -p for persistent REPL 595 | .boolean \p 596 | .alias \p \persistent 597 | .describe \p "Make REPL definitions and stack persist across entries" 598 | # -h for help (pretty standard) 599 | .help \h 600 | .alias \h \help 601 | .describe \h "Show this help message" 602 | # -e for execute from CLI 603 | .string \e 604 | .alias \e \execute 605 | .nargs \e 1 606 | .describe \e "Executes a string of valid Woden script" 607 | # other stuff 608 | .epilog "Made by Devin Hill 2016" 609 | .argv # parse those suckers 610 | 611 | fs = require \fs 612 | 613 | export repl = -> 614 | # setup a persistent environment for the repl 615 | # this enables function definitions to remain across inputs 616 | env = {} 617 | stack = [] 618 | unless flags.basic >= 2 619 | builtins.get-environment(flags.basic < 1, env) 620 | 621 | # setup/start the test REPL 622 | process.stdin.resume! 623 | process.stdin.set-encoding \utf8 624 | process.stdout.write "Woden REPL started.\nType `\\quit` to end execution, or `\\reset` to clear environmental variables.\n" 625 | process.stdout.write ">> " 626 | process.stdin.on \data (text) -> 627 | 628 | if text.trim! == '\\quit' # command to end the interpreter 629 | process.exit! 630 | 631 | if !flags.persistent or text.trim! == '\\reset' # command to reset environment 632 | env := {} # clear environmental variables 633 | stack := [] # clear stack 634 | unless flags.basic >= 2 # but reinstate builtin functionality 635 | builtins.get-environment(flags.basic < 1, env) 636 | 637 | text = "" if text.trim! == '\\reset' 638 | 639 | tokens = lex(text) 640 | if flags.verbose 641 | process.stdout.write "Lexed tokens: #{JSON.stringify tokens} \n" 642 | 643 | program = parse(tokens, env) # `env` is updated every time 644 | if flags.verbose 645 | process.stdout.write "Parsed program: #{JSON.stringify program, null, " "} \n" 646 | 647 | interpret(program, env, stack) 648 | 649 | process.stdout.write ">> " 650 | 651 | 652 | # handle logic based on CLI arguments 653 | if flags.repl 654 | repl! 655 | else if flags.execute 656 | env = {} # the base environment to receive imports and whatnot 657 | unless flags.basic >= 2 658 | builtins.get-environment(flags.basic < 1, env) 659 | interpret(parse(lex(flags.execute), env), env) 660 | else 661 | filepaths = flags._ 662 | if filepaths.length is 0 663 | repl! 664 | else 665 | env = {} # the base environment to receive imports and whatnot 666 | stack = [] # the base stack to be operated upon by all included files 667 | unless flags.basic >= 2 668 | builtins.get-environment(flags.basic < 1, env) 669 | 670 | for path in filepaths 671 | data = fs.readFileSync path, 'utf8' 672 | interpret(parse(lex(data), env), env, stack) 673 | 674 | --------------------------------------------------------------------------------