├── .github └── workflows │ ├── add-artifacts-to-current-release.yml │ └── ci.yml ├── .gitignore ├── .prettierignore ├── LICENSE ├── Mn_DeveloperGuide.md ├── README.md ├── build ├── description ├── docs ├── _defs_.md ├── about.md ├── get-started.md ├── learn-control-flow.md ├── learn-data-types.md ├── learn-definitions.md ├── learn-extending.md ├── learn-operators.md ├── learn.md └── reference.md ├── logo.svg ├── mn.nim ├── mn.nimble ├── mn.nims ├── mn.yml ├── mnpkg ├── interpreter.nim ├── lang.nim ├── meta.nim ├── parser.nim ├── scope.nim ├── utils.nim └── value.nim ├── mntool.mn └── tasks ├── build.mn ├── clean.mn ├── guide.mn ├── h3rald.mn ├── info.mn └── release.mn /.github/workflows/add-artifacts-to-current-release.yml: -------------------------------------------------------------------------------- 1 | name: Add artifacts to current release 2 | 3 | # Controls when the action will run. 4 | on: 5 | # Allows you to run this workflow manually from the Actions tab 6 | workflow_dispatch: 7 | 8 | jobs: 9 | release: 10 | name: "Build and upload artifacts" 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | matrix: 14 | os: 15 | - ubuntu-latest 16 | - macos-latest 17 | - windows-latest 18 | 19 | env: 20 | CHOOSENIM_CHOOSE_VERSION: stable 21 | CHOOSENIM_NO_ANALYTICS: 1 22 | 23 | steps: 24 | # Cancel other actions of the same type that might be already running 25 | - name: "Cancel similar actions in progress" 26 | uses: styfle/cancel-workflow-action@0.6.0 27 | with: 28 | access_token: ${{ github.token }} 29 | 30 | # Detects OS and provide Nim-friendly OS identifiers 31 | - name: Detect current OS 32 | id: os 33 | run: echo "::set-output name=id::${{matrix.os == 'ubuntu-latest' && 'linux' || matrix.os == 'macos-latest' && 'macosx' || matrix.os == 'windows-latest' && 'windows'}}" 34 | 35 | # Checks out the repository 36 | - uses: actions/checkout@v2 37 | 38 | # Installs libraries 39 | - name: install musl-gcc 40 | run: sudo apt-get install -y musl-tools 41 | if: matrix.os == 'ubuntu-latest' 42 | 43 | # Sets path (Linux, macOS) 44 | - name: Update $PATH 45 | run: echo "$HOME/.nimble/bin" >> $GITHUB_PATH 46 | if: matrix.os == 'macos-latest' || matrix.os == 'ubuntu-latest' 47 | 48 | # Sets path (Windows) 49 | - name: Update %PATH% 50 | run: echo "${HOME}/.nimble/bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append 51 | if: matrix.os == 'windows-latest' 52 | 53 | # Install the Nim compiler 54 | - name: Install Nim 55 | run: | 56 | curl https://nim-lang.org/choosenim/init.sh -sSf > init.sh 57 | sh init.sh -y 58 | 59 | # Build for Linux 60 | - name: Build (Linux) 61 | run: nim c -d:release --gcc.exe:musl-gcc --gcc.linkerexe:musl-gcc --gc:orc --deepcopy:on --opt:size mn 62 | if: matrix.os == 'ubuntu-latest' 63 | 64 | # Build for macOS/Windows 65 | - name: Build (macOS, Windows) 66 | run: nim c -d:release --gc:orc --deepcopy:on --opt:size mn 67 | if: matrix.os == 'macos-latest' || matrix.os == 'windows-latest' 68 | 69 | # Retrieve ID and Name of the current (draft) release 70 | - name: "Get current release" 71 | id: current-release 72 | uses: InsonusK/get-latest-release@v1.0.1 73 | with: 74 | myToken: ${{ github.token }} 75 | exclude_types: "release" 76 | view_top: 1 77 | 78 | # Package the resulting Linux/macOS binary 79 | - name: Create artifact (Linux, macOS) 80 | run: zip mn_${{steps.current-release.outputs.tag_name}}_${{steps.os.outputs.id}}_x64.zip mn 81 | if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' 82 | 83 | # Package the resulting Windows binary 84 | - name: Create artifact (Windows) 85 | run: Compress-Archive -Path mn.exe -DestinationPath mn_${{steps.current-release.outputs.tag_name}}_windows_x64.zip 86 | if: matrix.os == 'windows-latest' 87 | 88 | # Upload artifacts to current draft release 89 | - name: "Upload to current release" 90 | uses: xresloader/upload-to-github-release@v1 91 | env: 92 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 93 | with: 94 | file: "mn_v*.zip" 95 | overwrite: true 96 | tag_name: ${{steps.current-release.outputs.tag_name}} 97 | release_id: ${{steps.current-release.outputs.id }} 98 | verbose: true 99 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | # Controls when the action will run. 4 | on: 5 | # Triggers the workflow on push or pull request events but only for the master branch 6 | push: 7 | branches: [master] 8 | tags-ignore: ["**"] 9 | pull_request: 10 | branches: [master] 11 | tags-ignore: ["**"] 12 | 13 | # Allows you to run this workflow manually from the Actions tab 14 | workflow_dispatch: 15 | 16 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 17 | jobs: 18 | # This workflow contains a single job called "ci" 19 | ci: 20 | # The type of runner that the job will run on 21 | runs-on: ubuntu-latest 22 | env: 23 | CHOOSENIM_CHOOSE_VERSION: stable 24 | CHOOSENIM_NO_ANALYTICS: 1 25 | 26 | # Steps represent a sequence of tasks that will be executed as part of the job 27 | steps: 28 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 29 | - uses: actions/checkout@v2 30 | 31 | - name: install musl-gcc 32 | run: sudo apt-get install -y musl-tools 33 | 34 | - name: Update $PATH 35 | run: echo "$HOME/.nimble/bin" >> $GITHUB_PATH 36 | 37 | - name: Install Nim 38 | run: | 39 | curl https://nim-lang.org/choosenim/init.sh -sSf > init.sh 40 | sh init.sh -y 41 | - name: Build 42 | run: | 43 | nimble build -v --gc:orc --opt:size --deepcopy:on -d:release --gcc.exe:musl-gcc --gcc.linkerexe:musl-gcc 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | nimcache/ 2 | nimblecache/ 3 | htmldocs/ 4 | mn.exe 5 | mn_v* 6 | *.htm 7 | mn 8 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/*.md -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Fabio Cevasco 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Mn_DeveloperGuide.md: -------------------------------------------------------------------------------- 1 | % mn Language Developer Guide 2 | % Fabio Cevasco 3 | % - 4 | 5 | 42 | 43 | ## About mn 44 | 45 | {@ docs/about.md || 1 @} 46 | 47 | ## Get Started 48 | 49 | {@ docs/get-started.md || 1 @} 50 | 51 | ## Learning the mn Language 52 | 53 | {@ docs/learn.md || 1 @} 54 | 55 | ### Data Types 56 | 57 | {@ docs/learn-data-types.md || 2 @} 58 | 59 | ### Operators 60 | 61 | {@ docs/learn-operators.md || 2 @} 62 | 63 | ### Definitions 64 | 65 | {@ docs/learn-definitions.md || 2 @} 66 | 67 | ### Control Flow 68 | 69 | {@ docs/learn-control-flow.md || 2 @} 70 | 71 | ## Extending mn 72 | 73 | {@ docs/learn-extending.md || 1 @} 74 | 75 | ## Reference 76 | 77 | {@ docs/reference.md || 1 @} 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 |

4 | A truly minimal
concatenative programming language
5 |

6 | 7 | 8 | 9 |

10 | 11 | --- 12 | 13 | ► For more info, go to . 14 | 15 | -------------------------------------------------------------------------------- /build: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | nim c -d:release --opt:size --gc:orc --deepcopy:on mn 4 | -------------------------------------------------------------------------------- /description: -------------------------------------------------------------------------------- 1 | A truly minimal concatenative programming language. 2 | -------------------------------------------------------------------------------- /docs/_defs_.md: -------------------------------------------------------------------------------- 1 | {{q => [quot](class:kwd)}} 2 | {{q1 => [quot1](class:kwd)}} 3 | {{q2 => [quot2](class:kwd)}} 4 | {{q3 => [quot3](class:kwd)}} 5 | {{q4 => [quot4](class:kwd)}} 6 | {{1 => [1](class:kwd)}} 7 | {{2 => [2](class:kwd)}} 8 | {{3 => [3](class:kwd)}} 9 | {{4 => [4](class:kwd)}} 10 | {{flt => [flt](class:kwd)}} 11 | {{i => [int](class:kwd)}} 12 | {{i1 => [int1](class:kwd)}} 13 | {{i2 => [int2](class:kwd)}} 14 | {{i3 => [int3](class:kwd)}} 15 | {{n => [num](class:kwd)}} 16 | {{n1 => [num1](class:kwd)}} 17 | {{n2 => [num2](class:kwd)}} 18 | {{n3 => [num3](class:kwd)}} 19 | {{any => [a](class:kwd)}} 20 | {{a1 => [a1](class:kwd)}} 21 | {{a2 => [a2](class:kwd)}} 22 | {{a3 => [a3](class:kwd)}} 23 | {{a0p => [a\*](class:kwd)}} 24 | {{s0p => [str\*](class:kwd)}} 25 | {{s => [str](class:kwd)}} 26 | {{s1 => [str1](class:kwd)}} 27 | {{s2 => [str2](class:kwd)}} 28 | {{s3 => [str3](class:kwd)}} 29 | {{s4 => [str4](class:kwd)}} 30 | {{b => [bool](class:kwd)}} 31 | {{b1 => [bool1](class:kwd)}} 32 | {{b2 => [bool2](class:kwd)}} 33 | {{b3 => [bool3](class:kwd)}} 34 | {{01 => [?](class:kwd)}} 35 | {{0p => [\*](class:kwd)}} 36 | {{1p => [\+](class:kwd)}} 37 | {{sl => ['sym](class:kwd)}} 38 | {{sl1 => ['sym1](class:kwd)}} 39 | {{sl2 => ['sym2](class:kwd)}} 40 | {{sym => [sym](class:kwd)}} 41 | {{f => [false](class:kwd)}} 42 | {{t => [true](class:kwd)}} 43 | {{null => [null](class:kwd)}} 44 | {{none => ∅}} 45 | {{m => _mn_}} 46 | 47 | {#op => 48 | 49 | ## $1 50 | 51 | > %symbol% 52 | > [ $2 **⇒** $3](class:kwd) 53 | > 54 | > $4 55 | #} 56 | 57 | {#op => 58 | 59 | [$1](class:reference-title) 60 | 61 | > %symbol% 62 | > [ $2 **⇒** $3](class:kwd) 63 | > 64 | > $4 65 | #} 66 | 67 | {# link-symbol => [$1](#mn-symbol-id-$1) #} 68 | 69 | -------------------------------------------------------------------------------- /docs/about.md: -------------------------------------------------------------------------------- 1 | ----- 2 | content-type: "page" 3 | title: "About" 4 | ----- 5 | {@ _defs_.md || 0 @} 6 | 7 | {{m}} is a concatenative, fully-homoiconic, functional, interpreted programming language. 8 | 9 | This basically means that: 10 | 11 | * It is based on a somewhat obscure and slightly unintuitive programming paradigm, think of [Forth](http://www.forth.org/), [Factor](http://factorcode.org/) and [Joy](http://www.kevinalbrecht.com/code/joy-mirror/) but with parentheses for an extra [Lisp](https://common-lisp.net/)y flavor. 12 | * Programs written in {{m}} are actually written using *quotations*, i.e. lists. 13 | * It comes with map, filter, find, and loads of other functional goodies. 14 | * It is probably slower than the average production-ready programming language. 15 | 16 | ## Why? 17 | 18 | {{m}} is [min](https://min-lang.org)'s little brother. When I started implementing min, I wanted to create a small but practical programming language you could use for shell scripting and perform common tasks. As more feature requests piled in, I noticed it slowly became more and more comprehensive and _batteries-included_: I slowly swapped small, less-unknown and somewhat quirky libraries used for regular expressions, compression etc. with more complete and well-known ones, added HTTPS support (and OpenSSL), improved runtime checks when creating symbols, enhanced the type system, and so on. While min can now be used on its own to create quite complex programs, it became less minimal than originally intended. 19 | 20 | I tried to add compilation variants to reduce the modules to include but that made it more difficult to maintain and still included complex constructs like dictionaries and the full type system, so one day I decided to... fork it! And that's how {{m}} was born. 21 | 22 | Is {{m}} the *successor* of min? No! As I said, it is min's little brother, and it has its own (somewhat more minimalist) life. If you want to create a quick script to glue some shell commands together, then {{m}} is definitely the fastest way to do so. If you want to use the concatenative paradigm to create more complex applications, then min comes with a much bigger toolbox. 23 | 24 | ## How? 25 | 26 | {{m}} is developed entirely in [Nim](https://nim-lang.org) and started off as a fork of the [min](https://min-lang.org) programming language. I took the v0.35.0 codebase and started removing stuff, including the only vowel used in the language name. What else was removed you ask? Let's see... compared to min, {{m}}: 27 | 28 | * does not have dictionaries 29 | * does not have modules 30 | * does not have **require**, **include**, etc. 31 | * does not support compilation via Nim 32 | * does not have sigils 33 | * does not have an **operator** symbol, only **lambda** 34 | * does not have any dependency from third-party code 35 | * does not have type classes or type expressions, except for unions of basic types 36 | * does not have JSON interoperability 37 | * does not have error handling, i.e. a try/catch mechanism 38 | * does not have any built-in support for networking, cryptography, etc. 39 | * does not have a fancy REPL with autocompletion 40 | 41 | What *does* it have then? Well, {{m}} provides: 42 | 43 | * exactly 72 symbols, nearly all of which are borrowed from min 44 | * file reading/writing (via the {#link-symbol||read#} and {#link-symbol||write#} symbols) 45 | * stdin reading ({#link-symbol||gets#}) and writing ({#link-symbol||puts#}) 46 | * external command execution via {#link-symbol||run#} and automatic command expansion for all strings wrapped in square brackets 47 | * string evaluation via {#link-symbol||eval#} 48 | * string interpolation via {#link-symbol||interpolate#} 49 | * a basic REPL 50 | 51 | ## Who? 52 | 53 | {{m}} was created and implemented by [Fabio Cevasco](https://cevasco.org). 54 | 55 | ## When? 56 | 57 | {{m}} source code [repository](https://github.com/h3rald/mn) was created on March 23^rd 2021. 58 | -------------------------------------------------------------------------------- /docs/get-started.md: -------------------------------------------------------------------------------- 1 | ----- 2 | content-type: "page" 3 | title: "Get Started" 4 | ----- 5 | {@ _defs_.md || 0 @} 6 | 7 | You can download one of the following pre-built {{m}} binaries: 8 | 9 | - {#release||{{$version}}||macosx||macOS||x64#} 10 | - {#release||{{$version}}||windows||Windows||x64#} 11 | - {#release||{{$version}}||linux||Linux||x64#} 12 | 13 | {#release -> [mn v$1 for $3 ($4)](https://github.com/h3rald/mn/releases/download/v$1/mn_v$1_$2_$4.zip) #} 14 | 15 | ## Building from source 16 | 17 | Alternatively, you can build {{m}} from source as follows: 18 | 19 | 1. Download and install [Nim](https://nim-lang.org). 20 | 3. Clone the {{m}} [repository](https://github.com/h3rald/mn). 21 | 4. Navigate to the {{m}} repository local folder. 22 | 6. Run **./build.sh**. 23 | 24 | ## Running the mn REPL 25 | 26 | To start the {{m}} REPL, run [mn](class:cmd) with no arguments. You will be presented with a prompt displaying the path to the current directory: 27 | 28 | > %mn-terminal% 29 | > mn v{{$version}} 30 | > [::](class:prompt) 31 | 32 | You can type {{m}} code and press [ENTER](class:kbd) to evaluate it immediately: 33 | 34 | > %mn-terminal% 35 | > [::](class:prompt) 2 2 + 36 | > 4 37 | > [::](class:prompt) 38 | 39 | The result of each operation will be placed on top of the stack, and it will be available to subsequent operation 40 | 41 | > %mn-terminal% 42 | > [::](class:prompt) dup \* 43 | > 16 44 | > [::](class:prompt) 45 | 46 | To exit {{m}} shell, press [CTRL+C](class:kbd) or type [0 exit](class:cmd) and press [ENTER](class:kbd). 47 | 48 | ## Executing an mn Program 49 | 50 | To execute a {{m}} script, you can: 51 | 52 | - Run `mn -e:"... program ..."` to execute a program inline. 53 | - Run `mn myfile.mn` to execute a program contained in a file. 54 | 55 | {{m}} also supports running programs from standard input, so the following command can also be used (on Unix-like system) to run a program saved in [myfile.mn](class:file): 56 | 57 | > %mn-terminal% 58 | > 59 | > [$](class:prompt) cat myfile.mn | mn 60 | -------------------------------------------------------------------------------- /docs/learn-control-flow.md: -------------------------------------------------------------------------------- 1 | ----- 2 | content-type: "page" 3 | title: "Learn: Control Flow" 4 | ----- 5 | {@ _defs_.md || 0 @} 6 | 7 | {{m}} provides some symbols that can be used for the most common control flow statements. Unlike most programming languages, {{m}} does not differentiate between functions and statements -- control flow statements are just ordinary symbols that manipulate the main stack. 8 | 9 | ## Conditionals 10 | 11 | The {#link-symbol||when#} symbol can be used to implement conditional statements. 12 | 13 | For example, consider the following program: 14 | 15 | ( 16 | "Unknown" (system) let 17 | [uname] (uname) let 18 | (uname "MINGW" indexof -1 !=) 19 | ("Windows" (system) bind) 20 | when 21 | (uname "Linux" indexof -1 !=) 22 | ("Linux" (system) bind) 23 | when 24 | (uname "Darwin" indexof -1 !=) 25 | ("macOS" (system) bind) 26 | when 27 | "The current OS is $#" (system) interpolate puts 28 | ) (display-os) lambda 29 | 30 | This program defines a symbol `display-os` that execute the **uname** system command to discover the operating system and outputs a message. 31 | 32 | ## Loops 33 | 34 | The following symbols provide ways to implement common loops: 35 | 36 | * {#link-symbol||foreach#} 37 | * {#link-symbol||while#} 38 | 39 | For example, consider the following program: 40 | 41 | ( 42 | (n) let 43 | 1 (i) let 44 | 1 (f) let 45 | (i n <=) 46 | ( 47 | f i * (f) bind 48 | i 1 + (i) bind 49 | ) while 50 | f 51 | ) (factorial) lambda 52 | 53 | This program defines a symbol `factorial` that calculates the factorial of an integer iteratively using the symbol {#link-symbol||while#}. 54 | -------------------------------------------------------------------------------- /docs/learn-data-types.md: -------------------------------------------------------------------------------- 1 | ----- 2 | content-type: "page" 3 | title: "Learn: Data Types" 4 | ----- 5 | {@ _defs_.md || 0 @} 6 | 7 | 8 | The following data types are availanle in {{m}} (with the corresponding shorthand symbols used in symbol signatures in brackets): 9 | 10 | null (null) 11 | : null value. 12 | boolean (bool) 13 | : **true** or **false**. 14 | integer (int) 15 | : A 64-bit integer number like 1, 27, or -15. 16 | float (flt) 17 | : A 64-bit floating-point number like 3.14 or -56.9876. 18 | string (str) 19 | : A series of characters wrapped in double quotes: "Hello, World!". 20 | quotation (quot) 21 | : A list of elements, which may also contain symbols. Quotations can be used to create heterogenous lists of elements of any data type, and also to create a block of code that will be evaluated later on (quoted program). Example: `(1 2 3 + \*)` 22 | command (cmd) 23 | : A command string wrapped in square brackets that will be immediately executed on the current shell and converted into the command standard output. Example: `[ls -a]` 24 | -------------------------------------------------------------------------------- /docs/learn-definitions.md: -------------------------------------------------------------------------------- 1 | ----- 2 | content-type: "page" 3 | title: "Learn: Definitions" 4 | ----- 5 | {@ _defs_.md || 0 @} 6 | 7 | Being a concatenative language, {{m}} does not really need named parameters or variables: symbols just pop elements off the main stack in order, and that's normally enough. There is however one small problem with the traditional concatenative paradigm; consider the following program for example: 8 | 9 | dup 10 | () cons "Compiling in $# mode..." swap interpolate puts pop 11 | () cons "nim -d:$# c test.nim" swap interpolate run 12 | 13 | This program takes an string containing either "release" or "development" and attempts to build the file **test.nim** for it. Sure, it is remarkable that no variables are needed for such a program, but it is not very readable: because no variables are used, it is often necessary to make copies of elements and push them to the end of the stack -- that's what the {#link-symbol||dup#} and {#link-symbol||swap#} are used for. 14 | 15 | The good news is that you can use the {#link-symbol||let#} symbol to define new symbols, and symbols can also be set to literals of course. 16 | 17 | Consider the following program: 18 | 19 | (mode) let 20 | "Compiling in $# mode..." (mode) interpolate puts pop 21 | "nim -d:$# c test.nim" (mode) interpolate run 22 | 23 | In this case, the first element on the stack is saved to a symbol called **mode**, which is then used whenever needed in the rest of the program. 24 | 25 | 26 | ## Lexical scoping and binding 27 | 28 | mn, like many other programming languages, uses [lexical scoping](https://en.wikipedia.org/wiki/Scope_\(computer_science\)#Lexical_scope_vs._dynamic_scope) to resolve symbols. 29 | 30 | Consider the following program: 31 | 32 | 33 | 4 (a) let 34 | ( 35 | a 3 + (a) let 36 | ( 37 | a 1 + (a) let 38 | (a dup * (a) let) dequote 39 | ) dequote 40 | ) dequote 41 | 42 | ...What is the value of the symbol `a` after executing it? 43 | 44 | Simple: `4`. Every quotation defines its own scope, and in each scope, a new variable called `a` is defined. In the innermost scope containing the quotation `(a dup * (a) let)` the value of `a` is set to `64`, but this value is not propagated to the outer scopes. Note also that the value of `a` in the innermost scope is first retrieved from the outer scope (8). 45 | 46 | If we want to change the value of the original `a` symbol defined in the outermost scope, we have to use the {#link-symbol||bind#}, so that the program becomes the following: 47 | 48 | 4 (a) let ;First definition of the symbol a 49 | ( 50 | a 3 + (a) bind ;The value of a is updated to 7. 51 | ( 52 | a 1 + (a) bind ;The value of a is updated to 8 53 | (a dup * (a) bind) dequote ;The value of a is now 64 54 | ) dequote 55 | ) dequote 56 | -------------------------------------------------------------------------------- /docs/learn-extending.md: -------------------------------------------------------------------------------- 1 | ----- 2 | content-type: "page" 3 | title: "Learn: Extending mn" 4 | ----- 5 | {@ _defs_.md || 0 @} 6 | 7 | {{m}} provides a fairly very basic standard library. If you want to extend it, you basically have the following options: 8 | 9 | * Implementing new {{m}} symbols using {{m}} itself 10 | * Embedding {{m}} in your [Nim](https://nim-lang.org) program 11 | 12 | ## Implementing new mn symbols using mn itself 13 | 14 | When you just want to create more high-level {{m}} symbol using functionalities that are already available in mn, the easiest way is to create your own reusable {{m}} symbols in separate files. 15 | 16 | 17 | ``` 18 | (dup *) (pow2) lambda 19 | (dup dup * *) (pow3) lambda 20 | (dup dup dup * * *) (pow4) lambda 21 | 22 | ``` 23 | 24 | Save your code to a file (e.g. *quickpows.mn*) and you can use it in other nim files using the {#link-symbol||read#} symbol to read it and then the {#link-symbol||eval#} to evaluate the program in the current scope: 25 | 26 | ``` 27 | "quickpows.mn" read eval 28 | 29 | 2 pow3 pow2 puts ;prints 64 30 | ``` 31 | 32 | ## Embedding mn in your Nim program 33 | 34 | If you'd like to use {{m}} as a scripting language within your own program, and maybe extend it by implementing additional symbols, you can use {{m}} as a Nim library. 35 | 36 | To do so: 37 | 38 | 1. Download and install [Nim](https://nim-lang.org) 39 | 2. Import it in your Nim file. 40 | 3. Implement a new `proc` to define the module. 41 | 42 | The following code is adapted from [HastySite](https://github.com/h3rald/hastysite) (which internally uses [min](https://min-lang.org)) and shows how to define a new `hastysite` module containing some symbols (`preprocess`, `postprocess`, `process-rules`, ...): 43 | 44 | ``` 45 | import mn 46 | 47 | proc hastysite_module*(i: In, hs1: HastySite) = 48 | var hs = hs1 49 | let def = i.define() 50 | 51 | def.symbol("preprocess") do (i: In): 52 | hs.preprocess() 53 | 54 | def.symbol("postprocess") do (i: In): 55 | hs.postprocess() 56 | 57 | def.symbol("process-rules") do (i: In): 58 | hs.interpret(hs.files.rules) 59 | 60 | # ... 61 | 62 | def.finalize("hastysite") 63 | ``` 64 | 65 | Then you need to: 66 | 67 | 4. Instantiate a new {{m}} interpreter using the `newMnInterpreter` proc. 68 | 5. Run the `proc` used to define the module. 69 | 6. Call the `interpret` method to interpret a {{m}} file or string: 70 | 71 | ``` 72 | proc interpret(hs: HastySite, file: string) = 73 | var i = newMnInterpreter(file, file.parentDir) 74 | i.hastysite_module(hs) 75 | i.interpret(newFileStream(file, fmRead)) 76 | ``` 77 | 78 | > %tip% 79 | > Tip 80 | > 81 | > For more information on how to create new symbols with Nim, have a look in the [lang.nim](https://github.com/h3rald/mn/tree/master/mnpkg/lang.nim) file in the {{m}} repository, which contains all the symbols included in {{m}}. 82 | -------------------------------------------------------------------------------- /docs/learn-operators.md: -------------------------------------------------------------------------------- 1 | ----- 2 | content-type: "page" 3 | title: "Learn: Operators" 4 | ----- 5 | {@ _defs_.md || 0 @} 6 | 7 | Every {{m}} program needs _operators_ to: 8 | 9 | * Manipulate elements on the stack 10 | * Perform operations on data 11 | * Provide side effects (read/print to standard input/output/files, etc.) 12 | 13 | An {{m}} symbol is a single word that is either provided by {{m}} like `dup` or defined by the user. User-defined symbols must: 14 | 15 | * Start with a letter 16 | * Contain zero or more letters, numbers and/or underscores. 17 | 18 | To define a new operator symbol, you can use the {#link-symbol||lambda#} symbol. For example, the following symbol defines a quotation that can be used to calculate the square value of a number. 19 | 20 | (dup *) (square) lambda 21 | 22 | Note that this feels like using {#link-symbol||let#}, but the main difference between {#link-symbol||lambda#} and {#link-symbol||let#} is that `lambda` only works on quotations and it doesn't auto-quote them, so that they are immediately evaluated when the corresponding symbol is pushed on the stack. 23 | 24 | > %tip% 25 | > Tip 26 | > 27 | > You can use {#link-symbol||lambda-bind#} to re-set a previously set lambda. 28 | -------------------------------------------------------------------------------- /docs/learn.md: -------------------------------------------------------------------------------- 1 | ----- 2 | content-type: "page" 3 | title: "Learn" 4 | ----- 5 | {@ _defs_.md || 0 @} 6 | 7 | {{m}} is a stack-based, concatenative programming language that uses postfix notation. If you already know [Forth](http://www.forth.org/), [Factor](http://factorcode.org/) or [Joy](http://www.kevinalbrecht.com/code/joy-mirror/), or if you ever used an [RPN](https://en.wikipedia.org/wiki/Reverse_Polish_notation) calculator, then {{m}} will look somewhat familiar to you. 8 | 9 | If not, well, here's how a short {{m}} program looks like: 10 | 11 | ; This is a comment 12 | (1 2 3 4 5) (dup *) map 13 | #| This is a... 14 | ...multiline comment |# 15 | 16 | This program returns a list containing the square values of the first five integer numbers: 17 | 18 | (1 4 9 16 25) 19 | 20 | Let's see how it works: 21 | 22 | 1. First, a list containing the first five integers is pushed on the stack. 23 | 2. Then, another list containing two symbols (`dup` and `*`) is pushed on the stack. This constitutes a quoted program which, when executed duplicates the first element on the stack — this is done by `dup`— and then multiplies — with `*`— the two elements together. 24 | 3. Finally, the symbol `map` is pushed on the stack. Map takes a list of elements and a quoted program and applies the program to each element. 25 | 26 | Note that: 27 | 28 | * There are no variable assignments. 29 | * elements are pushed on the stack one by one. 30 | * Parentheses are used to group one or more elements together so that they are treated as a single element and they are not evaluated immediately. 31 | * *Symbols* (typically single words, or several words joined by dashes) are used to execute code that performs operations on the whole stack. 32 | 33 | Unlike more traditional programming languages, in a concatenative programming language, there is no inherent need for variables or named parameters, as symbols act as stack symbols that consume elements that are placed in order on top of a stack. 34 | -------------------------------------------------------------------------------- /docs/reference.md: -------------------------------------------------------------------------------- 1 | ----- 2 | content-type: "page" 3 | title: "Reference" 4 | ----- 5 | {@ _defs_.md || 0 @} 6 | 7 | ## Notation 8 | 9 | The following notation is used in the signature of all {{m}} symbols: 10 | 11 | ### Types and Values 12 | 13 | {{none}} 14 | : No value. 15 | {{null}} 16 | : null value 17 | {{any}} 18 | : A value of any type. 19 | {{b}} 20 | : A boolean value 21 | {{i}} 22 | : An integer value. 23 | {{flt}} 24 | : A float value. 25 | {{n}} 26 | : A numeric (integer or float) value. 27 | {{s}} 28 | : A string value. 29 | {{sl}} 30 | : A string-like value (string or quoted symbol). 31 | {{q}} 32 | : A quotation (also expressed as parenthesis enclosing other values). 33 | 34 | ### Suffixes 35 | 36 | The following suffixes can be placed at the end of a value or type to indicate ordering or quantities. 37 | 38 | {{1}} 39 | : The first value of the specified type. 40 | {{2}} 41 | : The second value of the specified type. 42 | {{3}} 43 | : The third value of the specified type. 44 | {{4}} 45 | : The fourth value of the specified type. 46 | {{01}} 47 | : Zero or one. 48 | {{0p}} 49 | : Zero or more. 50 | {{1p}} 51 | : One or more 52 | 53 | ## Symbols 54 | 55 | {#op||>||{{a1}} {{a2}}||{{b}}|| 56 | > Returns {{t}} if {{a1}} is greater than {{a2}}, {{f}} otherwise. 57 | > > %note% 58 | > > Note 59 | > > 60 | > > Only comparisons among two numbers or two strings are supported.#} 61 | 62 | {#op||>=||{{a1}} {{a2}}||{{b}}|| 63 | > Returns {{t}} if {{a1}} is greater than or equal to {{a2}}, {{f}} otherwise. 64 | > > %note% 65 | > > Note 66 | > > 67 | > > Only comparisons among two numbers or two strings are supported.#} 68 | 69 | {#op||<||{{a1}} {{a2}}||{{b}}|| 70 | > Returns {{t}} if {{a1}} is smaller than {{a2}}, {{f}} otherwise. 71 | > > %note% 72 | > > Note 73 | > > 74 | > > Only comparisons among two numbers or two strings are supported.#} 75 | 76 | {#op||<=||{{a1}} {{a2}}||{{b}}|| 77 | > Returns {{t}} if {{a1}} is smaller than or equal to {{a2}}, {{f}} otherwise. 78 | > > %note% 79 | > > Note 80 | > > 81 | > > Only comparisons among two numbers or two strings are supported.#} 82 | 83 | {#op||==||{{a1}} {{a2}}||{{b}}|| 84 | Returns {{t}} if {{a1}} is equal to {{a2}}, {{f}} otherwise. #} 85 | 86 | {#op||!=||{{a1}} {{a2}}||{{b}}|| 87 | Returns {{t}} if {{a1}} is not equal to {{a2}}, {{f}} otherwise. #} 88 | 89 | {#op||&&||{{q}}||{{b}}|| 90 | Assuming that {{q}} is a quotation of quotations each evaluating to a boolean value, it pushes {{t}} on the stack if they all evaluate to {{t}}, {{f}} otherwise. #} 91 | 92 | {#op|| \|\| ||{{q}}||{{b}}|| 93 | Assuming that {{q}} is a quotation of quotations each evaluating to a boolean value, it pushes {{t}} on the stack if any evaluates to {{t}}, {{f}} otherwise. 94 | #} 95 | 96 | {#op||!||{{b1}}||{{b2}}|| 97 | Negates {{b1}}.#} 98 | 99 | {#op||+||{{n1}} {{n2}}||{{n3}}|| 100 | Sums {{n1}} and {{n2}}. #} 101 | 102 | {#op||-||{{n1}} {{n2}}||{{n3}}|| 103 | Subtracts {{n2}} from {{n1}}. #} 104 | 105 | {#op||-inf||{{none}}||{{n}}|| 106 | Returns negative infinity. #} 107 | 108 | {#op||*||{{n1}} {{n2}}||{{n3}}|| 109 | Multiplies {{n1}} by {{n2}}. #} 110 | 111 | {#op||/||{{n1}} {{n2}}||{{n3}}|| 112 | Divides {{n1}} by {{n2}}. #} 113 | 114 | {#op||+inf||{{none}}||{{n}}|| 115 | Returns infinity. #} 116 | 117 | {#op||nan||{{none}}||nan|| 118 | Returns **NaN** (not a number). #} 119 | 120 | {#op||append||{{s1}} {{s2}}||{{none}}|| 121 | Appends {{s1}} to the end of file {{s2}}. #} 122 | 123 | {#op||apply||{{q}}||({{a0p}})|| 124 | Returns a new quotation obtained by evaluating each element of {{q}} in a separate stack. #} 125 | 126 | {#op||args||{{none}}||{{q}}|| 127 | Returns a list of all arguments passed to the current program.#} 128 | 129 | {#op||bind||{{any}} {{sl}}||{{none}}|| 130 | Binds the specified value (auto-quoted) to an existing symbol {{sl}}.#} 131 | 132 | {#op||concat||{{q1}} {{q2}}||{{q3}}|| 133 | Concatenates {{q1}} with {{q2}}. #} 134 | 135 | {#op||cons||{{a1}} ({{a0p}})||({{a1}} {{a0p}})|| 136 | Prepends {{a1}} to the quotation on top of the stack.#} 137 | 138 | {#op||cpu||{{none}}||{{s}}|| 139 | Returns the host CPU. It can be one of the following strings i386, alpha, powerpc, powerpc64, powerpc64el, sparc, amd64, mips, mipsel, arm, arm64. #} 140 | 141 | {#op||dip||{{a1}} ({{a2}})||{{a0p}} {{a1}}|| 142 | Removes the first and second element from the stack, dequotes the first element, and restores the second element.#} 143 | 144 | {#op||dup||{{a1}}||{{a1}} {{a1}}|| 145 | Duplicates the first element on the stack.#} 146 | 147 | {#op||dequote||{{q}}||{{a0p}}|| 148 | > Pushes the contents of quotation {{q}} on the stack. 149 | > 150 | > Each element is pushed on the stack one by one. If any error occurs, {{q}} is restored on the stack.#} 151 | 152 | {#op||eval||{{s}}||{{a0p}}|| 153 | Parses and interprets {{s}}. #} 154 | 155 | {#op||exit||{{i}}||{{none}}|| 156 | Exits the program or shell with {{i}} as return code. #} 157 | 158 | {#op||expect||{{q1}}||{{q2}}|| 159 | > Validates the first _n_ elements of the stack against the type descriptions specified in {{q1}} (_n_ is {{q1}}'s length) and if all the elements are valid returns them wrapped in {{q2}} (in reverse order). 160 | 161 | > > %tip% 162 | > > Tip 163 | > > 164 | > > You can specify two or more matching types by separating combined together in a type union, e.g.: `string|quot` 165 | 166 | > > %sidebar% 167 | > > Example 168 | > > 169 | > > Assuming that the following elements are on the stack (from top to bottom): 170 | > > 171 | > > `1 "test" 3.4` 172 | > > 173 | > > the following program evaluates to `true`: 174 | > > 175 | > > `(int string num) expect (3.4 "test" 1) ==`#} 176 | 177 | {#op||filter||{{q1}} {{q2}}||{{q3}}|| 178 | > Returns a new quotation {{q3}} containing all elements of {{q1}} that satisfy predicate {{q2}}. 179 | > 180 | > > %sidebar% 181 | > > Example 182 | > > 183 | > > The following program leaves `(34 2 6 8 12)` on the stack: 184 | > > 185 | > > (1 37 34 2 6 8 12 21) 186 | > > (2 / 0 ==) filter #} 187 | 188 | {#op||foreach||{{q1}} {{q2}}||{{a0p}}|| 189 | Applies the quotation {{q2}} to each element of {{q1}}.#} 190 | 191 | {#op||get||{{q}} {{i}}||{{any}}|| 192 | Returns the _n^th_ element of {{q}} (zero-based).#} 193 | 194 | {#op||gets||{{none}}||{{s}}|| 195 | Reads a line from STDIN and places it on top of the stack as a string.#} 196 | 197 | {#op||getstack||{{none}}||({{a0p}})|| 198 | Puts a quotation containing the contents of the stack on the stack.#} 199 | 200 | {#op||indexof||{{s1}} {{s2}}||{{i}}|| 201 | If {{s2}} is contained in {{s1}}, returns the index of the first match or -1 if no match is found. #} 202 | 203 | {#op||interpolate||{{s}} {{q}}||{{s}}|| 204 | > Substitutes the placeholders included in {{s}} with the values in {{q}}. 205 | > > %note% 206 | > > Notes 207 | > > 208 | > > * If {{q}} contains symbols or quotations, they are interpreted. 209 | > > * You can use the `$#` placeholder to indicate the next placeholder that has not been already referenced in the string. 210 | > > * You can use named placeholders like `$pwd`, but in this case {{q}} must contain a quotation containing both the placeholder names (odd items) and the values (even items). 211 | > 212 | > > %sidebar% 213 | > > Example 214 | > > 215 | > > The following code (executed in a directory called '/Users/h3rald/Development/mn' containing 15 files and directories): 216 | > > 217 | > > `"Directory '$1' includes $2 items." ([pwd] ([ls] "\n" split size)) interpolate` 218 | > > 219 | > > produces: 220 | > > 221 | > > `"Directory '/Users/h3rald/Development/mn' includes 15 items."`#} 222 | 223 | {#op||join||{{q}} {{sl}}||{{s}}|| 224 | Joins the elements of {{q}} using separator {{sl}}, producing {{s}}.#} 225 | 226 | {#op||lambda||{{q}} {{sl}}||{{none}}|| 227 | Defines a new symbol {{sl}}, containing the specified quotation {{q}}. Unlike with `let`, in this case {{q}} will not be quoted, so its values will be pushed on the stack when the symbol {{sl}} is pushed on the stack. 228 | 229 | Essentially, this symbol allows you to define an symbol without any validation of constraints and bind it to a symbol.#} 230 | 231 | {#op||lambdabind||{{q}} {{sl}}||{{none}}|| 232 | Binds the specified quotation (unquoted) to an existing symbol {{sl}}.#} 233 | 234 | {#op||length||{{sl}}||{{i}}|| 235 | Returns the length of {{sl}}.#} 236 | 237 | {#op||let||{{any}} {{sl}}||{{none}}|| 238 | Defines a new symbol {{sl}}, containing the specified value.#} 239 | 240 | {#op||map||{{q1}} {{q2}}||{{q3}}|| 241 | Returns a new quotation {{q3}} obtained by applying {{q2}} to each element of {{q1}}.#} 242 | 243 | {#op||os||{{none}}||{{s}}|| 244 | Returns the host operating system. It can be one of the following strings: windows, macosx, linux, netbsd, freebsd, openbsd, solaris, aix, standalone. #} 245 | 246 | {#op||pop||{{any}}||{{none}}|| 247 | Removes the first element from the stack.#} 248 | 249 | {#op||print||{{any}}||{{any}}|| 250 | Prints {{any}} to STDOUT.#} 251 | 252 | {#op||puts||{{any}}||{{any}}|| 253 | Prints {{any}} and a new line to STDOUT.#} 254 | 255 | {#op||quote||{{any}}||({{any}})|| 256 | Wraps {{any}} in a quotation. #} 257 | 258 | {#op||quotesym||{{s}}||({{sym}})|| 259 | Creates a symbol with the value of {{s}} and wraps it in a quotation. #} 260 | 261 | {#op||quotecmd||{{s}}||({{sym}})|| 262 | Creates a command with the value of {{s}} and wraps it in a quotation. #} 263 | 264 | {#op||read||{{s}}||{{s}}|| 265 | Reads the file {{s}} and puts its contents on the top of the stack as a string.#} 266 | 267 | {#op||replace||{{s1}} {{s2}} {{s3}}||{{s4}}|| 268 | > Returns a copy of {{s1}} containing all occurrences of {{s2}} replaced by {{s3}} #} 269 | 270 | {#op||run||{{sl}}||{{i}}|| 271 | Executes the external command {{sl}} in the current directory and pushes its return code on the stack. #} 272 | 273 | {#op||setstack||{{q}}||{{a0p}}|| 274 | Substitute the existing stack with the contents of {{q}}.#} 275 | 276 | {#op||swap||{{a1}} {{a2}}||{{a2}} {{a1}}|| 277 | Swaps the first two elements on the stack. #} 278 | 279 | {#op||size||{{q}}||{{i}}|| 280 | Returns the length of {{q}}.#} 281 | 282 | {#op||slice||{{q1}} {{i1}} {{i2}}||{{q2}}|| 283 | > Creates a new quotation {{q2}} obtaining by selecting all elements of {{q1}} between indexes {{i1}} and {{i2}}. 284 | > 285 | > > %sidebar% 286 | > > Example 287 | > > 288 | > > The following program leaves `(3 4 5)` on the stack: 289 | > > 290 | > > (1 2 3 4 5 6) 291 | > > 2 4 slice #} 292 | 293 | {#op||split||{{sl1}} {{sl2}}||{{q}}|| 294 | Splits {{sl1}} using separator {{sl2}} and returns the resulting strings within the quotation {{q}}. #} 295 | 296 | {#op||strip||{{sl}}||{{s}}|| 297 | Returns {{s}}, which is set to {{sl}} with leading and trailing spaces removed.#} 298 | 299 | {#op||substr||{{s1}} {{i1}} {{i2}}||{{s2}}|| 300 | Returns a substring {{s2}} obtained by retriving {{i2}} characters starting from index {{i1}} within {{s1}}.#} 301 | 302 | {#op||symbols||{{none}}||({{s0p}})|| 303 | Returns a list of all symbols defined in the global scope.#} 304 | 305 | {#op||timestamp||{{none}}||{{i}}|| 306 | Returns the current time as Unix timestamp. #} 307 | 308 | {#op||type||{{any}}||{{s}}|| 309 | Puts the data type of {{any}} on the stack.#} 310 | 311 | {#op||when||{{q1}} {{q2}}||{{a0p}}|| 312 | If {{q1}} evaluates to {{t}} then evaluates {{q2}}.#} 313 | 314 | {#op||which||{{sl}}||{{s}}|| 315 | Returns the full path to the directory containing executable {{sl}}, or an empty string if the executable is not found in **$PATH**. #} 316 | 317 | {#op||while||{{q1}} {{q2}}||{{a0p}}|| 318 | > Executes {{q2}} while {{q1}} evaluates to {{t}}. 319 | > 320 | > > %sidebar% 321 | > > Example 322 | > > 323 | > > The following program prints all natural numbers from 0 to 10: 324 | > > 325 | > > 0 (count) let 326 | > > (count 10 <=) 327 | > > (count puts 1 + (count) bind) while #} 328 | 329 | {#op||write||{{s1}} {{s2}}||{{none}}|| 330 | Writes {{s1}} to the file {{s2}}, erasing all its contents first. #} 331 | -------------------------------------------------------------------------------- /logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 29 | 49 | 50 | 55 | 60 | 65 | 70 | 75 | 76 | -------------------------------------------------------------------------------- /mn.nim: -------------------------------------------------------------------------------- 1 | import 2 | streams, 3 | strutils, 4 | os, 5 | mnpkg/parser, 6 | mnpkg/value, 7 | mnpkg/scope, 8 | mnpkg/interpreter, 9 | mnpkg/utils, 10 | mnpkg/lang 11 | 12 | export 13 | parser, 14 | interpreter, 15 | utils, 16 | value, 17 | scope, 18 | lang 19 | 20 | proc stdLib*(i: In) = 21 | i.lang_module 22 | 23 | proc interpret*(i: In, s: Stream) = 24 | i.stdLib() 25 | i.open(s, i.filename) 26 | discard i.parser.getToken() 27 | try: 28 | i.interpret() 29 | except CatchableError: 30 | i.error(getCurrentExceptionMsg()) 31 | i.close() 32 | 33 | proc interpret*(i: In, s: string): MnValue = 34 | i.open(newStringStream(s), i.filename) 35 | discard i.parser.getToken() 36 | try: 37 | result = i.interpret() 38 | except CatchableError: 39 | i.error(getCurrentExceptionMsg()) 40 | i.close() 41 | 42 | proc mnFile*(filename: string, op = "interpret", main = true): seq[string] {.discardable.} 43 | 44 | proc mnStream(s: Stream, filename: string, op = "interpret", main = true): seq[string] {.discardable.}= 45 | var i = newMinInterpreter(filename = filename) 46 | i.pwd = filename.parentDir 47 | i.interpret(s) 48 | newSeq[string](0) 49 | 50 | proc mnStr*(buffer: string) = 51 | mnStream(newStringStream(buffer), "input") 52 | 53 | proc mnFile*(filename: string, op = "interpret", main = true): seq[string] {.discardable.} = 54 | var fn = filename 55 | if not filename.endsWith(".mn"): 56 | fn &= ".mn" 57 | var fileLines = newSeq[string](0) 58 | var contents = "" 59 | try: 60 | fileLines = fn.readFile().splitLines() 61 | except CatchableError: 62 | stderr.writeLine("Cannot read from file: " & fn) 63 | quit(3) 64 | if fileLines[0].len >= 2 and fileLines[0][0..1] == "#!": 65 | contents = ";;\n" & fileLines[1..fileLines.len-1].join("\n") 66 | else: 67 | contents = fileLines.join("\n") 68 | mnStream(newStringStream(contents), fn, op, main) 69 | 70 | when isMainModule: 71 | import 72 | terminal, 73 | parseopt, 74 | mnpkg/meta 75 | 76 | var exeName = "mn" 77 | 78 | proc printResult(i: In, res: MnValue) = 79 | if res.isNil: 80 | return 81 | if i.stack.len > 0: 82 | let n = $i.stack.len 83 | if res.isQuotation and res.qVal.len > 1: 84 | echo " (" 85 | for item in res.qVal: 86 | echo " " & $item 87 | echo " ".repeat(n.len) & ")" 88 | elif res.isCommand: 89 | echo " [" & res.cmdVal & "]" 90 | else: 91 | echo " $1" % [$i.stack[i.stack.len - 1]] 92 | 93 | proc mnSimpleRepl*(i: var MnInterpreter) = 94 | i.stdLib() 95 | var s = newStringStream("") 96 | i.open(s, "") 97 | var line: string 98 | echo "mn v$#" % pkgVersion 99 | while true: 100 | stdout.write(":: ") 101 | stdout.flushFile() 102 | line = stdin.readLine() 103 | let r = i.interpret($line) 104 | if $line != "": 105 | i.printResult(r) 106 | 107 | proc mnSimpleRepl*() = 108 | var i = newMinInterpreter(filename = "") 109 | i.mnSimpleRepl() 110 | 111 | 112 | let usage* = """ mn v$version - A truly minimal concatenative programming language 113 | (c) 2021 Fabio Cevasco 114 | 115 | Usage: 116 | $exe [options] [filename] 117 | 118 | Arguments: 119 | filename A $exe file to interpret or compile 120 | Options: 121 | -e, --evaluate Evaluate a $exe program inline 122 | -h, --help Print this help 123 | -d, --debug Enable debug messages 124 | -v, —-version Print the program version""" % [ 125 | "exe", exeName, 126 | "version", pkgVersion, 127 | ] 128 | 129 | var file, s: string = "" 130 | var args = newSeq[string](0) 131 | var p = initOptParser() 132 | 133 | for kind, key, val in getopt(p): 134 | case kind: 135 | of cmdArgument: 136 | args.add key 137 | if file == "": 138 | file = key 139 | of cmdLongOption, cmdShortOption: 140 | case key: 141 | of "debug", "d": 142 | DEBUG = true 143 | of "evaluate", "e": 144 | if file == "": 145 | s = val 146 | of "help", "h": 147 | if file == "": 148 | echo usage 149 | quit(0) 150 | of "version", "v": 151 | if file == "": 152 | echo pkgVersion 153 | quit(0) 154 | else: 155 | discard 156 | else: 157 | discard 158 | var op = "interpret" 159 | if s != "": 160 | mnStr(s) 161 | elif file != "": 162 | mnFile file, op 163 | else: 164 | if isatty(stdin): 165 | mnSimpleRepl() 166 | quit(0) 167 | else: 168 | mnStream newFileStream(stdin), "stdin", op -------------------------------------------------------------------------------- /mn.nimble: -------------------------------------------------------------------------------- 1 | import 2 | mnpkg/meta 3 | 4 | # Package 5 | 6 | version = pkgVersion 7 | author = pkgAuthor 8 | description = pkgDescription 9 | license = "MIT" 10 | bin = @[pkgName] 11 | installFiles = @["mn.yml", "mn.nim"] 12 | installDirs = @["mnpkg"] 13 | 14 | # Dependencies 15 | 16 | requires "nim >= 1.6.12" 17 | -------------------------------------------------------------------------------- /mn.nims: -------------------------------------------------------------------------------- 1 | # https://blog.filippo.io/easy-windows-and-linux-cross-compilers-for-macos/ 2 | 3 | switch("amd64.windows.gcc.path", "/usr/local/bin") 4 | switch("amd64.windows.gcc.exe", "x86_64-w64-mingw32-gcc") 5 | switch("amd64.windows.gcc.linkerexe", "x86_64-w64-mingw32-gcc") 6 | 7 | switch("amd64.linux.gcc.path", "/usr/local/bin") 8 | switch("amd64.linux.gcc.exe", "x86_64-linux-musl-gcc") 9 | switch("amd64.linux.gcc.linkerexe", "x86_64-linux-musl-gcc") 10 | 11 | when not defined(dev): 12 | switch("define", "release") 13 | 14 | if findExe("musl-gcc") != "": 15 | switch("gcc.exe", "musl-gcc") 16 | switch("gcc.linkerexe", "musl-gcc") 17 | -------------------------------------------------------------------------------- /mn.yml: -------------------------------------------------------------------------------- 1 | author: Fabio Cevasco 2 | description: A truly minimal concatenative programming language. 3 | name: mn 4 | version: 0.4.0 5 | -------------------------------------------------------------------------------- /mnpkg/interpreter.nim: -------------------------------------------------------------------------------- 1 | import 2 | streams, 3 | strutils, 4 | os, 5 | osproc, 6 | critbits, 7 | algorithm 8 | import 9 | value, 10 | scope, 11 | parser 12 | 13 | type 14 | MnTrappedException* = ref object of CatchableError 15 | MnReturnException* = ref object of CatchableError 16 | MnRuntimeError* = ref object of CatchableError 17 | data*: MnValue 18 | 19 | var DEBUG* {. threadvar .} : bool 20 | DEBUG = false 21 | 22 | proc diff*(a, b: seq[MnValue]): seq[MnValue] = 23 | result = newSeq[MnValue](0) 24 | for it in b: 25 | if not a.contains it: 26 | result.add it 27 | 28 | proc newSym*(i: In, s: string): MnValue = 29 | return MnValue(kind: mnSymbol, symVal: s, filename: i.currSym.filename, line: i.currSym.line, column: i.currSym.column, outerSym: i.currSym.symVal) 30 | 31 | proc copySym*(i: In, sym: MnValue): MnValue = 32 | return MnValue(kind: mnSymbol, symVal: sym.outerSym, filename: sym.filename, line: sym.line, column: sym.column, outerSym: "", docComment: sym.docComment) 33 | 34 | proc raiseRuntime*(msg: string) = 35 | raise MnRuntimeError(msg: msg) 36 | 37 | proc dump*(i: MnInterpreter): string = 38 | var s = "" 39 | for item in i.stack: 40 | s = s & $item & " " 41 | return s 42 | 43 | template withScope*(i: In, res:ref MnScope, body: untyped): untyped = 44 | let origScope = i.scope 45 | try: 46 | i.scope = newScopeRef(origScope) 47 | body 48 | res = i.scope 49 | finally: 50 | i.scope = origScope 51 | 52 | template withScope*(i: In, body: untyped): untyped = 53 | let origScope = i.scope 54 | try: 55 | i.scope = newScopeRef(origScope) 56 | body 57 | finally: 58 | i.scope = origScope 59 | 60 | proc newMinInterpreter*(filename = "input", pwd = ""): MnInterpreter = 61 | var path = pwd 62 | if not pwd.isAbsolute: 63 | path = joinPath(getCurrentDir(), pwd) 64 | var stack:MnStack = newSeq[MnValue](0) 65 | var trace:MnStack = newSeq[MnValue](0) 66 | var stackcopy:MnStack = newSeq[MnValue](0) 67 | var pr:MnParser 68 | var scope = newScopeRef(nil) 69 | var i:MnInterpreter = MnInterpreter( 70 | filename: filename, 71 | pwd: path, 72 | parser: pr, 73 | stack: stack, 74 | trace: trace, 75 | stackcopy: stackcopy, 76 | scope: scope, 77 | currSym: MnValue(column: 1, line: 1, kind: mnSymbol, symVal: "") 78 | ) 79 | return i 80 | 81 | proc copy*(i: MnInterpreter, filename: string): MnInterpreter = 82 | var path = filename 83 | if not filename.isAbsolute: 84 | path = joinPath(getCurrentDir(), filename) 85 | result = newMinInterpreter() 86 | result.filename = filename 87 | result.pwd = path.parentDir 88 | result.stack = i.stack 89 | result.trace = i.trace 90 | result.stackcopy = i.stackcopy 91 | result.scope = i.scope 92 | result.currSym = MnValue(column: 1, line: 1, kind: mnSymbol, symVal: "") 93 | 94 | proc formatError(sym: MnValue, message: string): string = 95 | var name = sym.symVal 96 | return "$1($2,$3) [$4]: $5" % [sym.filename, $sym.line, $sym.column, name, message] 97 | 98 | proc formatTrace(sym: MnValue): string = 99 | var name = sym.symVal 100 | if sym.filename == "": 101 | return " in symbol: $1" % [name] 102 | else: 103 | return "$1($2,$3) in symbol: $4" % [sym.filename, $sym.line, $sym.column, name] 104 | 105 | proc stackTrace*(i: In) = 106 | var trace = i.trace 107 | trace.reverse() 108 | for sym in trace: 109 | echo sym.formatTrace 110 | 111 | proc error*(i: In, message: string) = 112 | stderr.writeLine(i.currSym.formatError(message)) 113 | 114 | proc open*(i: In, stream:Stream, filename: string) = 115 | i.filename = filename 116 | i.parser.open(stream, filename) 117 | 118 | proc close*(i: In) = 119 | i.parser.close(); 120 | 121 | proc push*(i: In, val: MnValue) {.gcsafe.} 122 | 123 | proc apply*(i: In, op: MnOperator, sym = "") {.gcsafe.}= 124 | if op.kind == mnProcOp: 125 | op.prc(i) 126 | else: 127 | if op.val.kind == mnQuotation: 128 | var newscope = newScopeRef(i.scope) 129 | i.withScope(newscope): 130 | for e in op.val.qVal: 131 | if e.isSymbol and e.symVal == sym: 132 | raiseInvalid("Symbol '$#' evaluates to itself" % sym) 133 | i.push e 134 | else: 135 | i.push(op.val) 136 | 137 | proc dequote*(i: In, q: var MnValue) = 138 | if q.kind == mnQuotation: 139 | i.withScope(): 140 | let qqval = deepCopy(q.qVal) 141 | for v in q.qVal: 142 | i.push v 143 | q.qVal = qqval 144 | else: 145 | i.push(q) 146 | 147 | proc apply*(i: In, q: var MnValue) {.gcsafe.}= 148 | var i2 = newMinInterpreter("") 149 | i2.trace = i.trace 150 | i2.scope = i.scope 151 | try: 152 | i2.withScope(): 153 | for v in q.qVal: 154 | if (v.kind == mnQuotation): 155 | var v2 = v 156 | i2.dequote(v2) 157 | else: 158 | i2.push v 159 | except CatchableError: 160 | i.currSym = i2.currSym 161 | i.trace = i2.trace 162 | raise 163 | i.push i2.stack.newVal 164 | 165 | proc pop*(i: In): MnValue = 166 | if i.stack.len > 0: 167 | return i.stack.pop 168 | else: 169 | raiseEmptyStack() 170 | 171 | # Inherit file/line/column from current symbol 172 | proc pushSym*(i: In, s: string) = 173 | i.push MnValue( 174 | kind: mnSymbol, 175 | symVal: s, 176 | filename: i.currSym.filename, 177 | line: i.currSym.line, 178 | column: i.currSym.column, 179 | outerSym: i.currSym.symVal, 180 | docComment: i.currSym.docComment) 181 | 182 | proc push*(i: In, val: MnValue) {.gcsafe.}= 183 | if val.kind == mnSymbol: 184 | if not i.evaluating: 185 | if val.outerSym != "": 186 | i.currSym = i.copySym(val) 187 | else: 188 | i.currSym = val 189 | i.trace.add val 190 | if DEBUG: 191 | echo "-- push symbol: $#" % val.symVal 192 | let symbol = val.symVal 193 | if i.scope.hasSymbol(symbol): 194 | i.apply i.scope.getSymbol(symbol), symbol 195 | else: 196 | raiseUndefined("Undefined symbol '$1'" % [val.symVal]) 197 | discard i.trace.pop 198 | elif val.kind == mnCommand: 199 | if DEBUG: 200 | echo "-- push command: $#" % val.cmdVal 201 | let res = execCmdEx(val.cmdVal) 202 | i.push res.output.strip.newVal 203 | else: 204 | if DEBUG: 205 | echo "-- push literal: $#" % $val 206 | i.stack.add(val) 207 | 208 | proc peek*(i: MnInterpreter): MnValue = 209 | if i.stack.len > 0: 210 | return i.stack[i.stack.len-1] 211 | else: 212 | raiseEmptyStack() 213 | 214 | template handleErrors*(i: In, body: untyped) = 215 | try: 216 | body 217 | except MnRuntimeError: 218 | let msg = getCurrentExceptionMsg() 219 | i.stack = i.stackcopy 220 | stderr.writeLine("$1:$2,$3 $4" % [i.currSym.filename, $i.currSym.line, $i.currSym.column, msg]) 221 | i.stackTrace() 222 | i.trace = @[] 223 | raise MnTrappedException(msg: msg) 224 | except MnTrappedException: 225 | raise 226 | except CatchableError: 227 | let msg = getCurrentExceptionMsg() 228 | i.stack = i.stackcopy 229 | i.stackTrace() 230 | i.trace = @[] 231 | raise MnTrappedException(msg: msg) 232 | 233 | proc interpret*(i: In, parseOnly=false): MnValue {.discardable.} = 234 | var val: MnValue 235 | var q: MnValue 236 | if parseOnly: 237 | q = newSeq[MnValue](0).newVal 238 | while i.parser.token != tkEof: 239 | if i.trace.len == 0: 240 | i.stackcopy = i.stack 241 | handleErrors(i) do: 242 | val = i.parser.parseMinValue(i) 243 | if parseOnly: 244 | q.qVal.add val 245 | else: 246 | i.push val 247 | if parseOnly: 248 | return q 249 | if i.stack.len > 0: 250 | return i.stack[i.stack.len - 1] 251 | 252 | proc eval*(i: In, s: string, name="", parseOnly=false): MnValue {.discardable.}= 253 | var i2 = i.copy(name) 254 | i2.open(newStringStream(s), name) 255 | discard i2.parser.getToken() 256 | result = i2.interpret(parseOnly) 257 | i.trace = i2.trace 258 | i.stackcopy = i2.stackcopy 259 | i.stack = i2.stack 260 | i.scope = i2.scope -------------------------------------------------------------------------------- /mnpkg/lang.nim: -------------------------------------------------------------------------------- 1 | import 2 | critbits, 3 | strutils, 4 | sequtils, 5 | algorithm, 6 | times, 7 | os 8 | import 9 | parser, 10 | value, 11 | interpreter, 12 | utils, 13 | scope 14 | 15 | proc lang_module*(i: In) = 16 | let def = i.scope 17 | 18 | def.symbol("apply") do (i: In): 19 | let vals = i.expect("quot") 20 | var prog = vals[0] 21 | i.apply prog 22 | 23 | def.symbol("expect") do (i: In): 24 | var q: MnValue 25 | i.reqQuotationOfSymbols q 26 | i.push(i.expect(q.qVal.mapIt(it.getString())).reversed.newVal) 27 | 28 | def.symbol("print") do (i: In): 29 | let a = i.peek 30 | a.print 31 | 32 | def.symbol("read") do (i: In): 33 | let vals = i.expect("str") 34 | let file = vals[0].strVal 35 | var contents = file.readFile 36 | i.push newVal(contents) 37 | 38 | def.symbol("write") do (i: In): 39 | let vals = i.expect("str", "str") 40 | let a = vals[0] 41 | let b = vals[1] 42 | a.strVal.writeFile(b.strVal) 43 | 44 | def.symbol("append") do (i: In): 45 | let vals = i.expect("str", "str") 46 | let a = vals[0] 47 | let b = vals[1] 48 | var f:File 49 | discard f.open(a.strVal, fmAppend) 50 | f.write(b.strVal) 51 | f.close() 52 | 53 | def.symbol("args") do (i: In): 54 | var args = newSeq[MnValue](0) 55 | for par in commandLineParams(): 56 | args.add par.newVal 57 | i.push args.newVal 58 | 59 | def.symbol("exit") do (i: In): 60 | let vals = i.expect("int") 61 | quit(vals[0].intVal.int) 62 | 63 | def.symbol("puts") do (i: In): 64 | let a = i.peek 65 | echo $$a 66 | 67 | def.symbol("gets") do (i: In) {.gcsafe.}: 68 | i.push stdin.readLine().newVal 69 | 70 | def.symbol("symbols") do (i: In): 71 | var q = newSeq[MnValue](0) 72 | var scope = i.scope 73 | while not scope.isNil: 74 | for s in scope.symbols.keys: 75 | q.add s.newVal 76 | scope = scope.parent 77 | i.push q.newVal 78 | 79 | def.symbol("defined") do (i: In): 80 | let vals = i.expect("'sym") 81 | i.push(i.scope.hasSymbol(vals[0].getString).newVal) 82 | 83 | # Language constructs 84 | 85 | def.symbol("let") do (i: In): 86 | let vals = i.expect("'sym", "a") 87 | let sym = vals[0] 88 | var q1 = vals[1] 89 | var symbol: string 90 | var isQuot = q1.isQuotation 91 | q1 = @[q1].newVal 92 | symbol = sym.getString 93 | if not validUserSymbol(symbol): 94 | raiseInvalid("User symbols must start with a letter and contain only letters, numbers, and underscores (_).") 95 | i.scope.symbols[symbol] = MnOperator(kind: mnValOp, val: q1, sealed: false, quotation: isQuot) 96 | 97 | def.symbol("lambda") do (i: In): 98 | let vals = i.expect("'sym", "quot") 99 | let sym = vals[0] 100 | var q1 = vals[1] 101 | var symbol: string 102 | symbol = sym.getString 103 | if not validUserSymbol(symbol): 104 | raiseInvalid("User symbols must start with a letter and contain only letters, numbers, and underscores (_).") 105 | i.scope.symbols[symbol] = MnOperator(kind: mnValOp, val: q1, sealed: false, quotation: true) 106 | 107 | def.symbol("bind") do (i: In): 108 | let vals = i.expect("'sym", "a") 109 | let sym = vals[0] 110 | var q1 = vals[1] 111 | var symbol: string 112 | var isQuot = q1.isQuotation 113 | q1 = @[q1].newVal 114 | symbol = sym.getString 115 | let res = i.scope.setSymbol(symbol, MnOperator(kind: mnValOp, val: q1, quotation: isQuot)) 116 | if not res: 117 | raiseUndefined("Attempting to bind undefined symbol: " & symbol) 118 | 119 | def.symbol("lambdabind") do (i: In): 120 | let vals = i.expect("'sym", "quot") 121 | let sym = vals[0] 122 | var q1 = vals[1] 123 | var symbol: string 124 | symbol = sym.getString 125 | let res = i.scope.setSymbol(symbol, MnOperator(kind: mnValOp, val: q1, quotation: true)) 126 | if not res: 127 | raiseUndefined("Attempting to lambda-bind undefined symbol: " & symbol) 128 | 129 | def.symbol("delete") do (i: In): 130 | let vals = i.expect("'sym") 131 | let sym = vals[0] 132 | let res = i.scope.delSymbol(sym.getString) 133 | if not res: 134 | raiseUndefined("Attempting to delete undefined symbol: " & sym.getString) 135 | 136 | def.symbol("eval") do (i: In): 137 | let vals = i.expect("str") 138 | let s = vals[0] 139 | i.eval s.strVal 140 | 141 | def.symbol("type") do (i: In): 142 | let vals = i.expect("a") 143 | i.push vals[0].typeName.newVal 144 | 145 | def.symbol("quotesym") do (i: In): 146 | let vals = i.expect("str") 147 | let s = vals[0] 148 | i.push(@[i.newSym(s.strVal)].newVal) 149 | 150 | def.symbol("quotecmd") do (i: In): 151 | let vals = i.expect("str") 152 | let s = vals[0] 153 | i.push(@[newCmd(s.strVal)].newVal) 154 | 155 | def.symbol("quote") do (i: In): 156 | let vals = i.expect("a") 157 | let a = vals[0] 158 | i.push @[a].newVal 159 | 160 | def.symbol("dequote") do (i: In): 161 | let vals = i.expect("quot") 162 | var q = vals[0] 163 | i.dequote(q) 164 | 165 | def.symbol("when") do (i: In): 166 | let vals = i.expect("quot", "quot") 167 | var tpath = vals[0] 168 | var check = vals[1] 169 | var stack = i.stack 170 | i.dequote(check) 171 | let res = i.pop 172 | i.stack = stack 173 | if not res.isBool: 174 | raiseInvalid("Result of check is not a boolean value") 175 | if res.boolVal == true: 176 | i.dequote(tpath) 177 | 178 | def.symbol("while") do (i: In): 179 | let vals = i.expect("quot", "quot") 180 | var d = vals[0] 181 | var b = vals[1] 182 | i.dequote(b) 183 | var check = i.pop 184 | while check.boolVal == true: 185 | i.dequote(d) 186 | i.dequote(b) 187 | check = i.pop 188 | 189 | def.symbol("os") do (i: In): 190 | i.push hostOS.newVal 191 | 192 | def.symbol("run") do (i: In): 193 | let vals = i.expect("'sym") 194 | let cmd = vals[0] 195 | let res = execShellCmd(cmd.getString) 196 | i.push(res.newVal) 197 | 198 | def.symbol("which") do (i: In): 199 | let vals = i.expect("'sym") 200 | let s = vals[0] 201 | i.push s.getString.findExe.newVal 202 | 203 | def.symbol("os") do (i: In): 204 | i.push hostOS.newVal 205 | 206 | def.symbol("cpu") do (i: In): 207 | i.push hostCPU.newVal 208 | 209 | def.symbol("timestamp") do (i: In): 210 | i.push getTime().toUnix().newVal 211 | 212 | def.symbol("getstack") do (i: In): 213 | i.push i.stack.newVal 214 | 215 | def.symbol("setstack") do (i: In): 216 | let vals = i.expect("quot") 217 | let q = vals[0] 218 | i.stack = q.qVal 219 | 220 | def.symbol("pop") do (i: In): 221 | discard i.pop 222 | 223 | def.symbol("dup") do (i: In): 224 | i.push i.peek 225 | 226 | def.symbol("dip") do (i: In): 227 | let vals = i.expect("quot", "a") 228 | var q = vals[0] 229 | let v = vals[1] 230 | i.dequote(q) 231 | i.push v 232 | 233 | def.symbol("swap") do (i: In): 234 | let vals = i.expect("a", "a") 235 | let a = vals[0] 236 | let b = vals[1] 237 | i.push a 238 | i.push b 239 | 240 | def.symbol("cons") do (i: In): 241 | let vals = i.expect("quot", "a") 242 | let q = vals[0] 243 | let v = vals[1] 244 | i.push newVal(@[v] & q.qVal) 245 | 246 | def.symbol("interpolate") do (i: In): 247 | var vals = i.expect("quot") 248 | var prog = vals[0] 249 | i.apply prog 250 | vals = i.expect("quot", "str") 251 | var q = vals[0] 252 | let s = vals[1] 253 | var strings = newSeq[string](0) 254 | for el in q.qVal: 255 | strings.add $$el 256 | let res = s.strVal % strings 257 | i.push res.newVal 258 | 259 | def.symbol("strip") do (i: In): 260 | let vals = i.expect("'sym") 261 | let s = vals[0] 262 | i.push s.getString.strip.newVal 263 | 264 | def.symbol("substr") do (i: In): 265 | let vals = i.expect("int", "int", "'sym") 266 | let length = vals[0].intVal 267 | let start = vals[1].intVal 268 | let s = vals[2].getString 269 | let index = min(start+length-1, s.len-1) 270 | i.push s[start..index].newVal 271 | 272 | def.symbol("split") do (i: In): 273 | let vals = i.expect("'sym", "'sym") 274 | let sep = vals[0].getString 275 | let s = vals[1].getString 276 | var q = newSeq[MnValue](0) 277 | var ss: seq[string] 278 | if sep == "": 279 | ss = s.items.toSeq.mapIt($it) 280 | else: 281 | ss = s.split(sep) 282 | for e in ss: 283 | q.add e.newVal 284 | i.push q.newVal 285 | 286 | def.symbol("join") do (i: In): 287 | let vals = i.expect("'sym", "quot") 288 | let s = vals[0] 289 | let q = vals[1] 290 | i.push q.qVal.mapIt($$it).join(s.getString).newVal 291 | 292 | def.symbol("length") do (i: In): 293 | let vals = i.expect("'sym") 294 | let s = vals[0] 295 | i.push s.getString.len.newVal 296 | 297 | def.symbol("indexof") do (i: In): 298 | let vals = i.expect("str", "str") 299 | let reg = vals[0] 300 | let str = vals[1] 301 | let index = str.strVal.find(reg.strVal) 302 | i.push index.newVal 303 | 304 | def.symbol("replace") do (i: In): 305 | let vals = i.expect("str", "str", "str") 306 | let s_replace = vals[0].strVal 307 | let src = vals[1].strVal 308 | let s_find = vals[2].strVal 309 | i.push s_find.replace(src, s_replace).newVal 310 | 311 | def.symbol("concat") do (i: In): 312 | let vals = i.expect("quot", "quot") 313 | let q1 = vals[0] 314 | let q2 = vals[1] 315 | let q = q2.qVal & q1.qVal 316 | i.push q.newVal 317 | 318 | def.symbol("get") do (i: In): 319 | let vals = i.expect("int", "quot") 320 | let index = vals[0] 321 | let q = vals[1] 322 | let ix = index.intVal 323 | if q.qVal.len < ix or ix < 0: 324 | raiseOutOfBounds("Index out of bounds") 325 | i.push q.qVal[ix.int] 326 | 327 | def.symbol("set") do (i: In): 328 | let vals = i.expect("int", "a", "quot") 329 | let index = vals[0] 330 | let val = vals[1] 331 | let q = vals[2] 332 | let ix = index.intVal 333 | if q.qVal.len < ix or ix < 0: 334 | raiseOutOfBounds("Index out of bounds") 335 | q.qVal[ix.int] = val 336 | i.push q 337 | 338 | def.symbol("remove") do (i: In): 339 | let vals = i.expect("int", "quot") 340 | let index = vals[0] 341 | let q = vals[1] 342 | let ix = index.intVal 343 | if q.qVal.len < ix or ix < 0: 344 | raiseOutOfBounds("Index out of bounds") 345 | var res = newSeq[MnValue](0) 346 | for x in 0..q.qVal.len-1: 347 | if x == ix: 348 | continue 349 | res.add q.qVal[x] 350 | i.push res.newVal 351 | 352 | def.symbol("size") do (i: In): 353 | let vals = i.expect("quot") 354 | let q = vals[0] 355 | i.push q.qVal.len.newVal 356 | 357 | def.symbol("included") do (i: In): 358 | let vals = i.expect("a", "quot") 359 | let v = vals[0] 360 | let q = vals[1] 361 | i.push q.qVal.contains(v).newVal 362 | 363 | def.symbol("map") do (i: In): 364 | let vals = i.expect("quot", "quot") 365 | var prog = vals[0] 366 | let list = vals[1] 367 | var res = newSeq[MnValue](0) 368 | for litem in list.qVal: 369 | i.push litem 370 | i.dequote(prog) 371 | res.add i.pop 372 | i.push res.newVal 373 | 374 | def.symbol("filter") do (i: In): 375 | let vals = i.expect("quot", "quot") 376 | var filter = vals[0] 377 | let list = vals[1] 378 | var res = newSeq[MnValue](0) 379 | for e in list.qVal: 380 | i.push e 381 | i.dequote(filter) 382 | var check = i.pop 383 | if check.isBool and check.boolVal == true: 384 | res.add e 385 | i.push res.newVal 386 | 387 | def.symbol("foreach") do (i: In): 388 | let vals = i.expect("quot", "quot") 389 | var prog = vals[0] 390 | var list = vals[1] 391 | for litem in list.qVal: 392 | i.push litem 393 | i.dequote(prog) 394 | 395 | def.symbol("slice") do (i: In): 396 | let vals = i.expect("int", "int", "quot") 397 | let finish = vals[0] 398 | let start = vals[1] 399 | let q = vals[2] 400 | let st = start.intVal 401 | let fn = finish.intVal 402 | if st < 0 or fn > q.qVal.len-1: 403 | raiseOutOfBounds("Index out of bounds") 404 | elif fn < st: 405 | raiseInvalid("End index must be greater than start index") 406 | let rng = q.qVal[st.int..fn.int] 407 | i.push rng.newVal 408 | 409 | def.symbol("nan") do (i: In): 410 | i.push newVal(NaN) 411 | 412 | def.symbol("+inf") do (i: In): 413 | i.push newVal(Inf) 414 | 415 | def.symbol("-inf") do (i: In): 416 | i.push newVal(NegInf) 417 | 418 | def.symbol("mod") do (i: In): 419 | let vals = i.expect("int", "int") 420 | let b = vals[0] 421 | let a = vals[1] 422 | i.push(newVal(a.intVal mod b.intVal)) 423 | 424 | def.symbol("+") do (i: In): 425 | let vals = i.expect("num", "num") 426 | let a = vals[0] 427 | let b = vals[1] 428 | if a.isInt: 429 | if b.isInt: 430 | i.push newVal(a.intVal + b.intVal) 431 | else: 432 | i.push newVal(a.intVal.float + b.floatVal) 433 | else: 434 | if b.isFloat: 435 | i.push newVal(a.floatVal + b.floatVal) 436 | else: 437 | i.push newVal(a.floatVal + b.intVal.float) 438 | 439 | def.symbol("-") do (i: In): 440 | let vals = i.expect("num", "num") 441 | let a = vals[0] 442 | let b = vals[1] 443 | if a.isInt: 444 | if b.isInt: 445 | i.push newVal(b.intVal - a.intVal) 446 | else: 447 | i.push newVal(b.floatVal - a.intVal.float) 448 | else: 449 | if b.isFloat: 450 | i.push newVal(b.floatVal - a.floatVal) 451 | else: 452 | i.push newVal(b.intVal.float - a.floatVal) 453 | 454 | def.symbol("*") do (i: In): 455 | let vals = i.expect("num", "num") 456 | let a = vals[0] 457 | let b = vals[1] 458 | if a.isInt: 459 | if b.isInt: 460 | i.push newVal(a.intVal * b.intVal) 461 | else: 462 | i.push newVal(a.intVal.float * b.floatVal) 463 | else: 464 | if b.isFloat: 465 | i.push newVal(a.floatVal * b.floatVal) 466 | else: 467 | i.push newVal(a.floatVal * b.intVal.float) 468 | 469 | def.symbol("/") do (i: In): 470 | let vals = i.expect("num", "num") 471 | let a = vals[0] 472 | let b = vals[1] 473 | if a.isInt: 474 | if b.isInt: 475 | i.push newVal(b.intVal.int / a.intVal.int) 476 | else: 477 | i.push newVal(b.floatVal / a.intVal.float) 478 | else: 479 | if b.isFloat: 480 | i.push newVal(b.floatVal / a.floatVal) 481 | else: 482 | i.push newVal(b.intVal.float / a.floatVal) 483 | 484 | def.symbol(">") do (i: In): 485 | var n1, n2: MnValue 486 | i.reqTwoNumbersOrStrings n2, n1 487 | if n1.isNumber and n2.isNumber: 488 | if n1.isInt and n2.isInt: 489 | i.push newVal(n1.intVal > n2.intVal) 490 | elif n1.isInt and n2.isFloat: 491 | i.push newVal(n1.intVal.float > n2.floatVal) 492 | elif n1.isFloat and n2.isFloat: 493 | i.push newVal(n1.floatVal > n2.floatVal) 494 | elif n1.isFloat and n2.isInt: 495 | i.push newVal(n1.floatVal > n2.intVal.float) 496 | else: 497 | i.push newVal(n1.strVal > n2.strVal) 498 | 499 | def.symbol(">=") do (i: In): 500 | var n1, n2: MnValue 501 | i.reqTwoNumbersOrStrings n2, n1 502 | if n1.isNumber and n2.isNumber: 503 | if n1.isInt and n2.isInt: 504 | i.push newVal(n1.intVal >= n2.intVal) 505 | elif n1.isInt and n2.isFloat: 506 | i.push newVal(n1.intVal.float > n2.floatVal or floatCompare(n1, n2)) 507 | elif n1.isFloat and n2.isFloat: 508 | i.push newVal(n1.floatVal > n2.floatVal or floatCompare(n1, n2)) 509 | elif n1.isFloat and n2.isInt: 510 | i.push newVal(n1.floatVal > n2.intVal.float or floatCompare(n1, n2)) 511 | else: 512 | i.push newVal(n1.strVal >= n2.strVal) 513 | 514 | def.symbol("<") do (i: In): 515 | var n1, n2: MnValue 516 | i.reqTwoNumbersOrStrings n1, n2 517 | if n1.isNumber and n2.isNumber: 518 | if n1.isInt and n2.isInt: 519 | i.push newVal(n1.intVal > n2.intVal) 520 | elif n1.isInt and n2.isFloat: 521 | i.push newVal(n1.intVal.float > n2.floatVal) 522 | elif n1.isFloat and n2.isFloat: 523 | i.push newVal(n1.floatVal > n2.floatVal) 524 | elif n1.isFloat and n2.isInt: 525 | i.push newVal(n1.floatVal > n2.intVal.float) 526 | else: 527 | i.push newVal(n1.strVal > n2.strVal) 528 | 529 | def.symbol("<=") do (i: In): 530 | var n1, n2: MnValue 531 | i.reqTwoNumbersOrStrings n1, n2 532 | if n1.isNumber and n2.isNumber: 533 | if n1.isInt and n2.isInt: 534 | i.push newVal(n1.intVal >= n2.intVal) 535 | elif n1.isInt and n2.isFloat: 536 | i.push newVal(n1.intVal.float > n2.floatVal or floatCompare(n1, n2)) 537 | elif n1.isFloat and n2.isFloat: 538 | i.push newVal(n1.floatVal > n2.floatVal or floatCompare(n1, n2)) 539 | elif n1.isFloat and n2.isInt: 540 | i.push newVal(n1.floatVal > n2.intVal.float or floatCompare(n1, n2)) 541 | else: 542 | i.push newVal(n1.strVal >= n2.strVal) 543 | 544 | def.symbol("==") do (i: In): 545 | var n1, n2: MnValue 546 | let vals = i.expect("a", "a") 547 | n1 = vals[0] 548 | n2 = vals[1] 549 | if (n1.kind == mnFloat or n2.kind == mnFloat) and n1.isNumber and n2.isNumber: 550 | i.push newVal(floatCompare(n1, n2)) 551 | else: 552 | i.push newVal(n1 == n2) 553 | 554 | def.symbol("!=") do (i: In): 555 | var n1, n2: MnValue 556 | let vals = i.expect("a", "a") 557 | n1 = vals[0] 558 | n2 = vals[1] 559 | if (n1.kind == mnFloat or n2.kind == mnFloat) and n1.isNumber and n2.isNumber: 560 | i.push newVal(not floatCompare(n1, n2)) 561 | i.push newVal(not (n1 == n2)) 562 | 563 | def.symbol("!") do (i: In): 564 | let vals = i.expect("bool") 565 | let b = vals[0] 566 | i.push newVal(not b.boolVal) 567 | 568 | def.symbol("&&") do (i: In): 569 | let vals = i.expect("quot") 570 | let q = vals[0] 571 | var c = 0 572 | for v in q.qVal: 573 | if not v.isQuotation: 574 | raiseInvalid("A quotation of quotations is expected") 575 | var vv = v 576 | i.dequote vv 577 | let r = i.pop 578 | c.inc() 579 | if not r.isBool: 580 | raiseInvalid("Quotation #$# does not evaluate to a boolean value") 581 | if not r.boolVal: 582 | i.push r 583 | return 584 | i.push true.newVal 585 | 586 | def.symbol("||") do (i: In): 587 | let vals = i.expect("quot") 588 | let q = vals[0] 589 | var c = 0 590 | for v in q.qVal: 591 | if not v.isQuotation: 592 | raiseInvalid("A quotation of quotations is expected") 593 | var vv = v 594 | i.dequote vv 595 | let r = i.pop 596 | c.inc() 597 | if not r.isBool: 598 | raiseInvalid("Quotation #$# does not evaluate to a boolean value") 599 | if r.boolVal: 600 | i.push r 601 | return 602 | i.push false.newVal 603 | -------------------------------------------------------------------------------- /mnpkg/meta.nim: -------------------------------------------------------------------------------- 1 | import 2 | strutils 3 | 4 | const ymlconfig = "../mn.yml".slurp 5 | 6 | var pkgName* {.threadvar.}: string 7 | var pkgVersion* {.threadvar.}: string 8 | var pkgAuthor* {.threadvar.}: string 9 | var pkgDescription* {.threadvar.}: string 10 | 11 | for line in ymlconfig.split("\n"): 12 | let pair = line.split(":") 13 | if pair[0].strip == "name": 14 | pkgName = pair[1].strip 15 | if pair[0].strip == "version": 16 | pkgVersion = pair[1].strip 17 | if pair[0].strip == "author": 18 | pkgAuthor = pair[1].strip 19 | if pair[0].strip == "description": 20 | pkgDescription = pair[1].strip 21 | -------------------------------------------------------------------------------- /mnpkg/parser.nim: -------------------------------------------------------------------------------- 1 | # Adapted from: https://github.com/Araq/Nimrod/blob/v0.9.6/lib/pure/json.nim 2 | import 3 | lexbase, 4 | strutils, 5 | streams, 6 | critbits 7 | 8 | import unicode except strip 9 | 10 | type 11 | MnTokenKind* = enum 12 | tkError, 13 | tkEof, 14 | tkString, 15 | tkCommand, 16 | tkInt, 17 | tkFloat, 18 | tkBracketLe, 19 | tkBracketRi, 20 | tkSqBracketLe, 21 | tkSqBracketRi, 22 | tkSymbol, 23 | tkNull, 24 | tkTrue, 25 | tkFalse 26 | MnKind* = enum 27 | mnInt, 28 | mnFloat, 29 | mnQuotation, 30 | mnCommand, 31 | mnString, 32 | mnSymbol, 33 | mnNull, 34 | mnBool 35 | MnEventKind* = enum ## enumeration of all events that may occur when parsing 36 | eMinError, ## an error ocurred during parsing 37 | eMinEof, ## end of file reached 38 | eMinString, ## a string literal 39 | eMinInt, ## an integer literal 40 | eMinFloat, ## a float literal 41 | eMinQuotationStart, ## start of an array: the ``(`` token 42 | eMinQuotationEnd, ## start of an array: the ``)`` token 43 | MnParserError* = enum ## enumeration that lists all errors that can occur 44 | errNone, ## no error 45 | errInvalidToken, ## invalid token 46 | errStringExpected, ## string expected 47 | errBracketRiExpected, ## ``)`` expected 48 | errQuoteExpected, ## ``"`` expected 49 | errSqBracketRiExpected,## ``]`` expected 50 | errEOC_Expected, ## ``*/`` expected 51 | errEofExpected, ## EOF expected 52 | errExprExpected 53 | MnParserState* = enum 54 | stateEof, 55 | stateStart, 56 | stateQuotation, 57 | stateExpectValue 58 | MnParser* = object of BaseLexer 59 | a*: string 60 | doc*: bool 61 | currSym*: MnValue 62 | token*: MnTokenKind 63 | state*: seq[MnParserState] 64 | kind*: MnEventKind 65 | err*: MnParserError 66 | filename*: string 67 | MnValue* = ref MnValueObject 68 | MnValueObject* = object 69 | line*: int 70 | column*: int 71 | filename*: string 72 | outerSym*: string 73 | docComment*: string 74 | case kind*: MnKind 75 | of mnNull: discard 76 | of mnInt: intVal*: BiggestInt 77 | of mnFloat: floatVal*: BiggestFloat 78 | of mnCommand: cmdVal*: string 79 | of mnQuotation: 80 | qVal*: seq[MnValue] 81 | of mnString: strVal*: string 82 | of mnSymbol: symVal*: string 83 | of mnBool: boolVal*: bool 84 | MnScopeKind* = enum 85 | mnNativeScope, 86 | mnLangScope 87 | MnScope* = object 88 | parent*: ref MnScope 89 | symbols*: CritBitTree[MnOperator] 90 | kind*: MnScopeKind 91 | MnOperatorProc* = proc (i: In) {.gcsafe.} 92 | MnOperatorKind* = enum 93 | mnProcOp 94 | mnValOp 95 | MnOperator* = object 96 | sealed*: bool 97 | case kind*: MnOperatorKind 98 | of mnProcOp: 99 | prc*: MnOperatorProc 100 | of mnValOp: 101 | quotation*: bool 102 | val*: MnValue 103 | MnStack* = seq[MnValue] 104 | In* = var MnInterpreter 105 | MnInterpreter* = object 106 | stack*: MnStack 107 | trace*: MnStack 108 | stackcopy*: MnStack 109 | pwd*: string 110 | scope*: ref MnScope 111 | parser*: MnParser 112 | currSym*: MnValue 113 | filename*: string 114 | evaluating*: bool 115 | MnParsingError* = ref object of ValueError 116 | MnUndefinedError* = ref object of ValueError 117 | MnEmptyStackError* = ref object of ValueError 118 | MnInvalidError* = ref object of ValueError 119 | MnOutOfBoundsError* = ref object of ValueError 120 | 121 | 122 | # Helpers 123 | 124 | proc raiseInvalid*(msg: string) = 125 | raise MnInvalidError(msg: msg) 126 | 127 | proc raiseUndefined*(msg: string) = 128 | raise MnUndefinedError(msg: msg) 129 | 130 | proc raiseOutOfBounds*(msg: string) = 131 | raise MnOutOfBoundsError(msg: msg) 132 | 133 | proc raiseEmptyStack*() = 134 | raise MnEmptyStackError(msg: "Insufficient items on the stack") 135 | 136 | const 137 | errorMessages: array[MnParserError, string] = [ 138 | "no error", 139 | "invalid token", 140 | "string expected", 141 | "')' expected", 142 | "'\"' expected", 143 | "']' expected", 144 | "'*/' expected", 145 | "EOF expected", 146 | "expression expected" 147 | ] 148 | tokToStr: array[MnTokenKind, string] = [ 149 | "invalid token", 150 | "EOF", 151 | "string literal", 152 | "command literal", 153 | "int literal", 154 | "float literal", 155 | "(", 156 | ")", 157 | "{", 158 | "}", 159 | "symbol", 160 | "null", 161 | "true", 162 | "false" 163 | ] 164 | 165 | proc newScope*(parent: ref MnScope, kind = mnLangScope): MnScope = 166 | result = MnScope(parent: parent, kind: kind) 167 | 168 | proc newScopeRef*(parent: ref MnScope, kind = mnLangScope): ref MnScope = 169 | new(result) 170 | result[] = newScope(parent, kind) 171 | 172 | proc open*(my: var MnParser, input: Stream, filename: string) = 173 | lexbase.open(my, input) 174 | my.filename = filename 175 | my.state = @[stateStart] 176 | my.kind = eMinError 177 | my.a = "" 178 | 179 | proc close*(my: var MnParser) = 180 | lexbase.close(my) 181 | 182 | proc getInt*(my: MnParser): int = 183 | assert(my.kind == eMinInt) 184 | return parseint(my.a) 185 | 186 | proc getFloat*(my: MnParser): float = 187 | assert(my.kind == eMinFloat) 188 | return parseFloat(my.a) 189 | 190 | proc kind*(my: MnParser): MnEventKind = 191 | return my.kind 192 | 193 | proc getColumn*(my: MnParser): int = 194 | result = getColNumber(my, my.bufpos) 195 | 196 | proc getLine*(my: MnParser): int = 197 | result = my.lineNumber 198 | 199 | proc getFilename*(my: MnParser): string = 200 | result = my.filename 201 | 202 | proc errorMsg*(my: MnParser, msg: string): string = 203 | assert(my.kind == eMinError) 204 | result = "$1 [l:$2, c:$3] ERROR - $4" % [ 205 | my.filename, $getLine(my), $getColumn(my), msg] 206 | 207 | proc errorMsg*(my: MnParser): string = 208 | assert(my.kind == eMinError) 209 | result = errorMsg(my, errorMessages[my.err]) 210 | 211 | proc errorMsgExpected*(my: MnParser, e: string): string = 212 | result = errorMsg(my, e & " expected") 213 | 214 | proc raiseParsing*(p: MnParser, msg: string) = 215 | raise MnParsingError(msg: errorMsgExpected(p, msg)) 216 | 217 | proc raiseUndefined*(p:MnParser, msg: string) = 218 | raise MnUndefinedError(msg: errorMsg(p, msg)) 219 | 220 | proc parseNumber(my: var MnParser) = 221 | var pos = my.bufpos 222 | var buf = my.buf 223 | if buf[pos] == '-': 224 | add(my.a, '-') 225 | inc(pos) 226 | if buf[pos] == '.': 227 | add(my.a, "0.") 228 | inc(pos) 229 | else: 230 | while buf[pos] in Digits: 231 | add(my.a, buf[pos]) 232 | inc(pos) 233 | if buf[pos] == '.': 234 | add(my.a, '.') 235 | inc(pos) 236 | # digits after the dot: 237 | while buf[pos] in Digits: 238 | add(my.a, buf[pos]) 239 | inc(pos) 240 | if buf[pos] in {'E', 'e'}: 241 | add(my.a, buf[pos]) 242 | inc(pos) 243 | if buf[pos] in {'+', '-'}: 244 | add(my.a, buf[pos]) 245 | inc(pos) 246 | while buf[pos] in Digits: 247 | add(my.a, buf[pos]) 248 | inc(pos) 249 | my.bufpos = pos 250 | 251 | proc handleHexChar(c: char, x: var int): bool = 252 | result = true # Success 253 | case c 254 | of '0'..'9': x = (x shl 4) or (ord(c) - ord('0')) 255 | of 'a'..'f': x = (x shl 4) or (ord(c) - ord('a') + 10) 256 | of 'A'..'F': x = (x shl 4) or (ord(c) - ord('A') + 10) 257 | else: result = false # error 258 | 259 | proc parseString(my: var MnParser): MnTokenKind = 260 | result = tkString 261 | var pos = my.bufpos + 1 262 | var buf = my.buf 263 | while true: 264 | case buf[pos] 265 | of '\0': 266 | my.err = errQuoteExpected 267 | result = tkError 268 | break 269 | of '"': 270 | inc(pos) 271 | break 272 | of '\\': 273 | case buf[pos+1] 274 | of '\\', '"', '\'', '/': 275 | add(my.a, buf[pos+1]) 276 | inc(pos, 2) 277 | of 'b': 278 | add(my.a, '\b') 279 | inc(pos, 2) 280 | of 'f': 281 | add(my.a, '\f') 282 | inc(pos, 2) 283 | of 'n': 284 | add(my.a, '\L') 285 | inc(pos, 2) 286 | of 'r': 287 | add(my.a, '\C') 288 | inc(pos, 2) 289 | of 't': 290 | add(my.a, '\t') 291 | inc(pos, 2) 292 | of 'u': 293 | inc(pos, 2) 294 | var r: int 295 | if handleHexChar(buf[pos], r): inc(pos) 296 | if handleHexChar(buf[pos], r): inc(pos) 297 | if handleHexChar(buf[pos], r): inc(pos) 298 | if handleHexChar(buf[pos], r): inc(pos) 299 | add(my.a, toUTF8(Rune(r))) 300 | else: 301 | # don't bother with the error 302 | add(my.a, buf[pos]) 303 | inc(pos) 304 | of '\c': 305 | pos = lexbase.handleCR(my, pos) 306 | buf = my.buf 307 | add(my.a, '\c') 308 | of '\L': 309 | pos = lexbase.handleLF(my, pos) 310 | buf = my.buf 311 | add(my.a, '\L') 312 | else: 313 | add(my.a, buf[pos]) 314 | inc(pos) 315 | my.bufpos = pos # store back 316 | 317 | proc parseCommand(my: var MnParser): MnTokenKind = 318 | result = tkCommand 319 | var pos = my.bufpos + 1 320 | var buf = my.buf 321 | while true: 322 | case buf[pos] 323 | of '\0': 324 | my.err = errSqBracketRiExpected 325 | result = tkError 326 | break 327 | of ']': 328 | inc(pos) 329 | break 330 | of '\\': 331 | case buf[pos+1] 332 | of '\\', '"', '\'', '/': 333 | add(my.a, buf[pos+1]) 334 | inc(pos, 2) 335 | of 'b': 336 | add(my.a, '\b') 337 | inc(pos, 2) 338 | of 'f': 339 | add(my.a, '\f') 340 | inc(pos, 2) 341 | of 'n': 342 | add(my.a, '\L') 343 | inc(pos, 2) 344 | of 'r': 345 | add(my.a, '\C') 346 | inc(pos, 2) 347 | of 't': 348 | add(my.a, '\t') 349 | inc(pos, 2) 350 | of 'u': 351 | inc(pos, 2) 352 | var r: int 353 | if handleHexChar(buf[pos], r): inc(pos) 354 | if handleHexChar(buf[pos], r): inc(pos) 355 | if handleHexChar(buf[pos], r): inc(pos) 356 | if handleHexChar(buf[pos], r): inc(pos) 357 | add(my.a, toUTF8(Rune(r))) 358 | else: 359 | # don't bother with the error 360 | add(my.a, buf[pos]) 361 | inc(pos) 362 | of '\c': 363 | pos = lexbase.handleCR(my, pos) 364 | buf = my.buf 365 | add(my.a, '\c') 366 | of '\L': 367 | pos = lexbase.handleLF(my, pos) 368 | buf = my.buf 369 | add(my.a, '\L') 370 | else: 371 | add(my.a, buf[pos]) 372 | inc(pos) 373 | my.bufpos = pos # store back 374 | 375 | proc parseSymbol(my: var MnParser): MnTokenKind = 376 | result = tkSymbol 377 | var pos = my.bufpos 378 | var buf = my.buf 379 | if not(buf[pos] in Whitespace): 380 | while not(buf[pos] in WhiteSpace) and not(buf[pos] in ['\0', ')', '(', ']', '[']): 381 | if buf[pos] == '"': 382 | add(my.a, buf[pos]) 383 | my.bufpos = pos 384 | let r = parseString(my) 385 | if r == tkError: 386 | result = tkError 387 | return 388 | add(my.a, buf[pos]) 389 | return 390 | else: 391 | add(my.a, buf[pos]) 392 | inc(pos) 393 | my.bufpos = pos 394 | 395 | proc addDoc(my: var MnParser, docComment: string, reset = true) = 396 | if my.doc and not my.currSym.isNil and my.currSym.kind == mnSymbol: 397 | if reset: 398 | my.doc = false 399 | if my.currSym.docComment.len == 0 or my.currSym.docComment.len > 0 and my.currSym.docComment[my.currSym.docComment.len-1] == '\n': 400 | my.currSym.docComment &= docComment.strip(true, false) 401 | else: 402 | my.currSym.docComment &= docComment 403 | 404 | proc skip(my: var MnParser) = 405 | var pos = my.bufpos 406 | var buf = my.buf 407 | while true: 408 | case buf[pos] 409 | of ';': 410 | # skip line comment: 411 | if buf[pos+1] == ';': 412 | my.doc = true 413 | inc(pos, 2) 414 | while true: 415 | case buf[pos] 416 | of '\0': 417 | break 418 | of '\c': 419 | pos = lexbase.handleCR(my, pos) 420 | buf = my.buf 421 | my.addDoc "\n" 422 | break 423 | of '\L': 424 | pos = lexbase.handleLF(my, pos) 425 | buf = my.buf 426 | my.addDoc "\n" 427 | break 428 | else: 429 | my.addDoc $my.buf[pos], false 430 | inc(pos) 431 | of '#': 432 | if buf[pos+1] == '|': 433 | # skip long comment: 434 | if buf[pos+2] == '|': 435 | inc(pos) 436 | my.doc = true 437 | inc(pos, 2) 438 | while true: 439 | case buf[pos] 440 | of '\0': 441 | my.err = errEOC_Expected 442 | break 443 | of '\c': 444 | pos = lexbase.handleCR(my, pos) 445 | my.addDoc "\n", false 446 | buf = my.buf 447 | of '\L': 448 | pos = lexbase.handleLF(my, pos) 449 | my.addDoc "\n", false 450 | buf = my.buf 451 | of '|': 452 | inc(pos) 453 | if buf[pos] == '|': 454 | inc(pos) 455 | if buf[pos] == '#': 456 | inc(pos) 457 | break 458 | my.addDoc $buf[pos], false 459 | else: 460 | my.addDoc $my.buf[pos], false 461 | inc(pos) 462 | else: 463 | break 464 | of ' ', '\t': 465 | inc(pos) 466 | of '\c': 467 | pos = lexbase.handleCR(my, pos) 468 | buf = my.buf 469 | of '\L': 470 | pos = lexbase.handleLF(my, pos) 471 | buf = my.buf 472 | else: 473 | break 474 | my.bufpos = pos 475 | 476 | proc getToken*(my: var MnParser): MnTokenKind = 477 | setLen(my.a, 0) 478 | skip(my) 479 | case my.buf[my.bufpos] 480 | of '-', '.': 481 | if my.bufpos+1 <= my.buf.len and my.buf[my.bufpos+1] in '0'..'9': 482 | parseNumber(my) 483 | if {'.', 'e', 'E'} in my.a: 484 | result = tkFloat 485 | else: 486 | result = tkInt 487 | else: 488 | result = parseSymbol(my) 489 | of '0'..'9': 490 | parseNumber(my) 491 | if {'.', 'e', 'E'} in my.a: 492 | result = tkFloat 493 | else: 494 | result = tkInt 495 | of '"': 496 | result = parseString(my) 497 | of '(': 498 | inc(my.bufpos) 499 | result = tkBracketLe 500 | of ')': 501 | inc(my.bufpos) 502 | result = tkBracketRi 503 | of '[': 504 | result = parseCommand(my) 505 | of '\0': 506 | result = tkEof 507 | else: 508 | result = parseSymbol(my) 509 | case my.a 510 | of "null": result = tkNull 511 | of "true": result = tkTrue 512 | of "false": result = tkFalse 513 | else: 514 | discard 515 | my.token = result 516 | 517 | 518 | proc next*(my: var MnParser) = 519 | var tk = getToken(my) 520 | var i = my.state.len-1 521 | case my.state[i] 522 | of stateEof: 523 | if tk == tkEof: 524 | my.kind = eMinEof 525 | else: 526 | my.kind = eMinError 527 | my.err = errEofExpected 528 | of stateStart: 529 | case tk 530 | of tkString, tkInt, tkFloat, tkTrue, tkFalse: 531 | my.state[i] = stateEof # expect EOF next! 532 | my.kind = MnEventKind(ord(tk)) 533 | of tkBracketLe: 534 | my.state.add(stateQuotation) # we expect any 535 | my.kind = eMinQuotationStart 536 | of tkEof: 537 | my.kind = eMinEof 538 | else: 539 | my.kind = eMinError 540 | my.err = errEofExpected 541 | of stateQuotation: 542 | case tk 543 | of tkString, tkInt, tkFloat, tkTrue, tkFalse: 544 | my.kind = MnEventKind(ord(tk)) 545 | of tkBracketLe: 546 | my.state.add(stateQuotation) 547 | my.kind = eMinQuotationStart 548 | of tkBracketRi: 549 | my.kind = eMinQuotationEnd 550 | discard my.state.pop() 551 | else: 552 | my.kind = eMinError 553 | my.err = errBracketRiExpected 554 | of stateExpectValue: 555 | case tk 556 | of tkString, tkInt, tkFloat, tkTrue, tkFalse: 557 | my.kind = MnEventKind(ord(tk)) 558 | of tkBracketLe: 559 | my.state.add(stateQuotation) 560 | my.kind = eMinQuotationStart 561 | else: 562 | my.kind = eMinError 563 | my.err = errExprExpected 564 | 565 | proc eat(p: var MnParser, token: MnTokenKind) = 566 | if p.token == token: discard getToken(p) 567 | else: raiseParsing(p, tokToStr[token]) 568 | 569 | proc `$`*(a: MnValue): string = 570 | case a.kind: 571 | of mnNull: 572 | return "null" 573 | of mnBool: 574 | return $a.boolVal 575 | of mnSymbol: 576 | return a.symVal 577 | of mnString: 578 | return "\"$1\"" % a.strVal.replace("\"", "\\\"") 579 | of mnInt: 580 | return $a.intVal 581 | of mnFloat: 582 | return $a.floatVal 583 | of mnQuotation: 584 | var q = "(" 585 | for i in a.qVal: 586 | q = q & $i & " " 587 | q = q.strip & ")" 588 | return q 589 | of mnCommand: 590 | return "[" & a.cmdVal & "]" 591 | 592 | proc `$$`*(a: MnValue): string = 593 | case a.kind: 594 | of mnNull: 595 | return "null" 596 | of mnBool: 597 | return $a.boolVal 598 | of mnSymbol: 599 | return a.symVal 600 | of mnString: 601 | return a.strVal 602 | of mnInt: 603 | return $a.intVal 604 | of mnFloat: 605 | return $a.floatVal 606 | of mnQuotation: 607 | var q = "(" 608 | for i in a.qVal: 609 | q = q & $i & " " 610 | q = q.strip & ")" 611 | return q 612 | of mnCommand: 613 | return "[" & a.cmdVal & "]" 614 | 615 | proc parseMinValue*(p: var MnParser, i: In): MnValue = 616 | case p.token 617 | of tkNull: 618 | result = MnValue(kind: mnNull) 619 | discard getToken(p) 620 | of tkTrue: 621 | result = MnValue(kind: mnBool, boolVal: true) 622 | discard getToken(p) 623 | of tkFalse: 624 | result = MnValue(kind: mnBool, boolVal: false) 625 | discard getToken(p) 626 | of tkString: 627 | result = MnValue(kind: mnString, strVal: p.a) 628 | p.a = "" 629 | discard getToken(p) 630 | of tkCommand: 631 | result = MnValue(kind: mnCommand, cmdVal: p.a) 632 | p.a = "" 633 | discard getToken(p) 634 | of tkInt: 635 | result = MnValue(kind: mnInt, intVal: parseint(p.a)) 636 | discard getToken(p) 637 | of tkFloat: 638 | result = MnValue(kind: mnFloat, floatVal: parseFloat(p.a)) 639 | discard getToken(p) 640 | of tkBracketLe: 641 | var q = newSeq[MnValue](0) 642 | discard getToken(p) 643 | while p.token != tkBracketRi: 644 | q.add p.parseMinValue(i) 645 | eat(p, tkBracketRi) 646 | result = MnValue(kind: mnQuotation, qVal: q) 647 | of tkSymbol: 648 | result = MnValue(kind: mnSymbol, symVal: p.a, column: p.getColumn, line: p.lineNumber, filename: p.filename) 649 | p.a = "" 650 | p.currSym = result 651 | discard getToken(p) 652 | else: 653 | let err = "Undefined or invalid value: "&p.a 654 | raiseUndefined(p, err) 655 | result.filename = p.filename 656 | 657 | proc print*(a: MnValue) = 658 | stdout.write($$a) 659 | stdout.flushFile() 660 | 661 | # Predicates 662 | 663 | proc isNull*(s: MnValue): bool = 664 | return s.kind == mnNull 665 | 666 | proc isSymbol*(s: MnValue): bool = 667 | return s.kind == mnSymbol 668 | 669 | proc isQuotation*(s: MnValue): bool = 670 | return s.kind == mnQuotation 671 | 672 | proc isCommand*(s: MnValue): bool = 673 | return s.kind == mnCommand 674 | 675 | proc isString*(s: MnValue): bool = 676 | return s.kind == mnString 677 | 678 | proc isFloat*(s: MnValue): bool = 679 | return s.kind == mnFloat 680 | 681 | proc isInt*(s: MnValue): bool = 682 | return s.kind == mnInt 683 | 684 | proc isNumber*(s: MnValue): bool = 685 | return s.kind == mnInt or s.kind == mnFloat 686 | 687 | proc isBool*(s: MnValue): bool = 688 | return s.kind == mnBool 689 | 690 | proc isStringLike*(s: MnValue): bool = 691 | return s.isSymbol or s.isString or (s.isQuotation and s.qVal.len == 1 and s.qVal[0].isSymbol) 692 | 693 | proc `==`*(a: MnValue, b: MnValue): bool = 694 | if not (a.kind == b.kind or (a.isNumber and b.isNumber)): 695 | return false 696 | if a.kind == mnSymbol and b.kind == mnSymbol: 697 | return a.symVal == b.symVal 698 | elif a.kind == mnInt and b.kind == mnInt: 699 | return a.intVal == b.intVal 700 | elif a.kind == mnInt and b.kind == mnFloat: 701 | return a.intVal.float == b.floatVal.float 702 | elif a.kind == mnFloat and b.kind == mnFloat: 703 | return a.floatVal == b.floatVal 704 | elif a.kind == mnFloat and b.kind == mnInt: 705 | return a.floatVal == b.intVal.float 706 | elif a.kind == b.kind: 707 | if a.kind == mnString: 708 | return a.strVal == b.strVal 709 | elif a.kind == mnBool: 710 | return a.boolVal == b.boolVal 711 | elif a.kind == mnNull: 712 | return true 713 | elif a.kind == mnQuotation: 714 | if a.qVal.len == b.qVal.len: 715 | var c = 0 716 | for item in a.qVal: 717 | if item == b.qVal[c]: 718 | c.inc 719 | else: 720 | return false 721 | return true 722 | else: 723 | return false 724 | else: 725 | return false 726 | -------------------------------------------------------------------------------- /mnpkg/scope.nim: -------------------------------------------------------------------------------- 1 | import 2 | strutils, 3 | critbits 4 | import 5 | parser 6 | 7 | proc copy*(s: ref MnScope): ref MnScope = 8 | var scope = newScope(s.parent) 9 | scope.symbols = s.symbols 10 | new(result) 11 | result[] = scope 12 | 13 | proc getSymbol*(scope: ref MnScope, key: string, acc=0): MnOperator = 14 | if scope.symbols.hasKey(key): 15 | return scope.symbols[key] 16 | else: 17 | if scope.parent.isNil: 18 | raiseUndefined("Symbol '$1' not found." % key) 19 | return scope.parent.getSymbol(key, acc + 1) 20 | 21 | proc hasSymbol*(scope: ref MnScope, key: string): bool = 22 | if scope.isNil: 23 | return false 24 | elif scope.symbols.hasKey(key): 25 | return true 26 | elif not scope.parent.isNil: 27 | return scope.parent.hasSymbol(key) 28 | else: 29 | return false 30 | 31 | proc delSymbol*(scope: ref MnScope, key: string): bool {.discardable.}= 32 | if scope.symbols.hasKey(key): 33 | if scope.symbols[key].sealed: 34 | raiseInvalid("Symbol '$1' is sealed." % key) 35 | scope.symbols.excl(key) 36 | return true 37 | return false 38 | 39 | proc setSymbol*(scope: ref MnScope, key: string, value: MnOperator, override = false): bool {.discardable.}= 40 | result = false 41 | # check if a symbol already exists in current scope 42 | if not scope.isNil and scope.symbols.hasKey(key): 43 | if not override and scope.symbols[key].sealed: 44 | raiseInvalid("Symbol '$1' is sealed ." % key) 45 | scope.symbols[key] = value 46 | result = true 47 | else: 48 | # Go up the scope chain and attempt to find the symbol 49 | if not scope.parent.isNil: 50 | result = scope.parent.setSymbol(key, value, override) 51 | 52 | proc previous*(scope: ref MnScope): ref MnScope = 53 | if scope.parent.isNil: 54 | return scope 55 | else: 56 | return scope.parent 57 | -------------------------------------------------------------------------------- /mnpkg/utils.nim: -------------------------------------------------------------------------------- 1 | import 2 | strutils, 3 | algorithm, 4 | critbits, 5 | math 6 | import 7 | parser, 8 | value, 9 | interpreter 10 | 11 | proc floatCompare*(n1, n2: MnValue): bool = 12 | let 13 | a:float = if n1.kind != mnFloat: n1.intVal.float else: n1.floatVal 14 | b:float = if n2.kind != mnFloat: n2.intVal.float else: n2.floatVal 15 | if a.classify == fcNan and b.classify == fcNan: 16 | return true 17 | else: 18 | const 19 | FLOAT_MIN_NORMAL = 2e-1022 20 | FLOAT_MAX_VALUE = (2-2e-52)*2e1023 21 | epsilon = 0.00001 22 | let 23 | absA = abs(a) 24 | absB = abs(b) 25 | diff = abs(a - b) 26 | 27 | if a == b: 28 | return true 29 | elif a == 0 or b == 0 or diff < FLOAT_MIN_NORMAL: 30 | return diff < (epsilon * FLOAT_MIN_NORMAL) 31 | else: 32 | return diff / min((absA + absB), FLOAT_MAX_VALUE) < epsilon 33 | 34 | # Library methods 35 | 36 | proc symbol*(scope: ref MnScope, sym: string, p: MnOperatorProc) = 37 | scope.symbols[sym] = MnOperator(prc: p, kind: mnProcOp, sealed: true) 38 | 39 | proc symbol*(scope: ref MnScope, sym: string, v: MnValue) = 40 | scope.symbols[sym] = MnOperator(val: v, kind: mnValOp, sealed: true) 41 | 42 | # Validators 43 | 44 | proc validUserSymbol*(s: string): bool = 45 | for i in 0.. 0: 51 | discard 52 | else: 53 | return false 54 | else: 55 | return false 56 | return true 57 | 58 | proc validate*(i: In, value: MnValue, t: string): bool {.gcsafe.} 59 | 60 | proc validateValueType*(i: var MnInterpreter, element: string, value: MnValue, vTypes: var seq[string], c: int): bool {.gcsafe.} = 61 | vTypes.add value.typeName 62 | let ors = element.split("|") 63 | for to in ors: 64 | let ands = to.split("&") 65 | var andr = true 66 | for ta in ands: 67 | var t = ta 68 | var neg = false 69 | if t.len > 1 and t[0] == '!': 70 | t = t[1..t.len-1] 71 | neg = true 72 | andr = i.validate(value, t) 73 | if neg: 74 | andr = not andr 75 | if not andr: 76 | if neg: 77 | vTypes[c] = t 78 | else: 79 | vTypes[c] = value.typeName 80 | break 81 | if andr: 82 | result = true 83 | break 84 | 85 | proc validateValueType*(i: var MnInterpreter, element: string, value: MnValue): bool {.gcsafe.} = 86 | var s = newSeq[string](0) 87 | var c = 0 88 | return i.validateValueType(element, value, s, c) 89 | 90 | proc validate*(i: In, value: MnValue, t: string): bool {.gcsafe.} = 91 | case t: 92 | of "bool": 93 | return value.isBool 94 | of "null": 95 | return value.isNull 96 | of "int": 97 | return value.isInt 98 | of "num": 99 | return value.isNumber 100 | of "quot": 101 | return value.isQuotation 102 | of "cmd": 103 | return value.isCommand 104 | of "'sym": 105 | return value.isStringLike 106 | of "sym": 107 | return value.isSymbol 108 | of "flt": 109 | return value.isFloat 110 | of "str": 111 | return value.isString 112 | of "a": 113 | return true 114 | else: 115 | raiseInvalid("Unknown type '$#'" % t) 116 | 117 | 118 | proc expect*(i: var MnInterpreter, elements: varargs[string]): seq[MnValue] {.gcsafe.}= 119 | let sym = i.currSym.getString 120 | var valid = newSeq[string](0) 121 | result = newSeq[MnValue](0) 122 | let message = proc(invalid: string, elements: varargs[string]): string = 123 | var pelements = newSeq[string](0) 124 | for e in elements.reversed: 125 | pelements.add e 126 | let stack = pelements.join(" ") 127 | result = "Incorrect values found on the stack:\n" 128 | result &= "- expected: " & stack & " $1\n" % sym 129 | var other = "" 130 | if valid.len > 0: 131 | other = valid.reversed.join(" ") & " " 132 | result &= "- got: " & invalid & " " & other & sym 133 | var res = false 134 | var vTypes = newSeq[string](0) 135 | var c = 0 136 | for el in elements: 137 | let value = i.pop 138 | result.add value 139 | res = i.validateValueType(el, value, vTypes, c) 140 | if res: 141 | valid.add el 142 | else: 143 | raiseInvalid(message(vTypes[c], elements)) 144 | c = c+1 145 | 146 | proc reqQuotationOfQuotations*(i: var MnInterpreter, a: var MnValue) = 147 | a = i.pop 148 | if not a.isQuotation: 149 | raiseInvalid("A quotation is required on the stack") 150 | for s in a.qVal: 151 | if not s.isQuotation: 152 | raiseInvalid("A quotation of quotations is required on the stack") 153 | 154 | proc reqQuotationOfNumbers*(i: var MnInterpreter, a: var MnValue) = 155 | a = i.pop 156 | if not a.isQuotation: 157 | raiseInvalid("A quotation is required on the stack") 158 | for s in a.qVal: 159 | if not s.isNumber: 160 | raiseInvalid("A quotation of numbers is required on the stack") 161 | 162 | proc reqQuotationOfIntegers*(i: var MnInterpreter, a: var MnValue) = 163 | a = i.pop 164 | if not a.isQuotation: 165 | raiseInvalid("A quotation is required on the stack") 166 | for s in a.qVal: 167 | if not s.isInt: 168 | raiseInvalid("A quotation of integers is required on the stack") 169 | 170 | proc reqQuotationOfSymbols*(i: var MnInterpreter, a: var MnValue) = 171 | a = i.pop 172 | if not a.isQuotation: 173 | raiseInvalid("A quotation is required on the stack") 174 | for s in a.qVal: 175 | if not s.isSymbol: 176 | raiseInvalid("A quotation of symbols is required on the stack") 177 | 178 | proc reqTwoNumbersOrStrings*(i: var MnInterpreter, a, b: var MnValue) = 179 | a = i.pop 180 | b = i.pop 181 | if not (a.isString and b.isString or a.isNumber and b.isNumber): 182 | raiseInvalid("Two numbers or two strings are required on the stack") 183 | 184 | proc reqStringOrQuotation*(i: var MnInterpreter, a: var MnValue) = 185 | a = i.pop 186 | if not a.isQuotation and not a.isString: 187 | raiseInvalid("A quotation or a string is required on the stack") 188 | 189 | proc reqTwoQuotationsOrStrings*(i: var MnInterpreter, a, b: var MnValue) = 190 | a = i.pop 191 | b = i.pop 192 | if not (a.isQuotation and b.isQuotation or a.isString and b.isString): 193 | raiseInvalid("Two quotations or two strings are required on the stack") -------------------------------------------------------------------------------- /mnpkg/value.nim: -------------------------------------------------------------------------------- 1 | import 2 | parser, 3 | hashes 4 | 5 | proc typeName*(v: MnValue): string = 6 | case v.kind: 7 | of mnInt: 8 | return "int" 9 | of mnFloat: 10 | return "flt" 11 | of mnCommand: 12 | return "cmd" 13 | of mnQuotation: 14 | return "quot" 15 | of mnString: 16 | return "str" 17 | of mnSymbol: 18 | return "sym" 19 | of mnNull: 20 | return "null" 21 | of mnBool: 22 | return "bool" 23 | 24 | # Constructors 25 | 26 | proc newNull*(): MnValue = 27 | return MnValue(kind: mnNull) 28 | 29 | proc newVal*(s: string): MnValue = 30 | return MnValue(kind: mnString, strVal: s) 31 | 32 | proc newVal*(s: cstring): MnValue = 33 | return MnValue(kind: mnString, strVal: $s) 34 | 35 | proc newVal*(q: seq[MnValue]): MnValue = 36 | return MnValue(kind: mnQuotation, qVal: q) 37 | 38 | proc newVal*(i: BiggestInt): MnValue = 39 | return MnValue(kind: mnInt, intVal: i) 40 | 41 | proc newVal*(f: BiggestFloat): MnValue = 42 | return MnValue(kind: mnFloat, floatVal: f) 43 | 44 | proc newVal*(s: bool): MnValue = 45 | return MnValue(kind: mnBool, boolVal: s) 46 | 47 | proc newSym*(s: string): MnValue = 48 | return MnValue(kind: mnSymbol, symVal: s) 49 | 50 | proc newCmd*(s: string): MnValue = 51 | return MnValue(kind: mnCommand, cmdVal: s) 52 | 53 | proc hash*(v: MnValue): Hash = 54 | return hash($v) 55 | 56 | # Get string value from string or quoted symbol 57 | 58 | proc getFloat*(v: MnValue): float = 59 | if v.isInt: 60 | return v.intVal.float 61 | elif v.isFloat: 62 | return v.floatVal 63 | else: 64 | raiseInvalid("Value is not a number") 65 | 66 | proc getString*(v: MnValue): string = 67 | if v.isSymbol: 68 | return v.symVal 69 | elif v.isString: 70 | return v.strVal 71 | elif v.isCommand: 72 | return v.cmdVal 73 | elif v.isQuotation: 74 | if v.qVal.len != 1: 75 | raiseInvalid("Quotation is not a quoted symbol") 76 | let sym = v.qVal[0] 77 | if sym.isSymbol: 78 | return sym.symVal 79 | else: 80 | raiseInvalid("Quotation is not a quoted symbol") 81 | -------------------------------------------------------------------------------- /mntool.mn: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env mn 2 | 3 | ; Validation 4 | (args size 2 <) ("No task specified" puts pop 1 exit) when 5 | 6 | args 1 get ":" split "taskspec" let 7 | taskspec 0 get "task" let 8 | "default" "subtask" let 9 | (taskspec size 1 >) (taskspec 1 get "subtask" bind) when 10 | 11 | "tasks/$#.mn" (task) interpolate "taskfile" let 12 | 13 | taskfile read eval 14 | 15 | "$#__$#" (task subtask) interpolate eval 16 | -------------------------------------------------------------------------------- /tasks/build.mn: -------------------------------------------------------------------------------- 1 | "tasks/info.mn" read eval 2 | 3 | ( 4 | (stage) let 5 | (target_os) let 6 | "mn_v$#_$#_x64" (cfg_version target_os) interpolate (o) let 7 | "" (ext) let 8 | (target_os "windows" ==) 9 | (".exe" (ext) bind) 10 | when 11 | "Building mn - $# (x64) - $#" (target_os stage) interpolate puts pop 12 | "nim c -d:$# --os:$# --mm:orc --deepcopy:on --opt:size mn" (stage target_os) interpolate (cmd) let 13 | cmd puts pop 14 | cmd run 15 | "tar -czvf $#.tar.gz mn$#" (o ext) interpolate run 16 | ) (compile) lambda 17 | 18 | #| Tasks |# 19 | 20 | ( 21 | os "release" compile 22 | ) (build__default) lambda 23 | 24 | ( 25 | os "dev" compile 26 | ) (build__dev) lambda 27 | 28 | ( 29 | "windows" "release" compile 30 | ) (build__windows) lambda 31 | 32 | ( 33 | "linux" "release" compile 34 | ) (build__linux) lambda 35 | 36 | ( 37 | "macosx" "release" compile 38 | ) (build__macosx) lambda 39 | -------------------------------------------------------------------------------- /tasks/clean.mn: -------------------------------------------------------------------------------- 1 | ( 2 | [ls] "\n" split ("mn_v" indexof -1 >) filter (files) let 3 | files ( 4 | (file) let 5 | "Removing: $#" (file) interpolate puts pop 6 | "rm $#" (file) interpolate run 7 | ) foreach 8 | ) (clean__default) lambda -------------------------------------------------------------------------------- /tasks/guide.mn: -------------------------------------------------------------------------------- 1 | "tasks/info.mn" read eval 2 | 3 | ( 4 | "Generating developer guide..." puts pop 5 | "hastyscribe --field/version=$# Mn_DeveloperGuide.md" (cfg_version) interpolate run 6 | "Done." puts pop 7 | ) (guide__default) lambda 8 | -------------------------------------------------------------------------------- /tasks/h3rald.mn: -------------------------------------------------------------------------------- 1 | 2 | 3 | ( 4 | "Copying developer guide.." puts pop 5 | "cp Mn_DeveloperGuide.htm ../h3rald/assets/mn/" run 6 | "cp Mn_DeveloperGuide.htm ../h3rald/output/mn/" run 7 | "Done." puts pop 8 | 9 | ) (h3rald__default) lambda 10 | -------------------------------------------------------------------------------- /tasks/info.mn: -------------------------------------------------------------------------------- 1 | null "cfg_author" let 2 | null "cfg_version" let 3 | null "cfg_name" let 4 | null "cfg_description" let 5 | 6 | "mn.yml" read "\n" split 7 | ( 8 | ; Process each line 9 | ":" split (parts) let 10 | (parts size 1 >) 11 | ( 12 | parts 0 get strip (name) let 13 | parts 1 get strip (value) let 14 | value "cfg_$#" (name) interpolate bind 15 | ) when 16 | ) foreach 17 | 18 | ( 19 | "$# v$# - $#" (cfg_name cfg_version cfg_description) interpolate puts pop 20 | ) (info__default) lambda 21 | 22 | ( 23 | cfg_version puts pop 24 | ) (info__version) lambda -------------------------------------------------------------------------------- /tasks/release.mn: -------------------------------------------------------------------------------- 1 | "tasks/clean.mn" read eval 2 | "tasks/build.mn" read eval 3 | 4 | ( 5 | clean__default 6 | build__linux 7 | build__windows 8 | build__macosx 9 | ) (release__default) lambda --------------------------------------------------------------------------------