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