├── .gitignore
├── .npmignore
├── .travis-update-gh-pages
├── .travis.yml
├── LICENSE
├── README.md
├── build
├── clean
├── examples
├── 0-rho-iota.apl
├── 0-rho-iota.out
├── 1-mult.apl
├── 1-mult.out
├── 2-sierpinski.apl
├── 2-sierpinski.out
├── 3-primes.apl
├── 3-primes.out
├── 4-life.apl
├── 4-life.out
├── 5-rule30.apl
├── 5-rule30.out
├── 6-queens.apl
├── 6-queens.out
├── 7-mandelbrot.apl
├── 7-mandelbrot.out
├── README
└── test
├── index.html
├── m-src
├── README
├── images
│ ├── build.py
│ ├── cursor.png
│ └── cursor.svg
├── index.coffee
├── index.css
└── index.html
├── old-apl.js
├── package.json
├── src
├── apl.coffee
├── array.coffee
├── compiler.coffee
├── complex.coffee
├── errors.coffee
├── helpers.coffee
├── lexer.coffee
├── parser.coffee
├── prelude.apl
├── vm.coffee
└── vocabulary
│ ├── arithmetic.coffee
│ ├── backslash.coffee
│ ├── circle.coffee
│ ├── comma.coffee
│ ├── comparisons.coffee
│ ├── compose.coffee
│ ├── cupcap.coffee
│ ├── decode.coffee
│ ├── dot.coffee
│ ├── each.coffee
│ ├── encode.coffee
│ ├── epsilon.coffee
│ ├── exclamation.coffee
│ ├── execute.coffee
│ ├── find.coffee
│ ├── floorceil.coffee
│ ├── fork.coffee
│ ├── format.coffee
│ ├── grade.coffee
│ ├── identity.coffee
│ ├── iota.coffee
│ ├── leftshoe.coffee
│ ├── logic.coffee
│ ├── poweroperator.coffee
│ ├── quad.coffee
│ ├── question.coffee
│ ├── raise.coffee
│ ├── rho.coffee
│ ├── rotate.coffee
│ ├── slash.coffee
│ ├── squish.coffee
│ ├── take.coffee
│ ├── transpose.coffee
│ ├── variant.coffee
│ └── vhelpers.coffee
├── test
├── collectdoctests.coffee
├── rundoctest.coffee
└── rundoctests.coffee
└── web-src
├── README
├── apl385.css
├── apl385.ttf
├── examples-gen.coffee
├── index.coffee
├── index.css
├── index.html
├── jquery.fieldselection.min.js
├── jquery.keyboard.extension-typing.js
├── jquery.keyboard.js
├── jquery.min.js
├── jquery.tipsy.js
├── keyboard.css
├── tipsy.css
└── tipsy.gif
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | lib/
3 | web/
4 | web-tmp/
5 | m/
6 | m-tmp/
7 | test/*.js
8 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | *
2 | !lib/**/*.js
3 | !bin/**
4 |
--------------------------------------------------------------------------------
/.travis-update-gh-pages:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 | cd $(dirname "$0")
4 |
5 | if [ -z "$TRAVIS_PULL_REQUEST" ]; then
6 | echo 'This script is intended to be run only by Travis'
7 | exit 1
8 | fi
9 |
10 | if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then
11 | echo 'This is a pull request. Skipping gh-pages update'
12 | exit 0
13 | fi
14 |
15 | rm -rf /tmp/apl
16 | cd /tmp
17 | git clone -b gh-pages https://${GH_TOKEN}@github.com/ngn/apl.git
18 | cd apl
19 | git config user.email travis@example.com
20 | git config user.name travis
21 | git merge origin/master --no-edit
22 | npm install
23 | ./build
24 | if [ -n "$(git status m web --porcelain)" ]; then
25 | git add -A web m lib
26 | git commit -m 'built demo'
27 | fi
28 | git push origin gh-pages
29 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - '0.8'
4 | after_success: ./.travis-update-gh-pages
5 | env:
6 | global:
7 | secure: Pb4yMw9zs7AraJ6yp+xcbJUiaf6WMezaduzrUi5DEqFarGzOvA6ytakNexAU8w/yltmoAhaJw2DOva8PxiGRkPb+dJr3SqYZDW9PepGPFpB5ts2oNZsoSDNtbDHbKh9W1g0zIaPzACBqpwWHZoVEbAs4FWaIdHscgmCwr/kh/6o=
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2011-2014 Nikolay G. Nikolov
2 |
3 | Permission is hereby granted, free of charge, to any person
4 | obtaining a copy of this software and associated documentation
5 | files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use,
7 | copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the
9 | Software is furnished to do so, subject to the following
10 | conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | _ _ ____ _ _ _
3 | | \ | |/ ___| \ | | | |
4 | | \| | | _| \| | __| |__
5 | | |\ | |_| | |\ | / _ _ \
6 | |_| \_|\____|_| \_| / / | | \ \
7 | _ ____ _ | | | | | |
8 | / \ | _ \| | \ \_| |_/ /
9 | / _ \ | |_) | | \__ __/
10 | / ___ \| __/| |___ ____| |____
11 | /_/ \_\_| |_____| |___________|
12 |
13 |
14 | [](https://travis-ci.org/ngn/apl)
15 |
16 | An [APL](https://en.wikipedia.org/wiki/APL_%28programming_language%29) compiler written in [CoffeeScript](http://jashkenas.github.com/coffee-script/)
17 | Runs on [NodeJS](http://nodejs.org/) or in a browser
18 |
19 | **[In-browser demo](http://ngn.github.com/apl/web/index.html)**
20 | (See also: [Paul L Jackson's web site](https://home.comcast.net/~paul.l.jackson/APL.js/) and [repl.it](http://repl.it/languages/APL))
21 |
22 | Supports: most primitives, dfns (`{⍺ ⍵}`), nested arrays, complex numbers
23 | (`1j2`), infinities (`¯` or `∞`), forks and hooks, strand assignment (`(a b)←c`), index
24 | assignment (`a[b]←c`), user-defined operators (`{⍺⍺ ⍵⍵}`)
25 |
26 | Doesn't support: traditional functions (`∇R←X f Y`), non-zero index origin
27 | (`⎕IO`), comparison tolerance (`⎕CT`), prototypes, NaN-s, modified assignment
28 | (`x+←1`), control structures (`:If`), object-oriented features, namespaces
29 |
30 | # Usage
31 |
32 | Install [NodeJS](http://nodejs.org/).
33 |
34 | Download [apl.js](http://ngn.github.io/apl/lib/apl.js) and make it executable:
35 |
36 | wget http://ngn.github.io/apl/lib/apl.js
37 | chmod +x apl.js
38 |
39 | Running `./apl.js` without arguments starts a REPL.
40 |
41 | Running it with an argument executes an APL script:
42 |
43 | ./apl.js filename.apl
44 |
45 | It can be `require()`d as a CommonJS module:
46 |
47 | var apl = require('./apl');
48 | console.log(apl('1 2 3 + 4 5 6').toString());
49 |
50 | or used in an HTML page:
51 |
52 |
53 |
56 |
57 | # Editor support
58 |
59 | [Vim keymap and syntax](https://github.com/ngn/vim-apl)
60 |
--------------------------------------------------------------------------------
/build:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 | cd $(dirname "$0")
4 | coffee=node_modules/.bin/coffee
5 |
6 | if [ ! -x $coffee ]; then
7 | npm install
8 | fi
9 |
10 | # xcat INPUTFILES OUTPUTFILE
11 | # Concatenates INPUTFILES into OUTPUTFILE only if at least one of them is newer than OUTPUTFILE
12 | xcat() { [ -e $2 ] && [ -z "$(find $1 -newer $2)" ] || ( echo "$1 -> $2" && cat $1 >$2 ); }
13 |
14 | # xcoffee COFFEEFILE OUTPUTDIR
15 | # Compiles COFFEEFILE only if newer than the corresponding .js file in OUTPUTDIR
16 | xcoffee() { if [ $1 -nt $2/`basename $1 .coffee`.js ]; then echo "Compiling $1"; $coffee -o $2 -bc $1; fi; }
17 |
18 | # Build lib/apl.js
19 | f=lib/apl.js
20 | if [ ! -e $f ] || [ -n "$(find src \( -name '*.coffee' -o -name '*.apl' \) -newer $f)" ]; then
21 | echo "Building $f"
22 | mkdir -p lib
23 | echo '//usr/bin/env node "$0" $@ ; exit $?' >$f
24 | $coffee -cp src/apl.coffee >>$f
25 | chmod +x $f
26 | fi
27 |
28 | # Build web demo
29 | mkdir -p web web-tmp
30 | xcoffee web-src/index.coffee web-tmp
31 | cp -uv web-src/index.html web/index.html
32 | cp -uv web-src/apl385.ttf web/apl385.ttf
33 | cp -uv web-src/tipsy.gif web/tipsy.gif
34 | xcoffee web-src/examples-gen.coffee web-tmp
35 | i=web-tmp/examples-gen.js ; o=web-tmp/examples.js ; [ $i -nt $o ] && echo "Building $o" && node $i
36 | xcoffee test/collectdoctests.coffee test
37 | xcoffee test/rundoctest.coffee test
38 | xcoffee test/rundoctests.coffee test
39 | o=web/all.js
40 | if [ ! -e $o ] || [ -n "$(find src web-src web-tmp test lib -newer $o)" ]; then
41 | echo "Building $o"
42 | (
43 | cat lib/apl.js \
44 | web-src/jquery.min.js \
45 | web-src/jquery.fieldselection.min.js \
46 | web-src/jquery.keyboard.js \
47 | web-src/jquery.keyboard.extension-typing.js \
48 | web-src/jquery.tipsy.js \
49 | web-tmp/examples.js \
50 | web-tmp/index.js \
51 | test/rundoctest.js
52 | echo -n 'var aplTests = '
53 | node test/collectdoctests.js
54 | ) >$o
55 | fi
56 | xcat 'web-src/index.css web-src/keyboard.css web-src/tipsy.css web-src/apl385.css' web/all.css
57 |
58 | # Build mobile demo
59 | mkdir -p m/images m-tmp
60 | xcoffee m-src/index.coffee m-tmp
61 | cp -uv m-src/index.html m/index.html
62 | cp -uv web-src/apl385.ttf m/apl385.ttf
63 | cp -uv m-src/images/cursor.png m/images/cursor.png
64 | xcat 'lib/apl.js web-src/jquery.min.js m-tmp/index.js' m/all.js
65 | xcat 'web-src/apl385.css m-src/index.css' m/all.css
66 |
67 | # Test
68 |
69 | echo 'Running doctests'
70 | node test/collectdoctests.js | node test/rundoctests.js
71 |
72 | echo 'Running example tests'
73 | examples/test
74 |
75 | echo 'OK'
76 |
--------------------------------------------------------------------------------
/clean:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | rm -rfv lib web web-tmp m m-tmp test/*.js
5 |
--------------------------------------------------------------------------------
/examples/0-rho-iota.apl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env apl
2 |
3 | ⍝ ⍳ n generates a list of numbers from 0 to n-1
4 | ⍝ n n ⍴ A arranges the elements of A in an n×n matrix
5 |
6 | ⎕ ← 5 5 ⍴ ⍳ 25
7 |
--------------------------------------------------------------------------------
/examples/0-rho-iota.out:
--------------------------------------------------------------------------------
1 | 0 1 2 3 4
2 | 5 6 7 8 9
3 | 10 11 12 13 14
4 | 15 16 17 18 19
5 | 20 21 22 23 24
6 |
--------------------------------------------------------------------------------
/examples/1-mult.apl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env apl
2 |
3 | ⍝ Multiplication table
4 | ⍝ a × b scalar multiplication, "a times b"
5 | ⍝ ∘. is the "outer product" operator
6 | ⍝ A ∘.× B every item in A times every item in B
7 | ⎕ ← (⍳ 10) ∘.× ⍳ 10
8 |
--------------------------------------------------------------------------------
/examples/1-mult.out:
--------------------------------------------------------------------------------
1 | 0 0 0 0 0 0 0 0 0 0
2 | 0 1 2 3 4 5 6 7 8 9
3 | 0 2 4 6 8 10 12 14 16 18
4 | 0 3 6 9 12 15 18 21 24 27
5 | 0 4 8 12 16 20 24 28 32 36
6 | 0 5 10 15 20 25 30 35 40 45
7 | 0 6 12 18 24 30 36 42 48 54
8 | 0 7 14 21 28 35 42 49 56 63
9 | 0 8 16 24 32 40 48 56 64 72
10 | 0 9 18 27 36 45 54 63 72 81
11 |
--------------------------------------------------------------------------------
/examples/2-sierpinski.apl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env apl
2 |
3 | ⍝ Sierpinski's triangle
4 |
5 | ⍝ It's a recursively defined figure.
6 | ⍝ We will use the following definition:
7 | ⍝
8 | ⍝ * the Sierpinski triangle of rank 0 is a one-by-one matrix 'X'
9 | ⍝
10 | ⍝ * if S is the triangle of rank n, then rank n+1 would be
11 | ⍝ the two-dimensional catenation:
12 | ⍝ S 0
13 | ⍝ S S
14 | ⍝ where "0" is an all-blank matrix same size as S.
15 |
16 | f ← {(⍵,(⍴⍵)⍴0)⍪⍵,⍵}
17 | S ← {' #'[(f⍣⍵) 1 1 ⍴ 1]}
18 | ⎕ ← S 5
19 |
--------------------------------------------------------------------------------
/examples/2-sierpinski.out:
--------------------------------------------------------------------------------
1 | #
2 | ##
3 | # #
4 | ####
5 | # #
6 | ## ##
7 | # # # #
8 | ########
9 | # #
10 | ## ##
11 | # # # #
12 | #### ####
13 | # # # #
14 | ## ## ## ##
15 | # # # # # # # #
16 | ################
17 | # #
18 | ## ##
19 | # # # #
20 | #### ####
21 | # # # #
22 | ## ## ## ##
23 | # # # # # # # #
24 | ######## ########
25 | # # # #
26 | ## ## ## ##
27 | # # # # # # # #
28 | #### #### #### ####
29 | # # # # # # # #
30 | ## ## ## ## ## ## ## ##
31 | # # # # # # # # # # # # # # # #
32 | ################################
33 |
--------------------------------------------------------------------------------
/examples/3-primes.apl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env apl
2 | ⎕ ← (1=+⌿0=A∘.∣A)/A←2↓⍳100
3 |
--------------------------------------------------------------------------------
/examples/3-primes.out:
--------------------------------------------------------------------------------
1 | 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97
2 |
--------------------------------------------------------------------------------
/examples/4-life.apl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env apl
2 |
3 | ⍝ Conway's game of life
4 |
5 | ⍝ This example was inspired by the impressive demo at
6 | ⍝ http://www.youtube.com/watch?v=a9xAKttWgP4
7 |
8 | ⍝ Create a matrix:
9 | ⍝ 0 1 1
10 | ⍝ 1 1 0
11 | ⍝ 0 1 0
12 | creature ← (3 3 ⍴ ⍳ 9) ∊ 1 2 3 4 7 ⍝ Original creature from demo
13 | creature ← (3 3 ⍴ ⍳ 9) ∊ 1 3 6 7 8 ⍝ Glider
14 |
15 | ⍝ Place the creature on a larger board, near the centre
16 | board ← ¯1 ⊖ ¯2 ⌽ 5 7 ↑ creature
17 |
18 | ⍝ A function to move from one generation to the next
19 | life ← {⊃1 ⍵ ∨.∧ 3 4 = +/ +⌿ 1 0 ¯1 ∘.⊖ 1 0 ¯1 ⌽¨ ⊂⍵}
20 |
21 | ⍝ Compute n-th generation and format it as a
22 | ⍝ character matrix
23 | gen ← {' #'[(life ⍣ ⍵) board]}
24 |
25 | ⍝ Show first three generations
26 | ⎕ ← (gen 1) (gen 2) (gen 3)
27 |
--------------------------------------------------------------------------------
/examples/4-life.out:
--------------------------------------------------------------------------------
1 |
2 |
3 | # # # #
4 | ## # # ##
5 | # ## ##
6 |
--------------------------------------------------------------------------------
/examples/5-rule30.apl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env apl
2 | ⍝ See https://en.wikipedia.org/wiki/Rule_30
3 |
4 | rule ← 30
5 | ruleSet ← ⌽(8/2)⊤rule
6 | n ← 39 ⍝ number of rows to compute
7 | row ← (n/0),1,n/0
8 | table ← (0,⍴row)⍴0
9 | {
10 | table ← table⍪row
11 | row ← ruleSet[(1⌽row)+(2×row)+4ׯ1⌽row]
12 | }¨⍳n
13 | ⎕ ← ' #'[table]
14 |
--------------------------------------------------------------------------------
/examples/5-rule30.out:
--------------------------------------------------------------------------------
1 | #
2 | ###
3 | ## #
4 | ## ####
5 | ## # #
6 | ## #### ###
7 | ## # # #
8 | ## #### ######
9 | ## # ### #
10 | ## #### ## # ###
11 | ## # # #### ## #
12 | ## #### ## # # ####
13 | ## # ### ## ## # #
14 | ## #### ## ### ### ## ###
15 | ## # # ### # ### # #
16 | ## #### ## # # ##### #######
17 | ## # ### #### # ### #
18 | ## #### ## ### ## ## # ###
19 | ## # # ### # ## ### #### ## #
20 | ## #### ## # ###### # # ### ####
21 | ## # ### #### #### ### ## # #
22 | ## #### ## ### # ## # # # ### ###
23 | ## # # ### # ### ## # ### ## # # # #
24 | ## #### ## # ### # # #### # # ## ######
25 | ## # ### #### ## ##### # ##### # # #
26 | ## #### ## ### # ## # # ## # ##### ###
27 | ## # # ### # ## # #### ## # ## ## # ## #
28 | ## #### ## # ### # # # ### #### # ## # ## # ####
29 | ## # ### #### #### ## ## ### # # #### # # #
30 | ## #### ## ### # ## # # ### # ## #### ### ## ###
31 | ## # # ### # ## # # ##### # ###### # # ## # # #
32 | ## #### ## # ### # # #### #### #### ## # # #########
33 | ## # ### #### #### # # ## # ## # # # # #
34 | ## #### ## ### # ## ## ### ## # ### ## # ##### # ## ###
35 | ## # # ### # ## # # ## # # # # # #### # # # ## #
36 | ## #### ## # ### # # #### #### ##### ## ##### # ## # ## ## ####
37 | ## # ### #### #### # # # # # # ### ## # # ### # #
38 | ## #### ## ### # ## ## ### ### ###### ## # # ### # # #### ###
39 | ## # # ### # ## # # ## ### ### # ## ### ## ## # # #### # #
40 |
--------------------------------------------------------------------------------
/examples/6-queens.apl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env apl
2 |
3 | queens←{ ⍝ The N-queens problem.
4 |
5 | search←{ ⍝ Search for all solutions.
6 | (⊂⍬)∊⍵:0⍴⊂⍬ ⍝ stitched: abandon this branch.
7 | 0=⍴⍵:rmdups ⍺ ⍝ all done: solution!
8 | (hd tl)←(↑⍵)(1↓⍵) ⍝ head 'n tail of remaining ranks.
9 | next←⍺∘,¨hd ⍝ possible next steps.
10 | rems←hd free¨⊂tl ⍝ unchecked squares.
11 | ⊃,/next ∇¨rems ⍝ ... in following ranks.
12 | }
13 |
14 | cvex←(1+⍳⍵)×⊂¯1 0 1 ⍝ Checking vectors.
15 |
16 | free←{⍵~¨⍺+(⍴⍵)↑cvex} ⍝ Unchecked squares.
17 |
18 | rmdups←{ ⍝ Ignore duplicate solution.
19 | rots←{{⍒⍵}\4/⊂⍵} ⍝ 4 rotations.
20 | refs←{{⍋⍵}\2/⊂⍵} ⍝ 2 reflections.
21 | best←{(↑⍋⊃⍵)⊃⍵} ⍝ best (=lowest) solution.
22 | all8←,⊃refs¨rots ⍵ ⍝ all 8 orientations.
23 | (⍵≡best all8)⊃⍬(,⊂⍵) ⍝ ignore if not best.
24 | }
25 |
26 | fmt←{ ⍝ Format solution.
27 | chars←'·⍟'[(⊃⍵)∘.=⍳⍺] ⍝ char array of placed queens.
28 | expd←1↓,⊃⍺⍴⊂0 1 ⍝ expansion mask.
29 | ⊃¨↓↓expd\chars ⍝ vector of char matrices.
30 | }
31 |
32 | squares←(⊂⍳⌈⍵÷2),1↓⍵⍴⊂⍳⍵ ⍝ initial squares
33 |
34 | ⍵ fmt ⍬ search squares ⍝ all distinct solutions.
35 | }
36 |
37 | ⎕←queens 5
38 |
--------------------------------------------------------------------------------
/examples/6-queens.out:
--------------------------------------------------------------------------------
1 | ⍟ · · · · · ⍟ · · ·
2 | · · ⍟ · · · · · · ⍟
3 | · · · · ⍟ · · ⍟ · ·
4 | · ⍟ · · · ⍟ · · · ·
5 | · · · ⍟ · · · · ⍟ ·
6 |
--------------------------------------------------------------------------------
/examples/7-mandelbrot.apl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env apl
2 | ⎕←' #'[9>|⊃{⍺+⍵*2}/9⍴⊂¯3×.7j.5-⍉a∘.+0j1×a←(⍳n+1)÷n←98]
3 |
--------------------------------------------------------------------------------
/examples/7-mandelbrot.out:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | ##
15 | #
16 | ##
17 | ######
18 | ######
19 | #####
20 | ######
21 | #########
22 | ###########
23 | ############
24 | ############
25 | ############
26 | #############
27 | ## ################## #
28 | ########################### ##
29 | ##################################
30 | ###################################
31 | ###################################
32 | ##################################
33 | ###################################
34 | ####################################
35 | #####################################
36 | # #####################################
37 | ## # ########################################
38 | ### ### #########################################
39 | #########################################################
40 | #########################################################
41 | #########################################################
42 | #########################################################
43 | ##########################################################
44 | ##########################################################
45 | #############################################################
46 | #############################################################
47 | #############################################################
48 | # ##############################################################
49 | ### ##################################################################
50 | #############################################################################
51 | ### ##################################################################
52 | # ##############################################################
53 | #############################################################
54 | #############################################################
55 | #############################################################
56 | ##########################################################
57 | ##########################################################
58 | #########################################################
59 | #########################################################
60 | #########################################################
61 | #########################################################
62 | ### ### #########################################
63 | ## # ########################################
64 | # #####################################
65 | #####################################
66 | ####################################
67 | ###################################
68 | ##################################
69 | ###################################
70 | ###################################
71 | ##################################
72 | ########################### ##
73 | ## ################## #
74 | #############
75 | ############
76 | ############
77 | ############
78 | ###########
79 | #########
80 | ######
81 | #####
82 | ######
83 | ######
84 | ##
85 | #
86 | ##
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/examples/README:
--------------------------------------------------------------------------------
1 | Examples are prefixed with a number, so they can sort properly in scripts that process them.
2 |
3 | The expected output of X.apl should be in X.out; running ./test ensures that all examples produce their expected outputs.
4 |
--------------------------------------------------------------------------------
/examples/test:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 | cd $(dirname "$0")
4 | for f in *.apl; do
5 | echo "Testing $f"
6 | ../lib/apl.js $f | diff - ${f%.apl}.out # fails if the files are different
7 | done
8 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ngn/apl
5 |
6 |
7 |
10 |
11 |
12 | Demo
13 |
14 |
15 |
--------------------------------------------------------------------------------
/m-src/README:
--------------------------------------------------------------------------------
1 | This directory contains artefacts for the mobile demo. They are processed in
2 | various ways by ../build and the result is ultimately put in ../m/
3 |
4 | ../build also uses some files from ../web-src to avoid repetition.
5 |
--------------------------------------------------------------------------------
/m-src/images/build.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | from subprocess import call
4 |
5 | call(['inkscape', 'cursor.svg', '-e', 'cursor.png'])
6 |
--------------------------------------------------------------------------------
/m-src/images/cursor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PlanetAPL/node-apl/3bbd7ddfe2378be316d0a9b62b098728f324b1a0/m-src/images/cursor.png
--------------------------------------------------------------------------------
/m-src/images/cursor.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
73 |
--------------------------------------------------------------------------------
/m-src/index.coffee:
--------------------------------------------------------------------------------
1 | $.fn.toggleVisibility = ->
2 | @css 'visibility', if @css('visibility') is 'hidden' then '' else 'hidden'
3 |
4 | extractTextFromDOM = (e) ->
5 | if e.nodeType in [3, 4]
6 | e.nodeValue
7 | else if e.nodeType is 1
8 | if e.nodeName.toLowerCase() is 'br'
9 | '\n'
10 | else
11 | c = e.firstChild
12 | r = ''
13 | while c
14 | r += extractTextFromDOM c
15 | c = c.nextSibling
16 | r
17 |
18 | jQuery ($) ->
19 | setInterval (-> $('#cursor').toggleVisibility()), 500
20 |
21 | $('#editor').on 'mousedown touchstart mousemove touchmove', (e) ->
22 | e.preventDefault()
23 | te = e.originalEvent?.touches?[0] ? e
24 | x = te.pageX
25 | y = te.pageY
26 |
27 | # Find the nearest character to (x, y)
28 | # Compare by Δy first, then by Δx
29 | bestDY = bestDX = 1 / 0 # infinity
30 | bestXSide = 0 # 0: must use insertBefore, 1: must use insertAfter
31 | $bestE = null
32 | $('#editor span').each ->
33 | $e = $ @
34 | p = $e.position()
35 | x1 = p.left + $e.width() / 2
36 | y1 = p.top + $e.height() / 2
37 | dx = Math.abs(x1 - x)
38 | dy = Math.abs(y1 - y)
39 | if dy < bestDY or dy is bestDY and dx < bestDX
40 | $bestE = $e
41 | bestDX = dx
42 | bestDY = dy
43 | bestXSide = (x > x1)
44 | return
45 |
46 | if $bestE
47 | if bestXSide
48 | $('#cursor').insertAfter $bestE
49 | else
50 | $('#cursor').insertBefore $bestE
51 |
52 | false
53 |
54 | $('.key').bind 'mousedown touchstart', (event) ->
55 | event.preventDefault()
56 | $k = $ @
57 | $k.addClass 'down'
58 | if $k.hasClass 'repeatable'
59 | $k.data 'timeoutId', setTimeout(
60 | ->
61 | $k.data 'timeoutId', null
62 | $k.trigger 'aplkeypress'
63 | $k.data 'intervalId',
64 | setInterval (-> $k.trigger 'aplkeypress'), 200
65 | return
66 | 500
67 | )
68 | false
69 |
70 | $('.key').bind 'mouseup touchend', (event) ->
71 | event.preventDefault()
72 | $k = $ @
73 | $k.removeClass 'down'
74 | clearTimeout $k.data 'timeoutId'
75 | $k.data 'timeoutId', null
76 | if (iid = $k.data 'intervalId')?
77 | clearInterval iid
78 | $k.data 'intervalId', null
79 | else
80 | $k.trigger 'aplkeypress'
81 | false
82 |
83 | layouts = [
84 | '1234567890qwertyuiopasdfghjklzxcvbnm'
85 | '!@#$%^&*()QWERTYUIOPASDFGHJKLZXCVBNM'
86 | '¨¯<≤=≥>≠∨∧←⍵∊⍴~↑↓⍳○*⍺⌈⌊⍪∇∆∘⋄⎕⊂⊃∩∪⊥⊤∣'
87 | '⍣[]{}«»;⍱⍲,⌽⍷\\⍉\'"⌷⍬⍟⊖+-×⍒⍋/÷⍞⌿⍀⍝.⍎⍕:'
88 | ]
89 | alt = shift = 0
90 |
91 | updateLayout = ->
92 | layout = layouts[2 * alt + shift]
93 | $('.keyboard .key:not(.special)').each (i) -> $(@).text layout[i]
94 | return
95 |
96 | updateLayout()
97 |
98 | actions =
99 | insert: (c) ->
100 | $('').text(c.replace /\ /g, '\xa0').insertBefore '#cursor'
101 | enter: -> $('
').insertBefore '#cursor'
102 | backspace: -> $('#cursor').prev().remove()
103 | exec: ->
104 | try
105 | code = extractTextFromDOM(document.getElementById 'editor')
106 | .replace /\xa0/g, ' '
107 | result = apl code
108 | $('#result').removeClass('error').text "#{apl.format(result).join '\n'}\n"
109 | catch err
110 | console?.error?(err)
111 | $('#result').addClass('error').text err
112 | $('#pageInput').hide()
113 | $('#pageOutput').show()
114 | return
115 |
116 | $('.key:not(.special)').on 'aplkeypress', -> actions.insert $(@).text()
117 | $('.enter').on 'aplkeypress', actions.enter
118 | $('.space').on 'aplkeypress', -> $(' ').insertBefore '#cursor'
119 | $('.bksp' ).on 'aplkeypress', actions.backspace
120 | $('.shift').on 'aplkeypress', -> $(@).toggleClass 'isOn', (shift = 1 - shift); updateLayout()
121 | $('.alt' ).on 'aplkeypress', -> $(@).toggleClass 'isOn', (alt = 1 - alt ); updateLayout()
122 | $('.exec' ).on 'aplkeypress', actions.exec
123 |
124 | $('body').keypress (event) ->
125 | if event.keyCode is 10
126 | actions.exec()
127 | else if event.keyCode is 13
128 | actions.enter()
129 | else
130 | actions.insert String.fromCharCode event.charCode
131 | false
132 |
133 | $('body').keydown (event) ->
134 | if event.keyCode is 8 then actions.backspace()
135 | return
136 |
137 | $('#closeOutputButton').bind 'mouseup touchend', (event) ->
138 | event.preventDefault()
139 | $('#pageInput').show()
140 | $('#pageOutput').hide()
141 | false
142 |
143 | # Bookmarkable source code
144 | hashParams = {}
145 | if location.hash
146 | for nameValue in location.hash.substring(1).split ','
147 | [name, value] = nameValue.split '='
148 | hashParams[name] = unescape value
149 | {code} = hashParams
150 | if code
151 | for c in code
152 | if c is '\n' then actions.enter()
153 | else actions.insert c
154 |
--------------------------------------------------------------------------------
/m-src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | padding: 0;
3 | margin: 0;
4 | font-family: apl;
5 | }
6 |
7 | #editor {
8 | cursor: text;
9 | padding: 10px;
10 | font-size: 24px;
11 | width: 100%;
12 | height: 60%;
13 | position: fixed;
14 | overflow: scroll;
15 | }
16 |
17 | .keyboard {
18 | width: 100%;
19 | height: 40%;
20 | background-color: black;
21 | padding: 3px 0 0 0;
22 | position: fixed;
23 | bottom: 0;
24 | }
25 |
26 | .keyboard .key {
27 | text-align: center;
28 | color: #dda;
29 | cursor: pointer;
30 | border-style: solid;
31 | border-color: #555 #333 #333 #555;
32 | border-width: 2px;
33 | border-radius: 10px;
34 | background-color: #444;
35 | overflow: hidden;
36 | text-overflow: ellipsis;
37 | }
38 |
39 | .keyboard .key.down, .keyboard .alt.isOn, .keyboard .shift.isOn {
40 | border-color: #222 #444 #444 #222;
41 | background-color: #333;
42 | }
43 |
44 | .keyboard .key.exec {
45 | background-color: #474;
46 | border-color: #62a562 #284628 #284628 #62a562;
47 | }
48 |
49 | .keyboard .key.alt, .keyboard td.key.shift {
50 | background-color: #763;
51 | border-color: #ad944a #41381c #41381c #ad944a;
52 | }
53 |
54 | .keyboard .key.alt.isOn, .keyboard td.key.shift.isOn {
55 | color: #aa8;
56 | background-color: #41381c;
57 | border-color: #0c0a05 #534724 #534724 #0c0a05;
58 | }
59 |
60 | .keyboard td {
61 | width: 5%;
62 | }
63 |
64 | .keyboard td.key {
65 | width: 10%;
66 | }
67 |
68 | .keyboard td.key.bksp {
69 | width: 15%;
70 | }
71 |
72 | #result {
73 | white-space: pre;
74 | margin: 15px 15px 60px 15px;
75 | }
76 |
77 | #result.error {
78 | color: red;
79 | }
80 |
81 | #closeOutputButton {
82 | position: fixed;
83 | bottom: 0;
84 | width: 100%;
85 | height: 10%;
86 | font-size: 3ex;
87 | color: #dda;
88 | cursor: pointer;
89 | border-style: solid;
90 | border-color: #555 #333 #333 #555;
91 | border-width: 2px;
92 | border-radius: 10px;
93 | background-color: #444;
94 | }
95 |
--------------------------------------------------------------------------------
/m-src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | NGN APL
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | |
15 | |
16 | |
17 | |
18 | |
19 | |
20 | |
21 | |
22 | |
23 | |
24 |
25 |
26 | |
27 | |
28 | |
29 | |
30 | |
31 | |
32 | |
33 | |
34 | |
35 | |
36 |
37 |
38 | |
39 | |
40 | |
41 | |
42 | |
43 | |
44 | |
45 | |
46 | |
47 | |
48 | |
49 |
50 |
51 | Shift |
52 | |
53 | |
54 | |
55 | |
56 | |
57 | |
58 | |
59 | Bksp |
60 |
61 |
62 | Alt |
63 | |
64 | Enter |
65 | Exec |
66 |
67 |
68 |
69 |
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "apl",
3 | "version": "0.1.15",
4 | "directories": {
5 | "lib": "./lib",
6 | "bin": "./lib"
7 | },
8 | "scripts": {
9 | "start": "./lib/apl.js"
10 | },
11 | "main": "./lib/apl.js",
12 | "devDependencies": {
13 | "coffee-script": "git+https://github.com/paiq/blackcoffee#4e1403ec30",
14 | "macronym": "git+https://github.com/ngn/macronym",
15 | "glob": ""
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/apl.coffee:
--------------------------------------------------------------------------------
1 | macro -> macro.fileToNode 'node_modules/macronym/assert.coffee'
2 |
3 | macro ->
4 | @tmpCounter = 0
5 | @tmp = -> "t#{@tmpCounter++}"
6 | return
7 |
8 | # Make it possible to use ⍺ and ⍵ as identifiers in CoffeeScript code
9 | macro withAlphaAndOmega (f) ->
10 | f.body.subst
11 | '⍺': macro.codeToNode -> alpha
12 | '⍵': macro.codeToNode -> omega
13 |
14 | macro include (f) -> macro.fileToNode "src/#{macro.nodeToVal f}.coffee"
15 | include 'helpers'
16 | include 'errors'
17 | include 'array'
18 | include 'complex'
19 | include 'vm'
20 | include 'lexer'
21 | include 'parser'
22 |
23 | vocabulary = {}
24 | addVocabulary = (h) ->
25 | for k, v of h then vocabulary[k] = v
26 | return
27 |
28 | withAlphaAndOmega ->
29 | include 'vocabulary/vhelpers'
30 | include 'vocabulary/arithmetic'
31 | include 'vocabulary/backslash'
32 | include 'vocabulary/circle'
33 | include 'vocabulary/comma'
34 | include 'vocabulary/comparisons'
35 | include 'vocabulary/compose'
36 | include 'vocabulary/cupcap'
37 | include 'vocabulary/decode'
38 | include 'vocabulary/dot'
39 | include 'vocabulary/each'
40 | include 'vocabulary/encode'
41 | include 'vocabulary/epsilon'
42 | include 'vocabulary/exclamation'
43 | include 'vocabulary/execute'
44 | include 'vocabulary/find'
45 | include 'vocabulary/floorceil'
46 | include 'vocabulary/fork'
47 | include 'vocabulary/format'
48 | include 'vocabulary/grade'
49 | include 'vocabulary/identity'
50 | include 'vocabulary/iota'
51 | include 'vocabulary/leftshoe'
52 | include 'vocabulary/logic'
53 | include 'vocabulary/poweroperator'
54 | include 'vocabulary/quad'
55 | include 'vocabulary/question'
56 | include 'vocabulary/raise'
57 | include 'vocabulary/rho'
58 | include 'vocabulary/rotate'
59 | include 'vocabulary/slash'
60 | include 'vocabulary/squish'
61 | include 'vocabulary/take'
62 | include 'vocabulary/transpose'
63 | include 'vocabulary/variant'
64 | include 'compiler'
65 |
66 | @apl = apl = (aplCode, opts) -> (apl.ws opts) aplCode
67 | extend apl, {format, approx, parse, compileAST, repr}
68 | apl.ws = (opts = {}) ->
69 | ctx = Object.create vocabulary
70 | if opts.in then ctx['get_⎕'] = ctx['get_⍞'] = -> s = opts.in(); assert typeof s is 'string'; new APLArray s
71 | if opts.out then ctx['set_⎕'] = ctx['set_⍞'] = (x) -> opts.out format(x).join('\n') + '\n'
72 | (aplCode) -> exec aplCode, {ctx}
73 |
74 | if module?
75 | module.exports = apl
76 | if module is require?.main then do ->
77 | usage = 'Usage: apl.js filename.apl\n'
78 | file = null
79 | for arg in process.argv[2..]
80 | if arg in ['-h', '--help'] then (process.stderr.write usage; process.exit 0)
81 | else if /^-/.test arg then (process.stderr.write "unrecognized option: #{arg}\n#{usage}"; process.exit 1)
82 | else if file? then (process.stderr.write usage; process.exit 1)
83 | else file = arg
84 | if file?
85 | exec require('fs').readFileSync file, 'utf8'
86 | else if !require('tty').isatty()
87 | exec Buffer.concat(loop # read all of stdin
88 | b = new Buffer 1024
89 | break unless (k = require('fs').readSync 0, b, 0, b.length, null)
90 | b.slice 0, k
91 | ).toString 'utf8'
92 | else
93 | process.stdout.write macro -> macro.valToNode "ngn apl #{(new Date).toISOString().replace /T.*/, ''}\n"
94 | rl = require('readline').createInterface process.stdin, process.stdout
95 | rl.setPrompt ' '
96 | ws = apl.ws()
97 | rl.on 'line', (line) ->
98 | try
99 | if !line.match /^[\ \t\f\r\n]*$/
100 | process.stdout.write format(ws line).join('\n') + '\n'
101 | catch e
102 | process.stdout.write e + '\n'
103 | rl.prompt()
104 | return
105 | rl.on 'close', -> process.stdout.write '\n'; process.exit 0
106 | rl.prompt()
107 | return
108 |
--------------------------------------------------------------------------------
/src/array.coffee:
--------------------------------------------------------------------------------
1 | macro ⍴ (a) -> macro.codeToNode(-> (a).shape).subst {a}
2 | macro ⍴⍴ (a) -> macro.codeToNode(-> (a).shape.length).subst {a}
3 |
4 | # each() is a hygienic macro that efficiently iterates the elements of an
5 | # APLArray in ravel order. No function calls are made during iteration as
6 | # those are relatively expensive in JavaScript.
7 | macro each (a0, f) ->
8 | (macro.codeToNode ->
9 | a = a0
10 | if !a.empty()
11 | data = a.data
12 | shape = ⍴ a
13 | stride = a.stride
14 | lastAxis = shape.length - 1
15 | p = a.offset
16 | indices = []
17 | axis = shape.length
18 | while --axis >= 0 then indices.push 0
19 | loop
20 | x = data[p]
21 | body
22 | axis = lastAxis
23 | while axis >= 0 and indices[axis] + 1 is shape[axis]
24 | p -= indices[axis] * stride[axis]
25 | indices[axis--] = 0
26 | if axis < 0 then break
27 | indices[axis]++
28 | p += stride[axis]
29 | ).subst
30 | a0: a0
31 | body: f.body
32 | a: macro.csToNode @tmp()
33 | axis: macro.csToNode @tmp()
34 | data: macro.csToNode @tmp()
35 | shape: macro.csToNode @tmp()
36 | stride: macro.csToNode @tmp()
37 | lastAxis: macro.csToNode @tmp()
38 | x: macro.csToNode f.params[0].name.value
39 | indices: macro.csToNode f.params[1]?.name?.value ? @tmp()
40 | p: macro.csToNode f.params[2]?.name?.value ? @tmp()
41 |
42 | # each2() is like each() but it iterates over two APLArray-s in parallel
43 | macro each2 (a0, b0, f) ->
44 | (macro.codeToNode ->
45 | a = a0; data = a.data; shape = a.shape; stride = a.stride
46 | b = b0; data1 = b.data; shape1 = b.shape; stride1 = b.stride
47 | if shape.length isnt shape1.length then rankError()
48 | axis = shape.length
49 | while --axis >= 0 when shape[axis] isnt shape1[axis] then lengthError()
50 | if !a.empty()
51 | lastAxis = shape.length - 1
52 | p = a.offset
53 | q = b.offset
54 | indices = Array (axis = shape.length)
55 | while --axis >= 0 then indices[axis] = 0
56 | loop
57 | x = data[p]
58 | y = data1[q]
59 | body
60 | axis = lastAxis
61 | while axis >= 0 and indices[axis] + 1 is shape[axis]
62 | p -= indices[axis] * stride[axis]
63 | q -= indices[axis] * stride1[axis]
64 | indices[axis--] = 0
65 | if axis < 0 then break
66 | indices[axis]++
67 | p += stride[axis]
68 | q += stride1[axis]
69 | ).subst
70 | a0: a0
71 | b0: b0
72 | body: f.body
73 | a: macro.csToNode @tmp()
74 | b: macro.csToNode @tmp()
75 | p: macro.csToNode @tmp()
76 | q: macro.csToNode @tmp()
77 | axis: macro.csToNode @tmp()
78 | data: macro.csToNode @tmp()
79 | data1: macro.csToNode @tmp()
80 | shape: macro.csToNode @tmp()
81 | shape1: macro.csToNode @tmp()
82 | stride: macro.csToNode @tmp()
83 | stride1: macro.csToNode @tmp()
84 | lastAxis: macro.csToNode @tmp()
85 | x: macro.csToNode f.params[0].name.value
86 | y: macro.csToNode f.params[1].name.value
87 | indices: macro.csToNode f.params[2]?.name?.value ? @tmp()
88 |
89 |
90 | class APLArray
91 |
92 | constructor: (@data, @shape, @stride, @offset = 0) ->
93 | @shape ?= [@data.length]
94 | @stride ?= strideForShape @shape
95 | assert @data.length?
96 | assert @shape.length?
97 | assert @stride.length?
98 | assert @data.length is 0 or isInt @offset, 0, @data.length
99 | assert @stride.length is ⍴⍴ @
100 | for x in @shape then assert isInt x, 0
101 | if @data.length
102 | for x, i in @stride then assert isInt x, -@data.length, @data.length + 1
103 |
104 | empty: ->
105 | for d in @shape when !d then return true
106 | false
107 |
108 | map: (f) ->
109 | assert typeof f is 'function'
110 | data = []
111 | each @, (x, indices) -> data.push f x, indices
112 | new APLArray data, @shape
113 |
114 | map2: (a, f) ->
115 | assert a instanceof APLArray
116 | assert typeof f is 'function'
117 | data = []
118 | each2 @, a, (x, y, indices) -> data.push f x, y, indices
119 | new APLArray data, @shape
120 |
121 | toArray: ->
122 | r = []
123 | each @, (x) -> r.push x
124 | r
125 |
126 | toInt: (start = -Infinity, end = Infinity) ->
127 | r = @unwrap()
128 | if typeof r isnt 'number' or r isnt ~~r or !(start <= r < end) then domainError() else r
129 |
130 | toBool: -> @toInt 0, 2
131 |
132 | toSimpleString: ->
133 | if ⍴⍴(@) > 1 then rankError()
134 | if typeof @data is 'string'
135 | if !⍴⍴ @ then return @data[@offset]
136 | if ⍴(@)[0] is 0 then return ''
137 | if @stride[0] is 1 then return @data[@offset ... @offset + @shape[0]]
138 | @toArray().join ''
139 | else
140 | a = @toArray()
141 | for x in a when typeof x isnt 'string' then domainError()
142 | a.join ''
143 |
144 | isSingleton: ->
145 | for n in @shape when n isnt 1 then return false
146 | true
147 |
148 | isSimple: -> ⍴⍴(@) is 0 and @data[@offset] !instanceof APLArray
149 | unwrap: -> if prod(⍴ @) is 1 then @data[@offset] else lengthError()
150 | getPrototype: -> if @empty() or typeof @data[@offset] isnt 'string' then 0 else ' ' # todo
151 | toString: -> format(@).join '\n'
152 | repr: -> "new APLArray(#{repr @data},#{repr @shape},#{repr @stride},#{repr @offset})"
153 |
154 | strideForShape = (shape) ->
155 | assert shape.length?
156 | if shape.length is 0 then return []
157 | r = Array shape.length
158 | r[r.length - 1] = 1
159 | for i in [r.length - 2 .. 0] by -1
160 | assert isInt shape[i], 0
161 | r[i] = r[i + 1] * shape[i + 1]
162 | r
163 |
164 | APLArray.zero = new APLArray [0], []
165 | APLArray.one = new APLArray [1], []
166 | APLArray.zilde = new APLArray [], [0]
167 | APLArray.scalar = (x) -> new APLArray [x], []
168 | APLArray.bool = [APLArray.zero, APLArray.one]
169 |
--------------------------------------------------------------------------------
/src/compiler.coffee:
--------------------------------------------------------------------------------
1 | [NOUN, VERB, ADVERB, CONJUNCTION] = [1..4]
2 |
3 | exec = (aplCode, opts = {}) ->
4 | ast = parse aplCode, opts
5 | code = compileAST ast, opts
6 | env = [prelude.env[0][..]]
7 | for k, v of ast.vars then env[0][v.slot] = opts.ctx[k]
8 | result = vm {code, env}
9 | for k, v of ast.vars
10 | x = opts.ctx[k] = env[0][v.slot]
11 | if v.category is ADVERB then x.isAdverb = true
12 | if v.category is CONJUNCTION then x.isConjunction = true
13 | result
14 |
15 | repr = (x) ->
16 | if x is null or typeof x in ['string', 'number', 'boolean'] then JSON.stringify x
17 | else if x instanceof Array then "[#{(for y in x then repr y).join ','}]"
18 | else if x.repr then x.repr()
19 | else "{#{(for k, v of x then "#{repr k}:#{repr v}").join ','}}"
20 |
21 | compileAST = (ast, opts = {}) ->
22 | ast.scopeDepth = 0
23 | ast.nSlots = prelude.nSlots
24 | ast.vars = Object.create prelude.vars
25 | opts.ctx ?= Object.create vocabulary
26 | for key, value of opts.ctx when !ast.vars[key]
27 | ast.vars[key] = varInfo = category: NOUN, slot: ast.nSlots++, scopeDepth: ast.scopeDepth
28 | if typeof value is 'function' or value instanceof λ
29 | varInfo.category = if value.isAdverb then ADVERB else if value.isConjunction then CONJUNCTION else VERB
30 | if /^[gs]et_.*/.test key then ast.vars[key[4..]] = category: NOUN
31 |
32 | err = (node, message) ->
33 | syntaxError message, file: opts.file, offset: node.offset, aplCode: opts.aplCode
34 |
35 | assert VERB < ADVERB < CONJUNCTION # we are relying on this ordering below
36 | (categorizeLambdas = (node) ->
37 | switch node[0]
38 | when 'B', ':', '←', '[', '{', '.', '⍬'
39 | r = VERB
40 | for i in [1...node.length] by 1 when node[i] then r = Math.max r, categorizeLambdas node[i]
41 | if node[0] is '{' then (node.category = r; VERB) else r
42 | when 'S', 'N', 'J' then 0
43 | when 'X'
44 | switch node[1]
45 | when '⍺⍺', '⍶', '∇∇' then ADVERB
46 | when '⍵⍵', '⍹' then CONJUNCTION
47 | else VERB
48 | else assert 0
49 | ) ast
50 |
51 | queue = [ast] # accumulates "body" nodes which we encounter on our way
52 | while queue.length
53 | {vars} = scopeNode = queue.shift()
54 |
55 | visit = (node) ->
56 | node.scopeNode = scopeNode
57 | switch node[0]
58 | when ':' then r = visit node[1]; visit node[2]; r
59 | when '←' then visitLHS node[1], visit node[2]
60 | when 'X'
61 | name = node[1]
62 | if (v = vars["get_#{name}"])?.category is VERB
63 | NOUN
64 | else
65 | # x ⋄ x←0 !!! VALUE ERROR
66 | vars[name]?.category or
67 | valueError "Symbol '#{name}' is referenced before assignment.",
68 | file: opts.file, offset: node.offset, aplCode: opts.aplCode
69 | when '{'
70 | for i in [1...node.length]
71 | queue.push extend (body = node[i]),
72 | scopeNode: scopeNode
73 | scopeDepth: d = scopeNode.scopeDepth + 1 + (node.category isnt VERB)
74 | nSlots: 4
75 | vars: v = extend Object.create(vars),
76 | '⍵': slot: 0, scopeDepth: d, category: NOUN
77 | '∇': slot: 1, scopeDepth: d, category: VERB
78 | '⍺': slot: 2, scopeDepth: d, category: NOUN
79 | # slot 3 is reserved for a "base pointer"
80 | '⍫': scopeDepth: d, category: VERB
81 | if node.category is CONJUNCTION
82 | v['⍵⍵'] = v['⍹'] = slot: 0, scopeDepth: d - 1, category: VERB
83 | v['∇∇'] = slot: 1, scopeDepth: d - 1, category: CONJUNCTION
84 | v['⍺⍺'] = v['⍶'] = slot: 2, scopeDepth: d - 1, category: VERB
85 | else if node.category is ADVERB
86 | v['⍺⍺'] = v['⍶'] = slot: 0, scopeDepth: d - 1, category: VERB
87 | v['∇∇'] = slot: 1, scopeDepth: d - 1, category: ADVERB
88 | node.category ? VERB
89 | when 'S', 'N', 'J', '⍬' then NOUN
90 | when '['
91 | for i in [2...node.length] by 1 when node[i] and visit(node[i]) isnt NOUN then err node, 'Indices must be nouns.'
92 | visit node[1]
93 | when '.'
94 | a = node[1..]
95 | h = Array a.length
96 | for i in [a.length - 1..0] by -1 then h[i] = visit a[i]
97 | # Form vectors from sequences of data
98 | i = 0
99 | while i < a.length - 1
100 | if h[i] is h[i + 1] is NOUN
101 | j = i + 2
102 | while j < a.length and h[j] is NOUN then j++
103 | a[i...j] = [['V'].concat a[i...j]]
104 | h[i...j] = NOUN
105 | else
106 | i++
107 | # Apply conjunctions
108 | i = a.length - 2
109 | while --i >= 0
110 | if h[i + 1] is CONJUNCTION and (h[i] isnt NOUN or h[i + 2] isnt NOUN)
111 | a[i...i+3] = [['C'].concat a[i...i+3]]
112 | h[i...i+3] = VERB
113 | i--
114 | # Apply postfix adverbs
115 | i = 0
116 | while i < a.length - 1
117 | if h[i] isnt NOUN and h[i + 1] is ADVERB
118 | a[i...i+2] = [['A'].concat a[i...i+2]]
119 | h[i...i+2] = VERB
120 | else
121 | i++
122 | # Hooks
123 | if h.length is 2 and h[0] isnt NOUN and h[1] isnt NOUN
124 | a = [['H'].concat a]
125 | h = [VERB]
126 | # Forks
127 | if h.length >= 3 and h.length % 2 is 1 and all(for x in h then x isnt NOUN)
128 | a = [['F'].concat a]
129 | h = [VERB]
130 | if h[h.length - 1] isnt NOUN
131 | if h.length > 1 then err a[h.length - 1], 'Trailing function in expression'
132 | else
133 | # Apply monadic and dyadic functions
134 | while h.length > 1
135 | if h.length is 2 or h[h.length - 3] isnt NOUN
136 | a[-2..] = [['M'].concat a[-2..]]
137 | h[-2..] = NOUN
138 | else
139 | a[-3..] = [['D'].concat a[-3..]]
140 | h[-3..] = NOUN
141 | node[..] = a[0]
142 | extend node, a[0]
143 | h[0]
144 | else
145 | assert 0
146 |
147 | visitLHS = (node, rhsCategory) ->
148 | node.scopeNode = scopeNode
149 | switch node[0]
150 | when 'X'
151 | name = node[1]
152 | if name in '∇⍫' then err node, "Assignment to #{name} is not allowed."
153 | if vars[name]
154 | if vars[name].category isnt rhsCategory
155 | err node, "Inconsistent usage of symbol '#{name}', it is assigned both nouns and verbs."
156 | else
157 | vars[name] = scopeDepth: scopeNode.scopeDepth, slot: scopeNode.nSlots++, category: rhsCategory
158 | when '.'
159 | rhsCategory is NOUN or err node, 'Strand assignment can be used only for nouns.'
160 | for i in [1...node.length] by 1 then visitLHS node[i], rhsCategory
161 | when '['
162 | rhsCategory is NOUN or err node, 'Index assignment can be used only for nouns.'
163 | visitLHS node[1], rhsCategory
164 | for i in [2...node.length] by 1 when c = node[i] then visit c
165 | else
166 | err node, "Invalid LHS node type: #{JSON.stringify node[0]}"
167 | rhsCategory
168 |
169 | for i in [1...scopeNode.length] by 1 then visit scopeNode[i]
170 |
171 | render = (node) ->
172 | switch node[0]
173 | when 'B'
174 | if node.length is 1
175 | # {}0 ←→ ⍬
176 | [LDC, APLArray.zilde, RET]
177 | else
178 | a = []
179 | for i in [1...node.length] by 1 then a.push render(node[i])...; a.push POP
180 | a[a.length - 1] = RET
181 | a
182 | when ':'
183 | x = render node[1]
184 | y = render node[2]
185 | x.concat JEQ, y.length + 2, POP, y, RET
186 | when '←'
187 | # A←5 ←→ 5
188 | # A×A←2 5 ←→ 4 25
189 | render(node[2]).concat renderLHS node[1]
190 | when 'X'
191 | # r←3 ⋄ get_c←{2×○r} ⋄ get_S←{○r*2}
192 | # ... before←.01×⌊100×r c S
193 | # ... r←r+1
194 | # ... after←.01×⌊100×r c S
195 | # ... before after ←→ (3 18.84 28.27)(4 25.13 50.26)
196 | # {⍺}0 !!! VALUE ERROR
197 | # {x}0 ⋄ x←0 !!! VALUE ERROR
198 | # {⍫1⋄2}⍬ ←→ 1
199 | # c←{} ⋄ x←{c←⍫⋄1}⍬ ⋄ {x=1:c 2⋄x}⍬ ←→ 2
200 | name = node[1]
201 | {vars} = node.scopeNode
202 | if name is '⍫'
203 | [CON]
204 | else if (v = vars["get_#{name}"])?.category is VERB
205 | [LDC, APLArray.zero, GET, v.scopeDepth, v.slot, MON]
206 | else
207 | v = vars[name]
208 | [GET, v.scopeDepth, v.slot]
209 | when '{'
210 | # {1 + 1} 1 ←→ 2
211 | # {⍵=0:1 ⋄ 2×∇⍵-1} 5 ←→ 32 # two to the power of
212 | # {⍵<2 : 1 ⋄ (∇⍵-1)+(∇⍵-2) } 8 ←→ 34 # Fibonacci sequence
213 | # ⊂{⍺⍺ ⍺⍺ ⍵}'hello' ←→ ⊂⊂'hello'
214 | # ⊂{⍺⍺ ⍵⍵ ⍵}⌽'hello' ←→ ⊂'olleh'
215 | # ⊂{⍶⍶⍵}'hello' ←→ ⊂⊂'hello'
216 | # ⊂{⍶⍹⍵}⌽'hello' ←→ ⊂'olleh'
217 | # +{⍵⍶⍵}10 20 30 ←→ 20 40 60
218 | # f←{⍵⍶⍵} ⋄ +f 10 20 30 ←→ 20 40 60
219 | # twice←{⍶⍶⍵} ⋄ *twice 2 ←→ 1618.1779919126539
220 | # f←{-⍵;⍺×⍵} ⋄ (f 5)(3 f 5) ←→ ¯5 15
221 | # f←{;} ⋄ (f 5)(3 f 5) ←→ ⍬⍬
222 | # ²←{⍶⍶⍵;⍺⍶⍺⍶⍵} ⋄ *²2 ←→ 1618.1779919126539
223 | # ²←{⍶⍶⍵;⍺⍶⍺⍶⍵} ⋄ 3*²2 ←→ 19683
224 | # H←{⍵⍶⍹⍵;⍺⍶⍹⍵} ⋄ +H÷ 2 ←→ 2.5
225 | # H←{⍵⍶⍹⍵;⍺⍶⍹⍵} ⋄ 7 +H÷ 2 ←→ 7.5
226 | # {;;} !!!
227 | x = render node[1]
228 | lx = [LAM, x.length].concat x
229 | f = switch node.length
230 | when 2 then lx
231 | when 3
232 | y = render node[2]
233 | ly = [LAM, y.length].concat y
234 | v = node.scopeNode.vars['⍠']
235 | ly.concat GET, v.scopeDepth, v.slot, lx, DYA
236 | else err node
237 | if node.category isnt VERB then [LAM, f.length + 1].concat f, RET else f
238 | when 'S'
239 | # ⍴'' ←→ ,0
240 | # ⍴'x' ←→ ⍬
241 | # ⍴'xx' ←→ ,2
242 | # ⍴'a''b' ←→ ,3
243 | # ⍴"a""b" ←→ ,3
244 | # ⍴'a""b' ←→ ,4
245 | # ⍴'''a' ←→ ,2
246 | # ⍴'a''' ←→ ,2
247 | # '''' ←→ "'"
248 | # ⍴"\f\t\n\r\u1234\xff" ←→ ,18
249 | # "a !!!
250 | d = node[1][0] # the delimiter: '"' or "'"
251 | s = node[1][1...-1].replace ///#{d + d}///g, d
252 | [LDC, new APLArray s, if s.length is 1 then []]
253 | when 'N'
254 | # ∞ ←→ ¯
255 | # ¯∞ ←→ ¯¯
256 | # ¯∞j¯∞ ←→ ¯¯j¯¯
257 | # ∞∞ ←→ ¯ ¯
258 | # ∞¯ ←→ ¯ ¯
259 | a = for x in node[1].replace(/[¯∞]/g, '-').split /j/i
260 | if x is '-' then Infinity
261 | else if x is '--' then -Infinity
262 | else if x.match /^-?0x/i then parseInt x, 16
263 | else parseFloat x
264 | v = if a[1] then new Complex(a[0], a[1]) else a[0]
265 | [LDC, new APLArray [v], []]
266 | when 'J'
267 | # 123 + «456 + 789» ←→ 1368
268 | f = do Function "return function(_w,_a){return(#{node[1].replace /^«|»$/g, ''})};"
269 | [EMB, (_w, _a) -> aplify f _w, _a]
270 | when '['
271 | # ⍴ x[⍋x←6?40] ←→ ,6
272 | v = node.scopeNode.vars._index
273 | axes = []
274 | a = []
275 | for i in [2...node.length] by 1 when c = node[i] then axes.push i - 2; a.push render(c)...
276 | a.push VEC, axes.length, LDC, new APLArray(axes), VEC, 2, GET, v.scopeDepth, v.slot
277 | a.push render(node[1])...
278 | a.push DYA
279 | a
280 | when 'V'
281 | fragments = for i in [1...node.length] by 1 then render node[i]
282 | if all(for f in fragments then f.length is 2 and f[0] is LDC)
283 | [LDC, new APLArray(for f in fragments then (if (x = f[1]).isSimple() then x.unwrap() else x))]
284 | else
285 | [].concat fragments..., VEC, node.length - 1
286 | when '⍬' then [LDC, APLArray.zilde]
287 | when 'M' then render(node[2]).concat render(node[1]), MON
288 | when 'A' then render(node[1]).concat render(node[2]), MON
289 | when 'D', 'C' then render(node[3]).concat render(node[2]), render(node[1]), DYA
290 | when 'H'
291 | v = node.scopeNode.vars._hook
292 | render(node[2]).concat GET, v.scopeDepth, v.slot, render(node[1]), DYA
293 | when 'F'
294 | u = node.scopeNode.vars._hook
295 | v = node.scopeNode.vars._fork1
296 | w = node.scopeNode.vars._fork2
297 | i = node.length - 1
298 | r = render node[i--]
299 | while i >= 2
300 | r = r.concat(
301 | GET, v.scopeDepth, v.slot, render(node[i--]), DYA,
302 | GET, w.scopeDepth, w.slot, render(node[i--]), DYA
303 | )
304 | if i then r.concat render(node[1]), GET, u.scopeDepth, u.slot, DYA else r
305 | else assert 0
306 |
307 | renderLHS = (node) ->
308 | switch node[0]
309 | when 'X'
310 | name = node[1]
311 | {vars} = node.scopeNode
312 | if (v = vars["set_#{name}"])?.category is VERB
313 | [GET, v.scopeDepth, v.slot, MON]
314 | else
315 | v = vars[name]
316 | [SET, v.scopeDepth, v.slot]
317 | when '.' # strand assignment
318 | # (a b) ← 1 2 ⋄ a ←→ 1
319 | # (a b) ← 1 2 ⋄ b ←→ 2
320 | # (a b) ← + !!!
321 | # (a b c) ← 3 4 5 ⋄ a b c ←→ 3 4 5
322 | # (a b c) ← 6 ⋄ a b c ←→ 6 6 6
323 | # (a b c) ← 7 8 ⋄ a b c !!!
324 | # ((a b)c)←3(4 5) ⋄ a b c ←→ 3 3 (4 5)
325 | n = node.length - 1
326 | a = [SPL, n]
327 | for i in [1...node.length] by 1 then a.push renderLHS(node[i])...; a.push POP
328 | a
329 | when '[' # index assignment
330 | v = node.scopeNode.vars._substitute
331 | axes = []
332 | a = []
333 | for i in [2...node.length] by 1 when c = node[i] then axes.push i - 2; a.push render(c)...
334 | a.push VEC, axes.length
335 | a.push render(node[1])...
336 | a.push LDC, new APLArray(axes), VEC, 4, GET, v.scopeDepth, v.slot, MON
337 | a.push renderLHS(node[1])...
338 | a
339 | else
340 | assert 0
341 |
342 | render ast
343 |
344 | prelude = do ->
345 | {code, nSlots, vars} = macro ->
346 | fs = macro.require 'fs'
347 | {parse, compileAST, repr} = macro.require "#{process.cwd()}/old-apl"
348 | ast = parse fs.readFileSync "#{process.cwd()}/src/prelude.apl", 'utf8'
349 | code = compileAST ast
350 | macro.jsToNode repr code: code, nSlots: ast.nSlots, vars: ast.vars
351 | env = [[]]
352 | for k, v of vars then env[0][v.slot] = vocabulary[k]
353 | vm {code, env}
354 | for k, v of vars then vocabulary[k] = env[0][v.slot]
355 | {nSlots, vars, env}
356 |
357 | aplify = (x) ->
358 | if typeof x is 'string' then (if x.length is 1 then APLArray.scalar x else new APLArray x)
359 | else if typeof x is 'number' then APLArray.scalar x
360 | else if x instanceof Array
361 | new APLArray(for y in x then (y = aplify y; if ⍴⍴ y then y else y.unwrap()))
362 | else if x instanceof APLArray then x
363 | else aplError 'Cannot aplify object ' + x
364 |
--------------------------------------------------------------------------------
/src/complex.coffee:
--------------------------------------------------------------------------------
1 | # complexify(x)
2 | # * if x is real, it's converted to a Complex instance with imaginary part 0
3 | # * if x is already complex, it's preserved
4 | complexify = (x) ->
5 | if typeof x is 'number'
6 | new Complex x, 0
7 | else if x instanceof Complex
8 | x
9 | else
10 | domainError()
11 |
12 | # simplify(re, im)
13 | # * if the imaginary part is 0, the real part is returned
14 | # * otherwise, a Complex instance is created
15 | simplify = (re, im) -> if im isnt 0 then new Complex re, im else re
16 |
17 | class Complex
18 |
19 | constructor: (@re, @im = 0) ->
20 | assert typeof @re is 'number'
21 | assert typeof @im is 'number'
22 | if isNaN(@re) or isNaN(@im) then domainError 'NaN'
23 |
24 | toString: -> "#{formatNumber @re}J#{formatNumber @im}"
25 | repr: -> "new Complex(#{repr @re},#{repr @im})"
26 |
27 | @exp = exp = (x) ->
28 | x = complexify x
29 | r = Math.exp x.re
30 | simplify(
31 | r * Math.cos x.im
32 | r * Math.sin x.im
33 | )
34 |
35 | @log = log = (x) ->
36 | if typeof x is 'number' and x > 0
37 | Math.log x
38 | else
39 | x = complexify x
40 | simplify(
41 | Math.log Math.sqrt x.re * x.re + x.im * x.im
42 | direction x
43 | )
44 |
45 | @conjugate = (x) -> new Complex x.re, -x.im
46 |
47 | @negate = negate = (x) -> new Complex -x.re, -x.im
48 |
49 | @itimes = itimes = (x) ->
50 | x = complexify x
51 | simplify -x.im, x.re
52 |
53 | @negitimes = negitimes = (x) ->
54 | x = complexify x
55 | simplify x.im, -x.re
56 |
57 | @add = add = (x, y) ->
58 | x = complexify x
59 | y = complexify y
60 | simplify x.re + y.re, x.im + y.im
61 |
62 | @subtract = subtract = (x, y) ->
63 | x = complexify x
64 | y = complexify y
65 | simplify x.re - y.re, x.im - y.im
66 |
67 | @multiply = multiply = (x, y) ->
68 | x = complexify x
69 | y = complexify y
70 | simplify x.re * y.re - x.im * y.im, x.re * y.im + x.im * y.re
71 |
72 | @divide = divide = (x, y) ->
73 | x = complexify x
74 | y = complexify y
75 | d = y.re * y.re + y.im * y.im
76 | simplify (x.re * y.re + x.im * y.im) / d, (y.re * x.im - y.im * x.re) / d
77 |
78 | @pow = pow = (x, y) ->
79 | if typeof x is typeof y is 'number' and x >= 0
80 | Math.pow x, y
81 | else
82 | exp multiply(y, log x)
83 |
84 | @sqrt = sqrt = (x) ->
85 | if typeof x is 'number' and x >= 0
86 | Math.sqrt x
87 | else
88 | pow x, 0.5
89 |
90 | @magnitude = (x) ->
91 | Math.sqrt x.re * x.re + x.im * x.im
92 |
93 | @direction = direction = (x) ->
94 | Math.atan2 x.im, x.re
95 |
96 | @sin = (x) -> negitimes sinh itimes x
97 |
98 | @cos = (x) -> cosh itimes x
99 |
100 | @tan = (x) -> negitimes tanh itimes x
101 |
102 | @asin = asin = (x) -> # arcsin x = -i ln(ix + sqrt(1 - x^2))
103 | x = complexify x
104 | negitimes log add(
105 | itimes x
106 | sqrt subtract 1, pow x, 2
107 | )
108 |
109 | @acos = acos = (x) -> # arccos x = -i ln(x + i sqrt(x^2 - 1))
110 | x = complexify x
111 | r = negitimes log add(
112 | x
113 | sqrt subtract pow(x, 2), 1
114 | )
115 | # TODO look up the algorithm for determining the sign of arccos; the following line is dubious
116 | if r instanceof Complex and (r.re < 0 or (r.re is 0 and r.im < 0)) then negate r else r
117 |
118 | @atan = atan = (x) -> # arctan x = (i/2) (ln(1-ix) - ln(1+ix))
119 | x = complexify x
120 | ix = itimes x
121 | multiply new Complex(0, .5), subtract(
122 | log subtract 1, ix
123 | log add 1, ix
124 | )
125 |
126 | @sinh = sinh = (x) ->
127 | a = exp x
128 | multiply 0.5, subtract a, divide 1, a
129 |
130 | @cosh = cosh = (x) ->
131 | a = exp x
132 | multiply 0.5, add a, divide 1, a
133 |
134 | @tanh = tanh = (x) ->
135 | a = exp x
136 | b = divide 1, a
137 | divide (subtract a, b), (add a, b)
138 |
139 | @asinh = (x) -> # arcsinh x = i arcsin(-ix)
140 | itimes asin negitimes x
141 |
142 | @acosh = (x) -> # arccosh x = +/- i arccos x
143 | x = complexify x
144 | sign = if x.im > 0 or (x.im is 0 and x.re <= 1) then 1 else -1
145 | multiply new Complex(0, sign), acos x
146 |
147 | @atanh = (x) -> # arctanh x = i arctan(-ix)
148 | itimes atan negitimes x
149 |
150 | @floor = floor = (x) ->
151 | if typeof x is 'number'
152 | Math.floor x
153 | else
154 | x = complexify x
155 | [re, im] = [(Math.floor x.re), (Math.floor x.im)]
156 | [r, i] = [x.re - re, x.im - im]
157 | if r + i >= 1
158 | if r >= i then re++ else im++
159 | simplify re, im
160 |
161 | @ceil = (x) ->
162 | if typeof x is 'number'
163 | Math.ceil x
164 | else
165 | x = complexify x
166 | [re, im] = [(Math.ceil x.re), (Math.ceil x.im)]
167 | [r, i] = [re - x.re, im - x.im]
168 | if r + i >= 1
169 | if r >= i then re-- else im--
170 | simplify re, im
171 |
172 | iszero = (x) ->
173 | x is 0 or (x instanceof Complex and x.re is 0 and x.im is 0)
174 |
175 | @residue = residue = (x, y) ->
176 | if typeof x is typeof y is 'number'
177 | if x is 0 then y else y - x * Math.floor y / x
178 | else
179 | if iszero x then y else subtract y, multiply x, floor divide y, x
180 |
181 | @isint = (x) ->
182 | if typeof x is 'number'
183 | x is Math.floor x
184 | else
185 | (x.re is Math.floor x.re) and (x.im is Math.floor x.im)
186 |
187 | firstquadrant = (x) -> # rotate into first quadrant
188 | if typeof x is 'number'
189 | Math.abs x
190 | else
191 | if x.re < 0
192 | x = negate x
193 | if x.im < 0
194 | x = itimes x
195 | if x.re is 0
196 | x = x.im
197 | x
198 |
199 | @gcd = gcd = (x, y) ->
200 | if typeof x is typeof y is 'number'
201 | while y then [x, y] = [y, x % y] # Euclid's algorithm
202 | Math.abs x
203 | else
204 | while !iszero y then [x, y] = [y, residue y, x] # Euclid's algorithm
205 | firstquadrant x
206 |
207 | @lcm = (x, y) ->
208 | p = multiply x, y
209 | if iszero p then p
210 | else divide p, gcd x, y
211 |
--------------------------------------------------------------------------------
/src/errors.coffee:
--------------------------------------------------------------------------------
1 | aplError = (name, message = '', opts) ->
2 | assert typeof name is 'string'
3 | assert typeof message is 'string'
4 | if opts?
5 | {aplCode, offset, file} = opts
6 | if aplCode? and offset?
7 | assert typeof aplCode is 'string'
8 | assert typeof offset is 'number'
9 | assert typeof file in ['string', 'undefined']
10 | a = aplCode[...offset].split '\n'
11 | line = a.length
12 | col = 1 + (a[a.length - 1] or '').length
13 | message += """
14 | \n#{file or '-'}:#{line}:#{col}
15 | #{aplCode.split('\n')[line - 1]}
16 | #{repeat '_', col - 1}^
17 | """
18 | e = Error message
19 | e.name = name
20 | for k, v of opts then e[k] = v
21 | throw e
22 |
23 | syntaxError = (a...) -> aplError 'SYNTAX ERROR', a...
24 | domainError = (a...) -> aplError 'DOMAIN ERROR', a...
25 | lengthError = (a...) -> aplError 'LENGTH ERROR', a...
26 | rankError = (a...) -> aplError 'RANK ERROR', a...
27 | indexError = (a...) -> aplError 'INDEX ERROR', a...
28 | nonceError = (a...) -> aplError 'NONCE ERROR', a...
29 | valueError = (a...) -> aplError 'VALUE ERROR', a...
30 |
--------------------------------------------------------------------------------
/src/helpers.coffee:
--------------------------------------------------------------------------------
1 | macro isInt (x, start, end) ->
2 | new macro.Parens(
3 | (
4 | if end then macro.codeToNode -> (tmp = x) is ~~tmp and start <= tmp < end
5 | else if start then macro.codeToNode -> (tmp = x) is ~~tmp and start <= tmp
6 | else macro.codeToNode -> (tmp = x) is ~~tmp
7 | ).subst
8 | tmp: macro.csToNode @tmp()
9 | x: new macro.Parens x
10 | start: new macro.Parens start
11 | end: new macro.Parens end
12 | )
13 |
14 | prod = (xs) -> r = 1; (for x in xs then r *= x); r
15 | all = (xs) -> (for x in xs when !x then return false); true
16 |
17 | # `repeat(a, n)` catenates `n` instances of a string or array `a`.
18 | repeat = (a, n) ->
19 | assert a.length?
20 | assert isInt n, 0
21 | if !n then return a[...0]
22 | m = n * a.length
23 | while a.length * 2 < m then a = a.concat a
24 | a.concat a[... m - a.length]
25 |
26 | extend = (x, y) ->
27 | for k of y then x[k] = y[k]
28 | x
29 |
30 | macro formatNumber (x) ->
31 | macro.codeToNode(-> ('' + x).replace('Infinity', '∞').replace(/-/g, '¯')).subst {x}
32 |
33 | @Uint8Array ?= Array
34 | @Uint16Array ?= Array
35 | @Uint32Array ?= Array
36 | @Int8Array ?= Array
37 | @Int16Array ?= Array
38 | @Int32Array ?= Array
39 |
40 | Array::set ?= (a, offset) ->
41 | for i in [0...a.length] by 1 then @[offset + i] = a[i]
42 | return
43 |
44 | macro spread (a, i, m, n) -> # repeat the pattern a[i...i+m] so that it covers a[i...i+n]
45 | (macro.codeToNode ->
46 | a = a0
47 | i = i0
48 | m = m0
49 | n = n0
50 | if a instanceof Array
51 | for j in [m...n] by 1
52 | a[i + j] = a[i + j % m]
53 | else
54 | a = a.subarray i, i + n
55 | while 2 * m < n
56 | a.set a.subarray(0, m), m
57 | m *= 2
58 | a.set a.subarray(0, n - m), m
59 | ).subst
60 | a: macro.csToNode @tmp()
61 | i: macro.csToNode @tmp()
62 | m: macro.csToNode @tmp()
63 | n: macro.csToNode @tmp()
64 | a0: a
65 | i0: i
66 | m0: m
67 | n0: n
68 |
69 | arrayEquals = (a, b) ->
70 | assert a.length?
71 | assert b.length?
72 | if a.length isnt b.length then return false
73 | for x, i in a when x isnt b[i] then return false
74 | true
75 |
76 | reversed = (a) ->
77 | if a instanceof Array
78 | a[..].reverse()
79 | else
80 | b = new a.constructor a.length
81 | b.set a
82 | i = -1
83 | j = a.length
84 | while ++i < --j
85 | h = b[i]; b[i] = b[j]; b[j] = h
86 | b
87 |
--------------------------------------------------------------------------------
/src/lexer.coffee:
--------------------------------------------------------------------------------
1 | # Token types:
2 | # '-' whitespace and comments
3 | # 'L' newline
4 | # '⋄' diamond (⋄)
5 | # 'N' number
6 | # 'S' string
7 | # '()[]{}:;←' self
8 | # 'J' JS literal
9 | # 'X' symbol
10 | # '$' end of file
11 |
12 | tokenDefs = [
13 | ['-', /^(?:[ \t]+|[⍝\#].*)+/]
14 | ['L', /^[\n\r]+/]
15 | ['⋄', /^[◇⋄]/]
16 | ['N', ///^
17 | ¯? (?: 0x[\da-f]+ | \d*\.?\d+(?:e[+¯]?\d+)? | ¯ | ∞ )
18 | (?: j ¯? (?: 0x[\da-f]+ | \d*\.?\d+(?:e[+¯]?\d+)? | ¯ | ∞ ) ) ?
19 | ///i]
20 | ['S', /^(?:'[^']*')+|^(?:"[^"]*")+/]
21 | ['.', /^[\(\)\[\]\{\}:;←]/]
22 | ['J', /^«[^»]*»/]
23 | ['X', /^(?:⎕?[a-z_]\w*|⍺⍺|⍵⍵|∇∇|[^¯'":«»])/i]
24 | ]
25 |
26 | # `stack` keeps track of bracket nesting and causes 'L' tokens to be dropped
27 | # when the latest unclosed bracket is '(' or '['. This allows for newlines
28 | # inside expressions without having them treated as statement separators.
29 | #
30 | # A sentry '$' token is generated at the end.
31 | tokenize = (s, opts = {}) ->
32 | offset = 0
33 | stack = ['{'] # a stack of brackets
34 | tokens = []
35 | ns = s.length
36 | while offset < ns
37 | type = null
38 | for [t, re] in tokenDefs when m = s[offset..].match re
39 | value = m[0]
40 | type = if t is '.' then value else t
41 | break
42 | if !type then syntaxError 'Unrecognized token', {file: opts.file, offset, s: opts.s}
43 | if type isnt '-'
44 | if type in '([{' then stack.push type
45 | else if type in ')]}' then stack.pop()
46 | if type isnt 'L' or stack[stack.length - 1] is '{'
47 | tokens.push {type, value, offset, aplCode: s}
48 | offset += value.length
49 | tokens.push {type: '$', value: '', offset, aplCode: s}
50 | tokens
51 |
--------------------------------------------------------------------------------
/src/parser.coffee:
--------------------------------------------------------------------------------
1 | # The parser builds an AST from a list of tokens.
2 | #
3 | # A node in the AST is a JavaScript array whose first item is a character
4 | # indicating the type of node. The rest of the items represent the content of
5 | # a node. For instance, "(1 + 2) × 3 4" will produce the tree:
6 | #
7 | # ['B',
8 | # ['.',
9 | # ['.',
10 | # ['N', '1'],
11 | # ['X', '+'],
12 | # ['N', '2']],
13 | # ['X', '×'],
14 | # ['N', '3'],
15 | # ['N', '4']]]
16 | #
17 | # Note, that right after parsing stage we don't yet know which symbols
18 | # represent verbs and which represent nouns. This will be resolved later in
19 | # the compiler.
20 | #
21 | # This parser is a hand-crafted recursive descent parser. Various parseX()
22 | # functions roughly correspond to the set of non-terminals in an imaginary
23 | # grammar.
24 | #
25 | # Node types:
26 | #
27 | # Type Description Example
28 | # ---- ----------- -------
29 | # 'B' body
30 | # ':' guard a:b
31 | # 'N' number 1
32 | # 'S' string 'a'
33 | # 'X' symbol a
34 | # 'J' embedded «a»
35 | # '⍬' empty ()
36 | # '{' lambda {}
37 | # '[' index a[b]
38 | # '←' assign a←b
39 | # '.' expr a b
40 | #
41 | # The compiler replaces '.' nodes with:
42 | # 'V' vector 1 2
43 | # 'M' monadic +1
44 | # 'D' dyadic 1+2
45 | # 'A' adverb +/
46 | # 'C' conjunction +.×
47 | # 'H' hook +÷
48 | # 'F' fork +÷⍴
49 | parse = (aplCode, opts = {}) ->
50 | tokens = tokenize aplCode
51 | i = 0
52 | token = tokens[i++] # single-token lookahead
53 |
54 | # consume(tt) consumes the upcoming token and returns a truthy value only
55 | # if its type matches any character in tt.
56 | macro consume (tt) ->
57 | new macro.Parens macro.csToNode """
58 | if token.type in #{JSON.stringify macro.nodeToVal(tt).split ''}
59 | token = tokens[i++]
60 | """
61 |
62 | # demand(tt) is like consume(tt) but intolerant to a mismatch.
63 | macro demand (tt) ->
64 | new macro.Parens macro.codeToNode(->
65 | if token.type is tt then token = tokens[i++]
66 | else parserError "Expected token of type '#{tt}' but got '#{token.type}'"
67 | ).subst {tt}
68 |
69 | parserError = (message) ->
70 | syntaxError message,
71 | file: opts.file, offset: token.offset, aplCode: aplCode
72 |
73 | parseBody = ->
74 | body = ['B']
75 | loop
76 | if token.type in '$};' then return body
77 | while consume '⋄L' then ;
78 | if token.type in '$};' then return body
79 | expr = parseExpr()
80 | if consume ':' then expr = [':', expr, parseExpr()]
81 | body.push expr
82 |
83 | parseExpr = ->
84 | expr = ['.']
85 | loop
86 | t = token
87 | if consume 'NSXJ'
88 | item = [t.type, t.value]
89 | else if consume '('
90 | if consume ')' then item = ['⍬']
91 | else item = parseExpr(); demand ')'
92 | else if consume '{'
93 | item = ['{', parseBody()]
94 | while consume ';' then item.push parseBody()
95 | demand '}'
96 | else parserError "Encountered unexpected token of type '#{token.type}'"
97 | if consume '['
98 | item = ['[', item]
99 | loop
100 | if consume ';' then item.push null
101 | else if token.type is ']' then item.push null; break
102 | else (item.push parseExpr(); if token.type is ']' then break else demand ';')
103 | demand ']'
104 | if consume '←' then return expr.concat [['←', item, parseExpr()]]
105 | expr.push item
106 | if token.type in ')]}:;⋄L$' then return expr
107 |
108 | result = parseBody()
109 | # 'hello'} !!! SYNTAX ERROR
110 | demand '$'
111 | result
112 |
113 | macro ->
114 | delete macro._macros.consume
115 | delete macro._macros.demand
116 | return
117 |
--------------------------------------------------------------------------------
/src/prelude.apl:
--------------------------------------------------------------------------------
1 | ⍬←()
2 | ⍝ ⍬ ←→ 0⍴0
3 | ⍝ ⍴⍬ ←→ ,0
4 | ⍝# ⍬←5 !!!
5 | ⍝ ⍳0 ←→ ⍬
6 | ⍝ ⍴0 ←→ ⍬
7 | ⍝ ⍬ ←→ ⍬
8 | ⍝ ⍬⍬ ←→ ⍬ ⍬
9 | ⍝ 1⍬2⍬3 ←→ 1 ⍬ 2 ⍬ 3
10 |
11 | ~←~⍠{(~⍺∊⍵)/⍺}
12 | ⍝ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"~"AEIOU" ←→ 'BCDFGHJKLMNPQRSTVWXYZ'
13 | ⍝ 1 2 3 4 5 6~2 4 6 ←→ 1 3 5
14 | ⍝ "THIS IS TEXT"~" " ←→ 'THISISTEXT'
15 | ⍝ "THIS" "AND" "THAT"~"T" ←→ 'THIS' 'AND' 'THAT'
16 | ⍝ "THIS" "AND" "THAT"~"AND" ←→ 'THIS' 'AND' 'THAT'
17 | ⍝ "THIS" "AND" "THAT"~⊂"AND" ←→ 'THIS' 'THAT'
18 | ⍝ "THIS" "AND" "THAT"~"TH" "AND" ←→ 'THIS' 'THAT'
19 | ⍝ 11 12 13 14 15 16~2 3⍴1 2 3 14 5 6 ←→ 11 12 13 15 16
20 | ⍝ (2 2⍴⍳4)~2 !!! RANK ERROR
21 |
22 | _hook←{⍵⍶⍹⍵;⍺⍶⍹⍵}
23 | ⍝ (+÷)\3 7 16 ¯294 ←→ (3 3.142857142857143 3.1415929203539825 3.141592653921421)
24 | ⍝ (=⌊)123 ←→ 1
25 | ⍝ (=⌊)123.4 ←→ 0
26 | ⍝ (÷⍟)1000 ←→ 144.76482730108395
27 |
28 | ⊃←{
29 | 0=⍴⍴⍵:↑⍵
30 | 0=×/⍴⍵:⍵
31 | shape←⍴⍵ ⋄ ⍵←,⍵
32 | r←⌈/≢¨shapes←⍴¨⍵ ⍝ maximum rank of all shapes
33 | max←↑⌈/shapes←(⍴ ↓ (r⍴1)∘,)¨shapes ⍝ maximum shape of rank adjusted shapes
34 | (shape,max)⍴↑⍪/shapes{max↑⍺⍴⍵}¨⍵
35 | ;
36 | 1<⍴⍴⍺:↗'RANK ERROR'
37 | x←⍵
38 | {
39 | 1<⍴⍴⍵:↗'RANK ERROR'
40 | ⍵←,⍵
41 | (⍴⍵)≠⍴⍴x:↗'RANK ERROR'
42 | ∨/⍵≥⍴x:↗'INDEX ERROR'
43 | x←⊃⍵⌷x
44 | }¨⍺
45 | x
46 | }
47 | ⍝ ⊃3 ←→ 3
48 | ⍝ ⊃(1 2)(3 4) ←→ 2 2⍴1 2 3 4
49 | ⍝ ⊃(1 2)(3 4 5) ←→ 2 3⍴1 2 0 3 4 5
50 | ⍝ ⊃1 2 ←→ 1 2
51 | ⍝ ⊃(1 2)3 ←→ 2 2⍴1 2 3 0
52 | ⍝ ⊃1(2 3) ←→ 2 2⍴1 0 2 3
53 | ⍝ ⊃2 2⍴1(1 1 2⍴3 4)(5 6)(2 0⍴0) ←→ 2 2 1 2 2⍴1 0 0 0 3 4 0 0 5 6 0 0 0 0 0 0
54 | ⍝ ⊃⍬ ←→ ⍬
55 | ⍝ ⊃2 3 0⍴0 ←→ 2 3 0⍴0
56 | ⍝ ⍬⊃3 ←→ 3
57 | ⍝ 2⊃'PICK' ←→ 'C'
58 | ⍝ (⊂1 0)⊃2 2⍴'ABCD' ←→ 'C'
59 | ⍝ 1⊃'foo' 'bar' ←→ 'bar'
60 | ⍝ 1 2⊃'foo' 'bar' ←→ 'r'
61 | ⍝ (2 2⍴0)⊃1 2 !!! RANK ERROR
62 | ⍝ (⊂2 1⍴0)⊃2 2⍴0 !!! RANK ERROR
63 | ⍝ (⊂2 2⍴0)⊃1 2 !!! RANK ERROR
64 | ⍝ (⊂2 2)⊃1 2 !!! RANK ERROR
65 | ⍝ (⊂0 2)⊃2 2⍴'ABCD' !!! INDEX ERROR
66 |
67 | ⊂←⊂⍠{ ⍝ ⍺⊂⍵ definition
68 | 1<⍴⍴⍺:↗'RANK ERROR' ⋄ 1≠⍴⍴⍵:↗'NONCE ERROR' ⋄ ⍺←,⍺=0
69 | keep←~1 1⍷⍺ ⋄ sel←keep/⍺ ⋄ dat←keep/⍵
70 | {1=1↑sel:{sel←1↓sel ⋄ dat←1↓dat}⍬}⍬
71 | {1=¯1↑sel:{sel←¯1↓sel ⋄ dat←¯1↓dat}⍬}⍬
72 | sel←(⍴sel),⍨sel/⍳⍴sel ⋄ drop←0
73 | sel{∆←drop↓⍺↑⍵ ⋄ drop←⍺+1 ⋄ ∆}¨⊂dat
74 | }
75 | ⍝ a←' this is a test ' ⋄ (a≠' ')⊂a ←→ 'this' 'is' (,'a') 'test'
76 |
77 | ↓←{
78 | 0=⍴⍴⍵:⍵ ⋄ ⊂[¯1+⍴⍴⍵]⍵
79 | ;
80 | 1<⍴⍴⍺:↗'RANK ERROR'
81 | a←,⍺
82 | ⍵←{0=⍴⍴⍵:((⍴a)⍴1)⍴⍵⋄⍵}⍵
83 | (⍴a)>⍴⍴⍵:↗'RANK ERROR'
84 | a←(⍴⍴⍵)↑a
85 | a←((a>0)×0⌊a-⍴⍵)+(a≤0)×0⌈a+⍴⍵
86 | a↑⍵
87 | }
88 | ⍝ ↓1 2 3 ←→ ⊂1 2 3
89 | ⍝ ↓(1 2)(3 4) ←→ ⊂(1 2)(3 4)
90 | ⍝ ↓2 2⍴⍳4 ←→ (0 1)(2 3)
91 | ⍝ ↓2 3 4⍴⍳24 ←→ 2 3⍴(0 1 2 3)(4 5 6 7)(8 9 10 11)(12 13 14 15)(16 17 18 19)(20 21 22 23)
92 | ⍝ 4↓'OVERBOARD' ←→ 'BOARD'
93 | ⍝ ¯5↓'OVERBOARD' ←→ 'OVER'
94 | ⍝ ⍴10↓'OVERBOARD' ←→ ,0
95 | ⍝ 0 ¯2↓3 3⍴'ONEFATFLY' ←→ 3 1⍴'OFF'
96 | ⍝ ¯2 ¯1↓3 3⍴'ONEFATFLY' ←→ 1 2⍴'ON'
97 | ⍝ 1↓3 3⍴'ONEFATFLY' ←→ 2 3⍴'FATFLY'
98 | ⍝ ⍬↓3 3⍴⍳9 ←→ 3 3⍴⍳9
99 | ⍝ 1 1↓2 3 4⍴"ABCDEFGHIJKLMNOPQRSTUVWXYZ" ←→ 1 2 4⍴'QRSTUVWX'
100 | ⍝ ¯1 ¯1↓2 3 4⍴"ABCDEFGHIJKLMNOPQRSTUVWXYZ" ←→ 1 2 4⍴'ABCDEFGH'
101 | ⍝ 1↓0 ←→ ⍬
102 | ⍝ 0 1↓2 ←→ 1 0⍴0
103 | ⍝ 1 2↓3 ←→ 0 0⍴0
104 | ⍝ ⍬↓0 ←→ 0
105 |
106 | ⍪←{(≢⍵)(×/1↓⍴⍵)⍴⍵; ⍺,[0]⍵}
107 | ⍝ ⍪2 3 4 ←→ 3 1⍴2 3 4
108 | ⍝ ⍪0 ←→ 1 1⍴0
109 | ⍝ ⍪2 2⍴2 3 4 5 ←→ 2 2⍴2 3 4 5
110 | ⍝ ⍴⍪2 3⍴⍳6 ←→ 2 3
111 | ⍝ ⍴⍪2 3 4⍴⍳24 ←→ 2 12
112 | ⍝ (2 3⍴⍳6)⍪9 ←→ 3 3⍴(0 1 2 3 4 5 9 9 9)
113 | ⍝ 1⍪2 ←→ 1 2
114 |
115 | ⊢←{⍵}
116 | ⍝ 123⊢456 ←→ 456
117 | ⍝ ⊢456 ←→ 456
118 |
119 | ⊣←{;⍺}
120 | ⍝ 123⊣456 ←→ 123
121 | ⍝ ⊣456 ←→ ⍬
122 |
123 | ≢←{⍬⍴(⍴⍵),1; ~⍺≡⍵}
124 | ⍝ ≢0 ←→ 1
125 | ⍝ ≢0 0 0 ←→ 3
126 | ⍝ ≢⍬ ←→ 0
127 | ⍝ ≢2 3⍴⍳6 ←→ 2
128 | ⍝ 3≢3 ←→ 0
129 |
130 | ,←{(×/⍴⍵)⍴⍵}⍠,
131 | ⍝ ,2 3 4⍴'abcdefghijklmnopqrstuvwx' ←→ 'abcdefghijklmnopqrstuvwx'
132 | ⍝ ,123 ←→ 1⍴123
133 |
134 | ⌹←{
135 | norm←{(⍵+.×+⍵)*0.5}
136 |
137 | QR←{ ⍝ QR decomposition
138 | n←(⍴⍵)[1]
139 | 1≥n:{t←norm,⍵ ⋄ (⍵÷t)(⍪t)}⍵
140 | m←⌈n÷2
141 | a0←((1↑⍴⍵),m)↑⍵
142 | a1←(0,m)↓⍵
143 | (q0 r0)←∇a0
144 | c←(+⍉q0)+.×a1
145 | (q1 r1)←∇a1-q0+.×c
146 | (q0,q1)((r0,c)⍪((⌊n÷2),-n)↑r1)
147 | }
148 |
149 | Rinv←{ ⍝ Inverse of an upper triangular matrix
150 | 1=n←1↑⍴⍵:÷⍵
151 | m←⌈n÷2
152 | ai←∇(m,m)↑⍵
153 | di←∇(m,m)↓⍵
154 | b←(m,m-n)↑⍵
155 | bx←-ai+.×b+.×di
156 | (ai,bx)⍪((⌊n÷2),-n)↑di
157 | }
158 |
159 | 0=⍴⍴⍵:÷⍵
160 | 1=⍴⍴⍵:,∇⍪⍵
161 | 2≠⍴⍴⍵:↗'RANK ERROR'
162 | 0∊≥/⍴⍵:↗'LENGTH ERROR'
163 | (Q R)←QR ⍵
164 | (Rinv R)+.×+⍉Q
165 | ;
166 | (⌹⍵)+.×⍺
167 | }
168 | ⍝ ⌹2 ←→ .5
169 | ⍝ ⌹2 2⍴4 3 3 2 ←→ 2 2⍴¯2 3 3 ¯4
170 | ⍝ (4 4⍴12 1 4 10 ¯6 ¯5 4 7 ¯4 9 3 4 ¯2 ¯6 7 7)⌹93 81 93.5 120.5 ←→
171 | ⍝ ... .0003898888816687221 ¯.005029566573526544 .04730651764247189 .0705568912859835
172 | ⍝ ⌹2 2 2⍴⍳8 !!! RANK ERROR
173 | ⍝ ⌹2 3⍴⍳6 !!! LENGTH ERROR
174 |
175 | ⍨←{⍵⍶⍵;⍵⍶⍺}
176 | ⍝ 17-⍨23 ←→ 6
177 | ⍝ 7⍴⍨2 3 ←→ 2 3⍴7
178 | ⍝ +⍨2 ←→ 4
179 | ⍝ -⍨123 ←→ 0
180 |
181 | ⍝ Aliases:
182 | −←- ⍝ U+2212 MINUS SIGN for U+002D HYPHEN-MINUS
183 | ⋆←* ⍝ U+22C6 STAR OPERATOR for U+002A ASTERISK
184 | ∣←| ⍝ U+2223 DIVIDES for U+007C VERTICAL LINE
185 | ^←∧ ⍝ U+005E CIRCUMFLEX ACCENT for U+2227 LOGICAL AND
186 | ∈←∊ ⍝ U+2208 ELEMENT OF for U+220A SMALL ELEMENT OF
187 |
--------------------------------------------------------------------------------
/src/vm.coffee:
--------------------------------------------------------------------------------
1 | [LDC, VEC, GET, SET, MON, DYA, LAM, RET, POP, SPL, JEQ, EMB, CON] = [1..13]
2 |
3 | class λ
4 | constructor: (@code, @addr, @size, @env) ->
5 | toFunction: -> (x, y) => vm code: @code, env: @env.concat([[x, @, y, null]]), pc: @addr
6 | toString: -> 'λ'
7 |
8 | vm = ({code, env, stack, pc}) ->
9 | assert code instanceof Array
10 | assert env instanceof Array
11 | for frame in env then assert frame instanceof Array
12 | stack ?= []
13 | pc ?= 0
14 | loop
15 | switch code[pc++]
16 | when LDC then stack.push code[pc++]
17 | when VEC
18 | a = []
19 | for x in stack.splice stack.length - code[pc++]
20 | a.push(if x.isSimple() then x.unwrap() else x)
21 | stack.push new APLArray a
22 | when GET then stack.push(env[code[pc++]][code[pc++]] ? valueError())
23 | when SET then env[code[pc++]][code[pc++]] = stack[stack.length - 1]
24 | when MON
25 | [w, f] = stack.splice -2
26 | if typeof f is 'function'
27 | if w instanceof λ then w = w.toFunction()
28 | if f.cps
29 | f w, undefined, undefined, (r) -> stack.push r; vm {code, env, stack, pc}; return
30 | return
31 | else
32 | stack.push f w
33 | else
34 | bp = stack.length
35 | stack.push code, pc, env
36 | {code} = f
37 | pc = f.addr
38 | env = f.env.concat [[w, f, null, bp]]
39 | when DYA
40 | [w, f, a] = stack.splice -3
41 | if typeof f is 'function'
42 | if w instanceof λ then w = w.toFunction()
43 | if a instanceof λ then a = a.toFunction()
44 | if f.cps
45 | f w, a, undefined, (r) -> stack.push r; vm {code, env, stack, pc}; return
46 | return
47 | else
48 | stack.push f w, a
49 | else
50 | bp = stack.length
51 | stack.push code, pc, env
52 | {code} = f
53 | pc = f.addr
54 | env = f.env.concat [[w, f, a, bp]]
55 | when LAM
56 | size = code[pc++]
57 | stack.push new λ code, pc, size, env
58 | pc += size
59 | when RET
60 | if stack.length is 1 then return stack[0]
61 | [code, pc, env] = stack.splice -4, 3
62 | when POP then stack.pop()
63 | when SPL
64 | n = code[pc++]
65 | a = stack[stack.length - 1].toArray().reverse()
66 | a = for x in a then (if x instanceof APLArray then x else new APLArray [x], [])
67 | if a.length is 1
68 | a = repeat a, n
69 | else if a.length isnt n
70 | lengthError()
71 | stack.push a...
72 | when JEQ
73 | n = code[pc++]
74 | if !stack[stack.length - 1].toBool()
75 | pc += n
76 | when EMB
77 | frame = env[env.length - 1]
78 | stack.push code[pc++](frame[0], frame[2])
79 | when CON
80 | frame = env[env.length - 1]
81 | do ->
82 | cont =
83 | code: code
84 | env: for x in env then x[..]
85 | stack: stack[...frame[3]]
86 | pc: frame[1].addr + frame[1].size - 1
87 | assert code[cont.pc] is RET
88 | stack.push (r) -> {code, env, stack, pc} = cont; stack.push r
89 | else aplError 'Unrecognized instruction: ' + code[pc - 1] + ', pc:' + pc
90 | return
91 |
--------------------------------------------------------------------------------
/src/vocabulary/arithmetic.coffee:
--------------------------------------------------------------------------------
1 | addVocabulary
2 |
3 | '+': withIdentity 0, pervasive
4 |
5 | # Conjugate (`+`)
6 | #
7 | # +4 ←→ 4
8 | # ++4 ←→ 4
9 | # + 4 5 ←→ 4 5
10 | # +((5 6) (7 1)) ←→ (5 6) (7 1)
11 | # + (5 6) (7 1) ←→ (5 6) (7 1)
12 | # +1j¯2 ←→ 1j2
13 | monad: numeric ((x) -> x), Complex.conjugate
14 |
15 | # Add (`+`)
16 | #
17 | # 1 + 2 ←→ 3
18 | # 2 3 + 5 8 ←→ 7 11
19 | # (2 3 ⍴ 1 2 3 4 5 6) + ¯2 ←→ 2 3 ⍴ ¯1 0 1 2 3 4
20 | # (2 3 ⍴ 1 2 3 4 5 6) + 2 ⍴ ¯2 !!! RANK ERROR
21 | # (2 3 ⍴ 1 2 3 4 5 6) + 2 3 ⍴ ¯2 ←→ 2 3 ⍴ ¯1 0 1 2 3 4
22 | # 1 2 3 + 4 5 !!! LENGTH ERROR
23 | # (2 3⍴⍳6) + 3 2⍴⍳6 !!! LENGTH ERROR
24 | # 1j¯2+¯2j3 ←→ ¯1j1
25 | # +/⍬ ←→ 0
26 | # ¯+¯¯ !!! DOMAIN ERROR
27 | # 1j¯+2j¯¯ !!! DOMAIN ERROR
28 | dyad: numeric ((y, x) -> x + y),
29 | (y, x) -> Complex.add x, y
30 |
31 | '-': withIdentity 0, pervasive
32 |
33 | # Negate (`-`)
34 | #
35 | # -4 ←→ ¯4
36 | # -1 2 3 ←→ ¯1 ¯2 ¯3
37 | # -1j2 ←→ ¯1j¯2
38 | monad: numeric ((x) -> -x), Complex.negate
39 |
40 | # Subtract (`-`)
41 | #
42 | # 1-3 ←→ ¯2
43 | # 5-¯3 ←→ 8
44 | # 5j2-3j8 ←→ 2j¯6
45 | # 5-3j8 ←→ 2j¯8
46 | # -/⍬ ←→ 0
47 | dyad: numeric ((y, x) -> x - y),
48 | (y, x) -> Complex.subtract x, y
49 |
50 | '×': withIdentity 1, pervasive
51 |
52 | # Sign of (`×`)
53 | #
54 | # × ¯2 ¯1 0 1 2 ←→ ¯1 ¯1 0 1 1
55 | # × ¯ ←→ 1
56 | # × ¯¯ ←→ ¯1
57 | # ×3j¯4 ←→ .6j¯.8
58 | monad: numeric ((x) -> (x > 0) - (x < 0)),
59 | (x) ->
60 | d = Math.sqrt x.re * x.re + x.im * x.im
61 | simplify x.re / d, x.im / d
62 |
63 | # Multiply (`×`)
64 | #
65 | # 7×8 ←→ 56
66 | # 1j¯2ׯ2j3 ←→ 4j7
67 | # 2×1j¯2 ←→ 2j¯4
68 | # ×/⍬ ←→ 1
69 | dyad: numeric ((y, x) -> x * y),
70 | (y, x) -> Complex.multiply x, y
71 |
72 | '÷': withIdentity 1, pervasive
73 |
74 | # Reciprocal (`÷`)
75 | #
76 | # ÷2 ←→ .5
77 | # ÷2j3 ←→ 0.15384615384615385J¯0.23076923076923078
78 | # 0÷0 !!! DOMAIN ERROR
79 | monad: numeric ((x) -> 1 / x),
80 | (x) ->
81 | d = x.re * x.re + x.im * x.im
82 | simplify x.re / d, -x.im / d
83 |
84 | # Divide (`÷`)
85 | #
86 | # 27÷9 ←→ 3
87 | # 4j7÷1j¯2 ←→ ¯2j3
88 | # 0j2÷0j1 ←→ 2
89 | # 5÷2j1 ←→ 2j¯1
90 | # ÷/⍬ ←→ 1
91 | dyad: numeric ((y, x) -> x / y),
92 | (y, x) -> Complex.divide x, y
93 |
94 | '*': withIdentity 1, pervasive
95 |
96 | # *2 ←→ 7.38905609893065
97 | # *2j3 ←→ ¯7.315110094901103J1.0427436562359045
98 | monad: exp = numeric Math.exp, Complex.exp
99 |
100 | # 2*3 ←→ 8
101 | # 3*2 ←→ 9
102 | # ¯2*3 ←→ ¯8
103 | # ¯3*2 ←→ 9
104 | # ¯1*.5 ←→ 0j1
105 | # 1j2*3j4 ←→ .129009594074467j.03392409290517014
106 | # */⍬ ←→ 1
107 | dyad: (y, x) -> Complex.pow x, y
108 |
109 | '⍟': pervasive
110 |
111 | # Natural logarithm (`⍟`)
112 | #
113 | # ⍟123 ←→ 4.812184355372417
114 | # ⍟0 ←→ ¯¯
115 | # ⍟¯1 ←→ 0j1 × ○1
116 | # ⍟123j456 ←→ 6.157609243895447J1.3073297857599793
117 | monad: Complex.log
118 |
119 | # Logarithm to the base (`⍟`)
120 | #
121 | # 12⍟34 ←→ 1.419111870829036
122 | # 12⍟¯34 ←→ 1.419111870829036j1.26426988871305
123 | # ¯12⍟¯34 ←→ 1.1612974763994781j¯.2039235425372641
124 | # 1j2⍟3j4 ←→ 1.2393828252698689J¯0.5528462880299602
125 | dyad: (y, x) ->
126 | if typeof x is typeof y is 'number' and x > 0 and y > 0
127 | Math.log(y) / Math.log(x)
128 | else
129 | Complex.divide (Complex.log y), (Complex.log x)
130 |
131 | '|': withIdentity 0, pervasive
132 |
133 | # Absolute value (`∣`)
134 | #
135 | # ∣ ¯8 0 8 ¯3.5 ←→ 8 0 8 3.5
136 | # |5j12 ←→ 13
137 | monad: numeric ((x) -> Math.abs x), Complex.magnitude
138 |
139 | # Residue (`∣`)
140 | #
141 | # 3∣5 ←→ 2
142 | # 1j2|3j4 ←→ ¯1j1
143 | # 7 ¯7 ∘.| 31 28 ¯30 ←→ 2 3⍴3 0 5 ¯4 0 ¯2
144 | # ¯0.2 0 0.2 ∘.| ¯0.3 0 0.3 ←→ 3 3⍴¯0.1 0 ¯0.1 ¯0.3 0 0.3 0.1 0 0.1
145 | # |/⍬ ←→ 0
146 | # 0|¯4 ←→ ¯4
147 | # 0|¯4j5 ←→ ¯4j5
148 | # 10|4j3 ←→ 4j3
149 | # 4j6|7j10 ←→ 3j4
150 | # ¯10 7j10 0.3|17 5 10 ←→ ¯3 ¯5j7 0.1
151 | dyad: (y, x) -> Complex.residue x, y
152 |
--------------------------------------------------------------------------------
/src/vocabulary/backslash.coffee:
--------------------------------------------------------------------------------
1 | addVocabulary
2 |
3 | # Scan or expand (`\`)
4 | #
5 | # +\ 20 10 ¯5 7 ←→ 20 30 25 32
6 | # ,\ "AB" "CD" "EF" ←→ 'AB' 'ABCD' 'ABCDEF'
7 | # ×\ 2 3⍴5 2 3 4 7 6 ←→ 2 3 ⍴ 5 10 30 4 28 168
8 | # ∧\ 1 1 1 0 1 1 ←→ 1 1 1 0 0 0
9 | # -\ 1 2 3 4 ←→ 1 ¯1 2 ¯2
10 | # ∨\ 0 0 1 0 0 1 0 ←→ 0 0 1 1 1 1 1
11 | # +\ 1 2 3 4 5 ←→ 1 3 6 10 15
12 | # +\ (1 2 3)(4 5 6)(7 8 9) ←→ (1 2 3) (5 7 9) (12 15 18)
13 | # M←2 3⍴1 2 3 4 5 6 ⋄ +\M ←→ 2 3 ⍴ 1 3 6 4 9 15
14 | # M←2 3⍴1 2 3 4 5 6 ⋄ +⍀M ←→ 2 3 ⍴ 1 2 3 5 7 9
15 | # M←2 3⍴1 2 3 4 5 6 ⋄ +\[0]M ←→ 2 3 ⍴ 1 2 3 5 7 9
16 | # ,\ 'ABC' ←→ 'A' 'AB' 'ABC'
17 | # T←"ONE(TWO) BOOK(S)" ⋄ ≠\T∊"()" ←→ 0 0 0 1 1 1 1 0 0 0 0 0 0 1 1 0
18 | # T←"ONE(TWO) BOOK(S)" ⋄ ((T∊"()")⍱≠\T∊"()")/T ←→ 'ONE BOOK'
19 | #
20 | # 1 0 1\'ab' ←→ 'a b'
21 | # 0 1 0 1 0\2 3 ←→ 0 2 0 3 0
22 | # (2 2⍴0)\'food' !!! RANK ERROR
23 | # 'abc'\'def' !!! DOMAIN ERROR
24 | # 1 0 1 1\'ab' !!! LENGTH ERROR
25 | # 1 0 1 1\'abcd' !!! LENGTH ERROR
26 | # 1 0 1\2 2⍴'ABCD' ←→ 2 3⍴'A BC D'
27 | # 1 0 1⍀2 2⍴'ABCD' ←→ 3 2⍴'AB CD'
28 | # 1 0 1\[0]2 2⍴'ABCD' ←→ 3 2⍴'AB CD'
29 | # 1 0 1\[1]2 2⍴'ABCD' ←→ 2 3⍴'A BC D'
30 | '\\': adverb (⍵, ⍺, axis) ->
31 | if typeof ⍵ is 'function'
32 | scan ⍵, undefined, axis
33 | else
34 | expand ⍵, ⍺, axis
35 |
36 | '⍀': adverb (⍵, ⍺, axis = APLArray.zero) ->
37 | if typeof ⍵ is 'function'
38 | scan ⍵, undefined, axis
39 | else
40 | expand ⍵, ⍺, axis
41 |
42 | # Helper for `\` and `⍀` in their adverbial sense
43 | scan = (f, g, axis) ->
44 | assert typeof g is 'undefined'
45 | (⍵, ⍺) ->
46 | assert !⍺?
47 | if !⍴⍴ ⍵ then return ⍵
48 | axis = if axis then axis.toInt 0, ⍴⍴ ⍵ else ⍴⍴(⍵) - 1
49 | ⍵.map (x, indices) ->
50 | p = ⍵.offset
51 | for index, a in indices then p += index * ⍵.stride[a]
52 | if x !instanceof APLArray then x = APLArray.scalar x
53 | for j in [0...indices[axis]] by 1
54 | p -= ⍵.stride[axis]
55 | y = ⍵.data[p]
56 | if y !instanceof APLArray then y = APLArray.scalar y
57 | x = f x, y
58 | if !⍴⍴ x then x = x.unwrap()
59 | x
60 |
61 | # Helper for `\` and `⍀` in their verbal sense
62 | expand = (⍵, ⍺, axis) ->
63 | if !⍴⍴ ⍵ then nonceError 'Expand of scalar not implemented'
64 | axis = if axis then axis.toInt 0, ⍴⍴ ⍵ else ⍴⍴(⍵) - 1
65 | if ⍴⍴(⍺) > 1 then rankError()
66 | a = ⍺.toArray()
67 |
68 | shape = ⍴(⍵)[..]
69 | shape[axis] = a.length
70 | b = []
71 | i = 0
72 | for x in a
73 | if !isInt x, 0, 2 then domainError()
74 | b.push(if x > 0 then i++ else null)
75 | if i isnt ⍴(⍵)[axis] then lengthError()
76 |
77 | data = []
78 | if shape[axis] isnt 0 and !⍵.empty()
79 | filler = ⍵.getPrototype()
80 | p = ⍵.offset
81 | indices = repeat [0], shape.length
82 | loop
83 | x =
84 | if b[indices[axis]]?
85 | ⍵.data[p + b[indices[axis]] * ⍵.stride[axis]]
86 | else
87 | filler
88 | data.push x
89 |
90 | i = shape.length - 1
91 | while i >= 0 and indices[i] + 1 is shape[i]
92 | if i isnt axis then p -= ⍵.stride[i] * indices[i]
93 | indices[i--] = 0
94 | if i < 0 then break
95 | if i isnt axis then p += ⍵.stride[i]
96 | indices[i]++
97 |
98 | new APLArray data, shape
99 |
--------------------------------------------------------------------------------
/src/vocabulary/circle.coffee:
--------------------------------------------------------------------------------
1 | addVocabulary
2 |
3 | '○': pervasive
4 |
5 | # Pi times (`○`)
6 | #
7 | # ○2 ←→ 6.283185307179586
8 | # ○2J2 ←→ 6.283185307179586J6.283185307179586
9 | # ○'ABC' !!! DOMAIN ERROR
10 | monad: numeric ((x) -> Math.PI * x),
11 | (x) ->
12 | new Complex Math.PI * x.re, Math.PI * x.im
13 |
14 | # Circular and hyperbolic functions (`○`)
15 | #
16 | # ¯12○2 ←→ ¯0.4161468365471J0.9092974268257
17 | # ¯12○2j3 ←→ ¯0.02071873100224J0.04527125315609
18 | # ¯11○2 ←→ 0j2
19 | # ¯11○2j3 ←→ ¯3j2
20 | # ¯10○2 ←→ 2
21 | # ¯10○2j3 ←→ 2j¯3
22 | # ¯9○2 ←→ 2
23 | # ¯9○2j3 ←→ 2j3
24 | # ¯8○2 ←→ 0J¯2.2360679774998
25 | # ¯8○2j3 ←→ ¯2.8852305489054J2.0795565201111
26 | # ¯7○0.5 ←→ 0.54930614433405
27 | # ¯7○2 ←→ 0.5493061443340548456976226185j¯1.570796326794896619231321692
28 | # ¯7○2j3 ←→ 0.1469466662255297520474327852j1.338972522294493561124193576
29 | # ¯6○0.5 ←→ ¯1.1102230246252E¯16J1.0471975511966
30 | # ¯6○2 ←→ 1.316957896924816708625046347
31 | # ¯6○2j3 ←→ 1.983387029916535432347076903j1.000143542473797218521037812
32 | # ¯5○2 ←→ 1.443635475178810342493276740
33 | # ¯5○2j3 ←→ 1.968637925793096291788665095j0.9646585044076027920454110595
34 | # ¯4○2 ←→ 1.7320508075689
35 | # ¯4○0 ←→ 0j1
36 | # ¯4○¯2 ←→ ¯1.7320508075689
37 | # ¯4○2j3 ←→ 1.9256697360917J3.1157990841034
38 | # ¯3○0.5 ←→ 0.46364760900081
39 | # ¯3○2 ←→ 1.107148717794090503017065460
40 | # ¯3○2j3 ←→ 1.409921049596575522530619385j0.2290726829685387662958818029
41 | # ¯2○0.5 ←→ 1.0471975511966
42 | # ¯2○2 ←→ 0J1.316957896924816708625046347
43 | # ¯2○2j3 ←→ 1.000143542473797218521037812J¯1.983387029916535432347076903
44 | # ¯1○0.5 ←→ 0.5235987755983
45 | # ¯1○2 ←→ 1.570796326794896619231321692J¯1.316957896924816708625046347
46 | # ¯1○2j3 ←→ 0.5706527843210994007102838797J1.983387029916535432347076903
47 | # 0○0.5 ←→ 0.86602540378444
48 | # 0○2 ←→ 0J1.7320508075689
49 | # 0○2j3 ←→ 3.1157990841034J¯1.9256697360917
50 | # 1e¯10>∣.5-1○○÷6 ←→ 1 # sin(pi/6) = .5
51 | # 1○1 ←→ 0.8414709848079
52 | # 1○2j3 ←→ 9.1544991469114J¯4.1689069599666
53 | # 2○1 ←→ 0.54030230586814
54 | # 2○2j3 ←→ ¯4.1896256909688J¯9.1092278937553
55 | # 3○1 ←→ 1.5574077246549
56 | # 3○2j3 ←→ ¯0.0037640256415041J1.0032386273536
57 | # 4○2 ←→ 2.2360679774998
58 | # 4○2j3 ←→ 2.0795565201111J2.8852305489054
59 | # 5○2 ←→ 3.626860407847
60 | # 5○2j3 ←→ ¯3.5905645899858J0.53092108624852
61 | # 6○2 ←→ 3.7621956910836
62 | # 6○2j3 ←→ ¯3.7245455049153J0.51182256998738
63 | # 7○2 ←→ 0.96402758007582
64 | # 7○2j3 ←→ 0.96538587902213J¯0.0098843750383225
65 | # 8○2 ←→ 0J2.2360679774998
66 | # 8○2j3 ←→ 2.8852305489054J¯2.0795565201111
67 | # 9○2 ←→ 2
68 | # 9○2j3 ←→ 2
69 | # 10○¯2 ←→ 2
70 | # 10○¯2j3 ←→ 3.605551275464
71 | # 11○2 ←→ 0
72 | # 11○2j3 ←→ 3
73 | # 12○2 ←→ 0
74 | # 12○2j3 ←→ 0.98279372324733
75 | # 1○'hello' !!! DOMAIN ERROR
76 | # 99○1 !!! DOMAIN ERROR
77 | # 99○1j2 !!! DOMAIN ERROR
78 | dyad: (x, i) ->
79 | if typeof x is 'number'
80 | switch i
81 | when -12 then Complex.exp simplify 0, x
82 | when -11 then simplify 0, x
83 | when -10 then x
84 | when -9 then x
85 | when -8 then simplify 0, -Math.sqrt(1 + x * x)
86 | when -7 then Complex.atanh x
87 | when -6 then Complex.acosh x
88 | when -5 then Complex.asinh x
89 | when -4
90 | t = Complex.sqrt(x * x - 1)
91 | if x < -1 then -t else t
92 | when -3 then Complex.atan x
93 | when -2 then Complex.acos x
94 | when -1 then Complex.asin x
95 | when 0 then Complex.sqrt(1 - x * x)
96 | when 1 then Math.sin x
97 | when 2 then Math.cos x
98 | when 3 then Math.tan x
99 | when 4 then Math.sqrt(1 + x * x)
100 | when 5 then a = Math.exp x; b = 1 / a ; 0.5 * (a - b) # sinh
101 | when 6 then a = Math.exp x; b = 1 / a ; 0.5 * (a + b) # cosh
102 | when 7 then a = Math.exp x; b = 1 / a; (a - b) / (a + b) # tanh
103 | when 8 then Complex.sqrt(-1 - x * x)
104 | when 9 then x
105 | when 10 then Math.abs x
106 | when 11 then 0
107 | when 12 then 0
108 | else domainError 'Unknown circular or hyperbolic function ' + i
109 | else if x instanceof Complex
110 | switch i
111 | when -12 then Complex.exp simplify -x.im, x.re
112 | when -11 then Complex.itimes x
113 | when -10 then Complex.conjugate x
114 | when -9 then x
115 | when -8
116 | t = Complex.subtract -1, (Complex.multiply x, x)
117 | Complex.negate Complex.sqrt t
118 | when -7 then Complex.atanh x
119 | when -6 then Complex.acosh x
120 | when -5 then Complex.asinh x
121 | when -4
122 | if x.re is -1 and x.im is 0 then 0
123 | else
124 | a = Complex.add x, 1
125 | b = Complex.subtract x, 1
126 | Complex.multiply a, Complex.sqrt (Complex.divide b, a)
127 | when -3 then Complex.atan x
128 | when -2 then Complex.acos x
129 | when -1 then Complex.asin x
130 | when 0 then Complex.sqrt Complex.subtract 1, Complex.multiply x, x
131 | when 1 then Complex.sin x
132 | when 2 then Complex.cos x
133 | when 3 then Complex.tan x
134 | when 4 then Complex.sqrt Complex.add 1, Complex.multiply x, x
135 | when 5 then Complex.sinh x
136 | when 6 then Complex.cosh x
137 | when 7 then Complex.tanh x
138 | when 8 then Complex.sqrt Complex.subtract -1, Complex.multiply x, x
139 | when 9 then x.re
140 | when 10 then Complex.magnitude x
141 | when 11 then x.im
142 | when 12 then Complex.direction x
143 | else domainError 'Unknown circular or hyperbolic function ' + i
144 | else
145 | domainError()
146 |
--------------------------------------------------------------------------------
/src/vocabulary/comma.coffee:
--------------------------------------------------------------------------------
1 | addVocabulary
2 |
3 | ',': (⍵, ⍺, axis) ->
4 | if ⍺
5 |
6 | # Catenate (`,`)
7 | #
8 | # 10,66 ←→ 10 66
9 | # '10 ','MAY ','1985' ←→ '10 MAY 1985'
10 | # (2 3⍴⍳6),2 2⍴⍳4 ←→ 2 5⍴(0 1 2 0 1 3 4 5 2 3)
11 | # (3 2⍴⍳6),2 2⍴⍳4 !!! LENGTH ERROR
12 | # (2 3⍴⍳6),9 ←→ 2 4⍴(0 1 2 9 3 4 5 9)
13 | # (2 3 4⍴⍳24),99 ←→ 2 3 5⍴(
14 | # ... 0 1 2 3 99
15 | # ... 4 5 6 7 99
16 | # ... 8 9 10 11 99
17 | # ...
18 | # ... 12 13 14 15 99
19 | # ... 16 17 18 19 99
20 | # ... 20 21 22 23 99)
21 | # ⍬,⍬ ←→ ⍬
22 | # ⍬,1 ←→ ,1
23 | # 1,⍬ ←→ ,1
24 | nAxes = Math.max ⍴⍴(⍺), ⍴⍴(⍵)
25 | if axis
26 | axis = axis.unwrap()
27 | if typeof axis isnt 'number' then domainError()
28 | if nAxes and !(-1 < axis < nAxes) then rankError()
29 | else
30 | axis = nAxes - 1
31 |
32 | if ⍴⍴(⍺) is ⍴⍴(⍵) is 0
33 | return new APLArray [⍺.unwrap(), ⍵.unwrap()]
34 | else if !⍴⍴ ⍺
35 | s = ⍴(⍵)[..]
36 | if isInt axis then s[axis] = 1
37 | ⍺ = new APLArray [⍺.unwrap()], s, repeat([0], ⍴⍴ ⍵)
38 | else if !⍴⍴ ⍵
39 | s = ⍴(⍺)[..]
40 | if isInt axis then s[axis] = 1
41 | ⍵ = new APLArray [⍵.unwrap()], s, repeat([0], ⍴⍴ ⍺)
42 | else if ⍴⍴(⍺) + 1 is ⍴⍴ ⍵
43 | if !isInt axis then rankError()
44 | shape = ⍴(⍺)[..]
45 | shape.splice axis, 0, 1
46 | stride = ⍺.stride[..]
47 | stride.splice axis, 0, 0
48 | ⍺ = new APLArray ⍺.data, shape, stride, ⍺.offset
49 | else if ⍴⍴(⍺) is ⍴⍴(⍵) + 1
50 | if !isInt axis then rankError()
51 | shape = ⍴(⍵)[..]
52 | shape.splice axis, 0, 1
53 | stride = ⍵.stride[..]
54 | stride.splice axis, 0, 0
55 | ⍵ = new APLArray ⍵.data, shape, stride, ⍵.offset
56 | else if ⍴⍴(⍺) isnt ⍴⍴(⍵)
57 | rankError()
58 |
59 | assert ⍴⍴(⍺) is ⍴⍴(⍵)
60 | for i in [0...⍴⍴ ⍺]
61 | if i isnt axis and ⍴(⍺)[i] isnt ⍴(⍵)[i]
62 | lengthError()
63 |
64 | shape = ⍴(⍺)[..]
65 | if isInt axis
66 | shape[axis] += ⍴(⍵)[axis]
67 | else
68 | shape.splice Math.ceil(axis), 0, 2
69 | data = Array prod shape
70 | stride = Array shape.length
71 | stride[shape.length - 1] = 1
72 | for i in [shape.length - 2 .. 0] by -1
73 | stride[i] = stride[i + 1] * shape[i + 1]
74 |
75 | if isInt axis
76 | rStride = stride
77 | else
78 | rStride = stride[..]
79 | rStride.splice Math.ceil(axis), 1
80 |
81 | if !⍺.empty()
82 | r = 0 # pointer in data (the result)
83 | p = ⍺.offset # pointer in ⍺.data
84 | pIndices = repeat [0], ⍴⍴ ⍺
85 | loop
86 | data[r] = ⍺.data[p]
87 | a = pIndices.length - 1
88 | while a >= 0 and pIndices[a] + 1 is ⍴(⍺)[a]
89 | p -= pIndices[a] * ⍺.stride[a]
90 | r -= pIndices[a] * rStride[a]
91 | pIndices[a--] = 0
92 | if a < 0 then break
93 | p += ⍺.stride[a]
94 | r += rStride[a]
95 | pIndices[a]++
96 |
97 | if !⍵.empty()
98 | r = # pointer in data (the result)
99 | if isInt axis
100 | stride[axis] * ⍴(⍺)[axis]
101 | else
102 | stride[Math.ceil axis]
103 | q = ⍵.offset # pointer in ⍵.data
104 | pIndices = repeat [0], ⍴⍴ ⍵
105 | loop
106 | data[r] = ⍵.data[q]
107 | a = pIndices.length - 1
108 | while a >= 0 and pIndices[a] + 1 is ⍴(⍵)[a]
109 | q -= pIndices[a] * ⍵.stride[a]
110 | r -= pIndices[a] * rStride[a]
111 | pIndices[a--] = 0
112 | if a < 0 then break
113 | q += ⍵.stride[a]
114 | r += rStride[a]
115 | pIndices[a]++
116 |
117 | new APLArray data, shape, stride
118 |
119 | else
120 | assert 0
121 |
--------------------------------------------------------------------------------
/src/vocabulary/comparisons.coffee:
--------------------------------------------------------------------------------
1 | addVocabulary
2 |
3 | # Equals (`=`)
4 | #
5 | # 12 = 12 ←→ 1
6 | # 2 = 12 ←→ 0
7 | # "Q" = "Q" ←→ 1
8 | # 1 = "1" ←→ 0
9 | # "1" = 1 ←→ 0
10 | # 11 7 2 9 = 11 3 2 6 ←→ 1 0 1 0
11 | # "STOAT" = "TOAST" ←→ 0 0 0 0 1
12 | # 8 = 2 + 2 + 2 + 2 ←→ 1
13 | # (2 3⍴1 2 3 4 5 6) = 2 3⍴3 3 3 5 5 5 ←→ 2 3 ⍴ 0 0 1 0 1 0
14 | # 3 = 2 3⍴1 2 3 4 5 6 ←→ 2 3 ⍴ 0 0 1 0 0 0
15 | # 3 = (2 3⍴1 2 3 4 5 6) (2 3⍴3 3 3 5 5 5)
16 | # ... ←→ (2 3 ⍴ 0 0 1 0 0 0) (2 3 ⍴ 1 1 1 0 0 0)
17 | # 2j3=2j3 ←→ 1
18 | # 2j3=3j2 ←→ 0
19 | # 0j0 ←→ 0
20 | # 123j0 ←→ 123
21 | # 2j¯3+¯2j3 ←→ 0
22 | # =/⍬ ←→ 1
23 | '=': withIdentity 1, pervasive dyad: eq = (y, x) ->
24 | if x instanceof Complex and y instanceof Complex
25 | +(x.re is y.re and x.im is y.im)
26 | else
27 | +(x is y)
28 |
29 | # Not equals (`≠`)
30 | #
31 | # 3≢5 ←→ 1
32 | # 8≠8 ←→ 0
33 | # ≠/⍬ ←→ 0
34 | '≠': withIdentity 0, pervasive dyad: (y, x) -> 1 - eq y, x
35 |
36 | # Less than (`<`)
37 | #
38 | # ⍬ ←→ 0
39 | '<': withIdentity 0, pervasive dyad: real (y, x) -> +(x < y)
40 |
41 | # Greater than (`<`)
42 | #
43 | # >/⍬ ←→ 0
44 | '>': withIdentity 0, pervasive dyad: real (y, x) -> +(x > y)
45 |
46 | # Less than or equal to (`≤`)
47 | #
48 | # ≤/⍬ ←→ 1
49 | '≤': withIdentity 1, pervasive dyad: real (y, x) -> +(x <= y)
50 |
51 | # Greater than or equal to (`≥`)
52 | #
53 | # ≥/⍬ ←→ 1
54 | '≥': withIdentity 1, pervasive dyad: real (y, x) -> +(x >= y)
55 |
56 | '≡': (⍵, ⍺) ->
57 | if ⍺
58 |
59 | # Match (`≡`)
60 | #
61 | # 3≡3 ←→ 1
62 | # 3≡,3 ←→ 0
63 | # 4 7.1 8 ≡ 4 7.2 8 ←→ 0
64 | # (3 4⍴⍳12) ≡ 3 4⍴⍳12 ←→ 1
65 | # (3 4⍴⍳12) ≡ ⊂3 4⍴⍳12 ←→ 0
66 | # ("ABC" "DEF") ≡ "ABCDEF" ←→ 0
67 | #! (⍳0)≡"" ←→ 0
68 | # (2 0⍴0)≡(0 2⍴0) ←→ 0
69 | #! (0⍴1 2 3)≡0⍴⊂2 2⍴⍳4 ←→ 0
70 | APLArray.bool[+match ⍵, ⍺]
71 |
72 | else
73 |
74 | # Depth (`≡`)
75 | #
76 | # ≡4 ←→ 0
77 | # ≡⍳4 ←→ 1
78 | # ≡2 2⍴⍳4 ←→ 1
79 | # ≡"abc" 1 2 3 (23 55) ←→ 2
80 | # ≡"abc" (2 4⍴("abc" 2 3 "k")) ←→ 3
81 | new APLArray [depthOf ⍵], []
82 |
83 | depthOf = (x) ->
84 | if x instanceof APLArray
85 | if (!⍴⍴ x) and (x.data[0] !instanceof APLArray) then return 0
86 | r = 0
87 | each x, (y) -> r = Math.max r, depthOf y
88 | r + 1
89 | else
90 | 0
91 |
--------------------------------------------------------------------------------
/src/vocabulary/compose.coffee:
--------------------------------------------------------------------------------
1 | addVocabulary
2 |
3 | # Composition operator (`∘`)
4 | #
5 | # (÷∘-)2 ←→ ¯0.5
6 | # 8(÷∘-)2 ←→ ¯4
7 | # ÷∘-2 ←→ ¯0.5
8 | # 8÷∘-2 ←→ ¯4
9 | # ⍴∘⍴2 3⍴⍳6 ←→ ,2
10 | # 3⍴∘⍴2 3⍴⍳6 ←→ 2 3 2
11 | # 3∘-1 ←→ 2
12 | # (-∘2)9 ←→ 7
13 | '∘': conjunction (g, f) ->
14 | if typeof f is 'function'
15 | if typeof g is 'function'
16 | (⍵, ⍺) -> # f∘g
17 | f (g ⍵), ⍺
18 | else
19 | (⍵, ⍺) -> # f∘B
20 | assert !⍺?
21 | f g, ⍵
22 | else
23 | assert typeof g is 'function'
24 | (⍵, ⍺) -> # A∘g
25 | assert !⍺?
26 | g ⍵, f
27 |
--------------------------------------------------------------------------------
/src/vocabulary/cupcap.coffee:
--------------------------------------------------------------------------------
1 | addVocabulary
2 |
3 | '∪': (⍵, ⍺) ->
4 | if ⍺
5 |
6 | # Union (`∪`)
7 | #
8 | # 1 2∪2 3 ←→ 1 2 3
9 | # 'SHOCK'∪'CHOCOLATE' ←→ 'SHOCKLATE'
10 | # 1∪1 ←→ ,1
11 | # 1∪2 ←→ 1 2
12 | # 1∪2 1 ←→ 1 2
13 | # 1 2∪2 2 2 2 ←→ 1 2
14 | # 1 2∪2 2⍴3 !!! RANK ERROR
15 | # (2 2⍴3)∪4 5 !!! RANK ERROR
16 | # ⍬∪1 ←→ ,1
17 | # 1 2∪⍬ ←→ 1 2
18 | # ⍬∪⍬ ←→ ⍬
19 | #
20 | # 'lentils' 'bulghur' (3 4 5) ∪ 'lentils' 'rice'
21 | # ... ←→ 'lentils' 'bulghur' (3 4 5) 'rice'
22 | data = []
23 | for a in [⍺, ⍵]
24 | if ⍴⍴(a) > 1 then rankError()
25 | each a, (x) -> if !contains data, x then data.push x
26 | new APLArray data
27 |
28 | else
29 |
30 | # Unique (`∪`)
31 | #
32 | # ∪3 17 17 17 ¯3 17 0 ←→ 3 17 ¯3 0
33 | # ∪3 17 ←→ 3 17
34 | # ∪17 ←→ ,17
35 | # ∪⍬ ←→ ⍬
36 | data = []
37 | each ⍵, (x) -> if !contains data, x then data.push x
38 | new APLArray data
39 |
40 | '∩': (⍵, ⍺) ->
41 | if ⍺
42 |
43 | # Intersection (`∩`)
44 | #
45 | # 'ABRA'∩'CAR' ←→ 'ARA'
46 | # 1 'PLUS' 2 ∩ ⍳5 ←→ 1 2
47 | data = []
48 | b = ⍵.toArray()
49 | for x in ⍺.toArray() when contains b, x then data.push x
50 | new APLArray data
51 |
52 | else
53 | nonceError()
54 |
55 | contains = (a, x) ->
56 | assert a.length?
57 | for y in a when match x, y then return true
58 | false
59 |
--------------------------------------------------------------------------------
/src/vocabulary/decode.coffee:
--------------------------------------------------------------------------------
1 | addVocabulary
2 |
3 | # Decode (`⊥`)
4 | #
5 | # 10 ⊥ 3 2 6 9 ←→ 3269
6 | # 8 ⊥ 3 1 ←→ 25
7 | # 1760 3 12 ⊥ 1 2 8 ←→ 68
8 | # 2 2 2 ⊥ 1 ←→ 7
9 | # 0 20 12 4 ⊥ 2 15 6 3 ←→ 2667
10 | # 1760 3 12 ⊥ 3 3⍴1 1 1 2 0 3 0 1 8 ←→ 60 37 80
11 | # 60 60 ⊥ 3 13 ←→ 193
12 | # 0 60 ⊥ 3 13 ←→ 193
13 | # 60 ⊥ 3 13 ←→ 193
14 | # 2 ⊥ 1 0 1 0 ←→ 10
15 | # 2 ⊥ 1 2 3 4 ←→ 26
16 | # 3 ⊥ 1 2 3 4 ←→ 58
17 | #
18 | # //gives '(1j1 ⊥ 1 2 3 4) = 5j9', 1 # todo: ⊥ for complex numbers
19 | #
20 | # M ← (3 8 ⍴
21 | # ... 0 0 0 0 1 1 1 1
22 | # ... 0 0 1 1 0 0 1 1
23 | # ... 0 1 0 1 0 1 0 1)
24 | # ... A ← (4 3 ⍴
25 | # ... 1 1 1
26 | # ... 2 2 2
27 | # ... 3 3 3
28 | # ... 4 4 4)
29 | # ... A ⊥ M
30 | # ... ←→ (4 8⍴
31 | # ... 0 1 1 2 1 2 2 3
32 | # ... 0 1 2 3 4 5 6 7
33 | # ... 0 1 3 4 9 10 12 13
34 | # ... 0 1 4 5 16 17 20 21)
35 | #
36 | # M ← (3 8 ⍴
37 | # ... 0 0 0 0 1 1 1 1
38 | # ... 0 0 1 1 0 0 1 1
39 | # ... 0 1 0 1 0 1 0 1)
40 | # ... 2 ⊥ M
41 | # ... ←→ 0 1 2 3 4 5 6 7
42 | #
43 | # M ← (3 8 ⍴
44 | # ... 0 0 0 0 1 1 1 1
45 | # ... 0 0 1 1 0 0 1 1
46 | # ... 0 1 0 1 0 1 0 1)
47 | # ... A ← 2 1 ⍴ 2 10
48 | # ... A ⊥ M
49 | # ... ←→ (2 8⍴
50 | # ... 0 1 2 3 4 5 6 7
51 | # ... 0 1 10 11 100 101 110 111)
52 | '⊥': (⍵, ⍺) ->
53 | assert ⍺
54 | if !⍴⍴ ⍺ then ⍺ = new APLArray [⍺.unwrap()]
55 | if !⍴⍴ ⍵ then ⍵ = new APLArray [⍵.unwrap()]
56 | lastDimA = ⍴(⍺)[⍴⍴(⍺) - 1]
57 | firstDimB = ⍴(⍵)[0]
58 | if lastDimA isnt 1 and firstDimB isnt 1 and lastDimA isnt firstDimB
59 | lengthError()
60 |
61 | a = ⍺.toArray()
62 | b = ⍵.toArray()
63 | data = []
64 | for i in [0 ... a.length / lastDimA]
65 | for j in [0 ... b.length / firstDimB]
66 | x = a[i * lastDimA ... (i + 1) * lastDimA]
67 | y = for k in [0...firstDimB] then b[j + k * (b.length / firstDimB)]
68 | if x.length is 1 then x = for [0...y.length] then x[0]
69 | if y.length is 1 then y = for [0...x.length] then y[0]
70 | z = y[0]
71 | for k in [1...y.length]
72 | z = z * x[k] + y[k]
73 | data.push z
74 |
75 | new APLArray data, ⍴(⍺)[...-1].concat ⍴(⍵)[1..]
76 |
--------------------------------------------------------------------------------
/src/vocabulary/dot.coffee:
--------------------------------------------------------------------------------
1 | addVocabulary
2 | '.': conjunction (g, f) ->
3 | if f is vocabulary['∘'] then outerProduct g else innerProduct g, f
4 |
5 | # Outer product
6 | #
7 | # 2 3 4 ∘.× 1 2 3 4
8 | # ... ←→ (3 4⍴
9 | # ... 2 4 6 8
10 | # ... 3 6 9 12
11 | # ... 4 8 12 16)
12 | #
13 | # 0 1 2 3 4 ∘.! 0 1 2 3 4
14 | # ... ←→ (5 5⍴
15 | # ... 1 1 1 1 1
16 | # ... 0 1 2 3 4
17 | # ... 0 0 1 3 6
18 | # ... 0 0 0 1 4
19 | # ... 0 0 0 0 1)
20 | #
21 | # 1 2 ∘., 1+⍳3
22 | # ... ←→ (2 3⍴
23 | # ... (1 1) (1 2) (1 3)
24 | # ... (2 1) (2 2) (2 3))
25 | #
26 | # ⍴ 1 2 ∘., 1+⍳3 ←→ 2 3
27 | #
28 | # 2 3 ∘.↑ 1 2
29 | # ... ←→ (2 2⍴
30 | # ... (1 0) (2 0)
31 | # ... (1 0 0) (2 0 0))
32 | #
33 | # ⍴ 2 3 ∘.↑ 1 2 ←→ 2 2
34 | # ⍴ ((4 3 ⍴ 0) ∘.+ (5 2 ⍴ 0)) ←→ 4 3 5 2
35 | # 2 3 ∘.× 4 5 ←→ 2 2⍴ 8 10 12 15
36 | # 2 3 ∘ . × 4 5 ←→ 2 2⍴ 8 10 12 15
37 | # 2 3 ∘.{⍺×⍵} 4 5 ←→ 2 2⍴ 8 10 12 15
38 | outerProduct = (f) ->
39 | assert typeof f is 'function'
40 | (⍵, ⍺) ->
41 | if !⍺ then syntaxError 'Adverb ∘. (Outer product) can be applied to dyadic verbs only'
42 | a = ⍺.toArray()
43 | b = ⍵.toArray()
44 | data = []
45 | for x in a then for y in b
46 | if x !instanceof APLArray then x = APLArray.scalar x
47 | if y !instanceof APLArray then y = APLArray.scalar y
48 | z = f y, x
49 | if !⍴⍴ z then z = z.unwrap()
50 | data.push z
51 | new APLArray data, ⍴(⍺).concat ⍴ ⍵
52 |
53 |
54 | # Inner product (`.`)
55 | #
56 | # For matrices, the inner product behaves like matrix multiplication where +
57 | # and × can be substituted with any verbs.
58 | #
59 | # For higher dimensions, the general formula is:
60 | # `A f.g B <-> f/¨ (⊂[¯1+⍴⍴A]A) ∘.g ⊂[0]B`
61 | #
62 | # (1 3 5 7) +.= 2 3 6 7 ←→ 2
63 | # (1 3 5 7) ∧.= 2 3 6 7 ←→ 0
64 | # (1 3 5 7) ∧.= 1 3 5 7 ←→ 1
65 | # 7 +.= 8 8 7 7 8 7 5 ←→ 3
66 | # 8 8 7 7 8 7 5 +.= 7 ←→ 3
67 | # 7 +.= 7 ←→ 1
68 | # (3 2⍴5 ¯3 ¯2 4 ¯1 0) +.× 2 2⍴6 ¯3 5 7 ←→ 3 2⍴15 ¯36 8 34 ¯6 3
69 | innerProduct = (g, f) ->
70 | F = vocabulary['¨'] reduce f
71 | G = outerProduct g
72 | (⍵, ⍺) ->
73 | if !⍴⍴ ⍺ then ⍺ = new APLArray [⍺.unwrap()]
74 | if !⍴⍴ ⍵ then ⍵ = new APLArray [⍵.unwrap()]
75 | F G(
76 | vocabulary['⊂'](⍵, undefined, new APLArray [0])
77 | vocabulary['⊂'](⍺, undefined, new APLArray [⍴⍴(⍺) - 1])
78 | )
79 |
--------------------------------------------------------------------------------
/src/vocabulary/each.coffee:
--------------------------------------------------------------------------------
1 | addVocabulary
2 |
3 | # Each (`¨`)
4 | #
5 | # ⍴¨ (0 0 0 0) (0 0 0) ←→ (,4) (,3)
6 | # ⍴¨ "MONDAY" "TUESDAY" ←→ (,6) (,7)
7 | # ⍴ (2 2⍴⍳4) (⍳10) 97.3 (3 4⍴"K") ←→ ,4
8 | # ⍴¨ (2 2⍴⍳4) (⍳10) 97.3 (3 4⍴"K") ←→ (2 2) (,10) ⍬ (3 4)
9 | # ⍴⍴¨ (2 2⍴⍳4) (⍳10) 97.3 (3 4⍴"K") ←→ ,4
10 | # ⍴¨⍴¨ (2 2⍴⍳4) (⍳10) 97.3 (3 4⍴"K") ←→ (,2) (,1) (,0) (,2)
11 | # (1 2 3) ,¨ 4 5 6 ←→ (1 4) (2 5) (3 6)
12 | # 2 3 ↑¨ 'MONDAY' 'TUESDAY' ←→ 'MO' 'TUE'
13 | # 2 ↑¨ 'MONDAY' 'TUESDAY' ←→ 'MO' 'TU'
14 | # 2 3 ⍴¨ 1 2 ←→ (1 1) (2 2 2)
15 | # 4 5 ⍴¨ "THE" "CAT" ←→ 'THET' 'CATCA'
16 | # {1+⍵*2}¨ 2 3 ⍴ ⍳ 6 ←→ 2 3 ⍴ 1 2 5 10 17 26
17 | '¨': adverb (f, g) ->
18 | assert typeof f is 'function'
19 | assert typeof g is 'undefined'
20 | (⍵, ⍺) ->
21 | if !⍺
22 | ⍵.map (x) ->
23 | if x !instanceof APLArray then x = new APLArray [x], []
24 | r = f x
25 | assert r instanceof APLArray
26 | if ⍴⍴ r then r else r.unwrap()
27 | else if arrayEquals ⍴(⍺), ⍴(⍵)
28 | ⍵.map2 ⍺, (x, y) ->
29 | if x !instanceof APLArray then x = new APLArray [x], []
30 | if y !instanceof APLArray then y = new APLArray [y], []
31 | r = f x, y
32 | assert r instanceof APLArray
33 | if ⍴⍴ r then r else r.unwrap()
34 | else if ⍺.isSingleton()
35 | y = if ⍺.data[0] instanceof APLArray then ⍺.unwrap() else ⍺
36 | ⍵.map (x) ->
37 | if x !instanceof APLArray then x = new APLArray [x], []
38 | r = f x, y
39 | assert r instanceof APLArray
40 | if ⍴⍴ r then r else r.unwrap()
41 | else if ⍵.isSingleton()
42 | x = if ⍵.data[0] instanceof APLArray then ⍵.unwrap() else ⍵
43 | ⍺.map (y) ->
44 | if y !instanceof APLArray then y = new APLArray [y], []
45 | r = f x, y
46 | assert r instanceof APLArray
47 | if ⍴⍴ r then r else r.unwrap()
48 | else
49 | lengthError()
50 |
--------------------------------------------------------------------------------
/src/vocabulary/encode.coffee:
--------------------------------------------------------------------------------
1 | addVocabulary
2 |
3 | # Encode (`⊤`)
4 | #
5 | # 1760 3 12⊤75 ←→ 2 0 3
6 | # 3 12⊤75 ←→ 0 3
7 | # 100000 12⊤75 ←→ 6 3
8 | # 16 16 16 16⊤100 ←→ 0 0 6 4
9 | # 1760 3 12⊤75.3 ←→ 2 0 (75.3-72)
10 | # 0 1⊤75.3 ←→ 75 (75.3-75)
11 | #
12 | # 2 2 2 2 2⊤1 2 3 4 5 ←→ (5 5 ⍴
13 | # ... 0 0 0 0 0
14 | # ... 0 0 0 0 0
15 | # ... 0 0 0 1 1
16 | # ... 0 1 1 0 0
17 | # ... 1 0 1 0 1)
18 | #
19 | # 10⊤5 15 125 ←→ 5 5 5
20 | # 0 10⊤5 15 125 ←→ 2 3⍴ 0 1 12 5 5 5
21 | #
22 | # (8 3⍴ 2 0 0
23 | # ... 2 0 0
24 | # ... 2 0 0
25 | # ... 2 0 0
26 | # ... 2 8 0
27 | # ... 2 8 0
28 | # ... 2 8 16
29 | # ... 2 8 16) ⊤ 75
30 | # ... ←→ (8 3⍴
31 | # ... 0 0 0
32 | # ... 1 0 0
33 | # ... 0 0 0
34 | # ... 0 0 0
35 | # ... 1 0 0
36 | # ... 0 1 0
37 | # ... 1 1 4
38 | # ... 1 3 11)
39 | '⊤': (⍵, ⍺) ->
40 | assert ⍺
41 | a = ⍺.toArray()
42 | b = ⍵.toArray()
43 | shape = ⍴(⍺).concat ⍴ ⍵
44 | data = Array prod shape
45 | n = if ⍴⍴ ⍺ then ⍴(⍺)[0] else 1
46 | m = a.length / n
47 | for i in [0...m]
48 | for y, j in b
49 | if isNeg = (y < 0) then y = -y
50 | for k in [n - 1 .. 0] by -1
51 | x = a[k * m + i]
52 | if x is 0
53 | data[(k * m + i) * b.length + j] = y
54 | y = 0
55 | else
56 | data[(k * m + i) * b.length + j] = y % x
57 | y = Math.round((y - (y % x)) / x)
58 | new APLArray data, shape
59 |
--------------------------------------------------------------------------------
/src/vocabulary/epsilon.coffee:
--------------------------------------------------------------------------------
1 | addVocabulary
2 |
3 | '∊': (⍵, ⍺) ->
4 | if ⍺
5 | # Membership (`∊`)
6 | #
7 | # 2 3 4 5 6 ∊ 1 2 3 5 8 13 21 ←→ 1 1 0 1 0
8 | # 5 ∊ 1 2 3 5 8 13 21 ←→ 1
9 | a = ⍵.toArray()
10 | ⍺.map (x) ->
11 | for y in a when match x, y then return 1
12 | 0
13 | else
14 | # Enlist (`∊`)
15 | #
16 | # ∊ 17 ←→ ,17
17 | # ⍴ ∊ (1 2 3) "ABC" (4 5 6) ←→ ,9
18 | # ∊ 2 2⍴(1 + 2 2⍴⍳4) "DEF" (1 + 2 3⍴⍳6) (7 8 9)
19 | # ... ←→ 1 2 3 4,'DEF',1 2 3 4 5 6 7 8 9
20 | data = []
21 | enlist ⍵, data
22 | new APLArray data
23 |
24 | enlist = (x, r) ->
25 | if x instanceof APLArray
26 | each x, (y) -> enlist y, r
27 | else
28 | r.push x
29 |
--------------------------------------------------------------------------------
/src/vocabulary/exclamation.coffee:
--------------------------------------------------------------------------------
1 | addVocabulary
2 |
3 | '!': withIdentity 1, pervasive
4 |
5 | # Factorial (`!`)
6 | #
7 | # !5 ←→ 120
8 | # !21 ←→ 51090942171709440000
9 | # !0 ←→ 1
10 | # !1.5 ←→ 1.3293403881791
11 | # !¯1.5 ←→ ¯3.544907701811
12 | # !¯2.5 ←→ 2.3632718012074
13 | # !¯200.5 ←→ 0
14 | # !¯1 !!! DOMAIN ERROR
15 | # !¯200 !!! DOMAIN ERROR
16 | monad: real (x) ->
17 | if !isInt x then Γ(x + 1)
18 | else if x < 0 then domainError()
19 | else if x < smallFactorials.length then smallFactorials[x]
20 | else Math.round Γ(x + 1)
21 |
22 | # Binomial (`!`)
23 | #
24 | # 2 ! 4 ←→ 6
25 | # 3 ! 20 ←→ 1140
26 | # 2 ! 6 12 20 ←→ 15 66 190
27 | # (2 3⍴1+⍳6)!2 3⍴3 6 9 12 15 18 ←→ 2 3⍴ 3 15 84 495 3003 18564
28 | # 0.5!1 ←→ 1.2732395447351612
29 | # 1.2!3.4 ←→ 3.795253463731253
30 | # !/⍬ ←→ 1
31 | # (2!1000)=499500 ←→ 1
32 | # (998!1000)=499500 ←→ 1
33 | #
34 | # Negative integer? Expected
35 | # ⍺ ⍵ ⍵-⍺ Result
36 | # ----------- ----------
37 | # 3!5 ←→ 10 # 0 0 0 (!⍵)÷(!⍺)×!⍵-⍺
38 | # 5!3 ←→ 0 # 0 0 1 0
39 | # see below # 0 1 0 Domain Error
40 | # 3!¯5 ←→ ¯35 # 0 1 1 (¯1*⍺)×⍺!⍺-⍵+1
41 | # ¯3!5 ←→ 0 # 1 0 0 0
42 | # # 1 0 1 Cannot arise
43 | # ¯5!¯3 ←→ 6 # 1 1 0 (¯1*⍵-⍺)×(|⍵+1)!(|⍺+1)
44 | # ¯3!¯5 ←→ 0 # 1 1 1 0
45 | #
46 | # 0.5!¯1 !!! DOMAIN ERROR
47 | dyad: Beta = real (n, k) ->
48 | r = switch 4*negInt(k) + 2*negInt(n) + negInt(n - k)
49 | when 0b000 then Math.exp lnΓ(n + 1) - lnΓ(k + 1) - lnΓ(n - k + 1)
50 | when 0b001 then 0
51 | when 0b010 then domainError()
52 | when 0b011 then Math.pow(-1, k) * Beta k - n - 1, k
53 | when 0b100 then 0
54 | when 0b101 then ;
55 | when 0b110 then Math.pow(-1, n - k) * Beta Math.abs(k + 1), Math.abs(n + 1)
56 | when 0b111 then 0
57 | if isInt(n) and isInt(k) then Math.round r else r
58 |
59 | negInt = (x) -> isInt(x) and x < 0
60 | smallFactorials = do -> [x = 1].concat(for i in [1..25] then x *= i)
61 |
62 | {Γ, lnΓ} = do ->
63 | g = 7
64 | p = [0.99999999999980993, 676.5203681218851, -1259.1392167224028, 771.32342877765313, -176.61502916214059, 12.507343278686905, -0.13857109526572012, 9.9843695780195716e-6, 1.5056327351493116e-7]
65 | g_ln = 607/128
66 | p_ln = [0.99999999999999709182, 57.156235665862923517, -59.597960355475491248, 14.136097974741747174, -0.49191381609762019978, 0.33994649984811888699e-4, 0.46523628927048575665e-4, -0.98374475304879564677e-4, 0.15808870322491248884e-3, -0.21026444172410488319e-3, 0.21743961811521264320e-3, -0.16431810653676389022e-3, 0.84418223983852743293e-4, -0.26190838401581408670e-4, 0.36899182659531622704e-5]
67 |
68 | # Spouge approximation (suitable for large arguments)
69 | lnΓ: (z) ->
70 | if z < 0 then return NaN
71 | x = p_ln[0]
72 | for i in [p_ln.length - 1 ... 0] by -1 then x += p_ln[i] / (z + i)
73 | t = z + g_ln + .5
74 | .5*Math.log(2*Math.PI)+(z+.5)*Math.log(t)-t+Math.log(x)-Math.log(z)
75 |
76 | Γ: (z) ->
77 | if z < 0.5 then Math.PI / (Math.sin(Math.PI * z) * Γ(1 - z))
78 | else if z > 100 then Math.exp lnΓ z
79 | else
80 | z--
81 | x = p[0]
82 | for i in [1...g+2] by 1 then x += p[i] / (z + i)
83 | t = z + g + .5
84 | Math.sqrt(2 * Math.PI) * Math.pow(t, z + 0.5) * Math.exp(-t) * x
85 |
--------------------------------------------------------------------------------
/src/vocabulary/execute.coffee:
--------------------------------------------------------------------------------
1 | addVocabulary
2 |
3 | # Execute (`⍎`)
4 | #
5 | # ⍎'+/ 2 2 ⍴ 1 2 3 4' ←→ 3 7
6 | # ⍴⍎'123 456' ←→ ,2
7 | # ⍎'{⍵*2} ⍳5' ←→ 0 1 4 9 16
8 | # ⍎'undefinedVariable' !!!
9 | # ⍎'1 2 (3' !!!
10 | # ⍎123 !!!
11 | '⍎': (⍵, ⍺) -> if ⍺ then nonceError() else exec ⍵.toSimpleString()
12 |
--------------------------------------------------------------------------------
/src/vocabulary/find.coffee:
--------------------------------------------------------------------------------
1 | addVocabulary
2 |
3 | '⍷': (⍵, ⍺) ->
4 | if ⍺
5 |
6 | # Find (`⍷`)
7 | #
8 | # "AN"⍷"BANANA" ←→ 0 1 0 1 0 0
9 | # "BIRDS" "NEST"⍷"BIRDS" "NEST" "SOUP" ←→ 1 0 0
10 | # "ME"⍷"HOME AGAIN" ←→ 0 0 1 0 0 0 0 0 0 0
11 | #
12 | # "DAY"⍷7 9⍴("SUNDAY ",
13 | # ... "MONDAY ",
14 | # ... "TUESDAY ",
15 | # ... "WEDNESDAY",
16 | # ... "THURSDAY ",
17 | # ... "FRIDAY ",
18 | # ... "SATURDAY ")
19 | # ... ←→ (7 9 ⍴
20 | # ... 0 0 0 1 0 0 0 0 0
21 | # ... 0 0 0 1 0 0 0 0 0
22 | # ... 0 0 0 0 1 0 0 0 0
23 | # ... 0 0 0 0 0 0 1 0 0
24 | # ... 0 0 0 0 0 1 0 0 0
25 | # ... 0 0 0 1 0 0 0 0 0
26 | # ... 0 0 0 0 0 1 0 0 0)
27 | #
28 | # (2 2⍴"ABCD")⍷"ABCD" ←→ 4 ⍴ 0
29 | # (1 2) (3 4) ⍷ "START" (1 2 3)(1 2)(3 4) ←→ 0 0 1 0
30 | #
31 | # (2 2⍴7 8 12 13)⍷ 1+ 4 5⍴⍳20
32 | # ... ←→ 4 5⍴(0 0 0 0 0
33 | # ... 0 1 0 0 0
34 | # ... 0 0 0 0 0
35 | # ... 0 0 0 0 0)
36 | #
37 | # 1⍷⍳5 ←→ 0 1 0 0 0
38 | # 1 2⍷⍳5 ←→ 0 1 0 0 0
39 | # ⍬⍷⍳5 ←→ 1 1 1 1 1
40 | # ⍬⍷⍬ ←→ ⍬
41 | # 1⍷⍬ ←→ ⍬
42 | # 1 2 3⍷⍬ ←→ ⍬
43 | # (2 3 0⍴0)⍷(3 4 5⍴0) ←→ 3 4 5⍴1
44 | # (2 3 4⍴0)⍷(3 4 0⍴0) ←→ 3 4 0⍴0
45 | # (2 3 0⍴0)⍷(3 4 0⍴0) ←→ 3 4 0⍴0
46 | if ⍴⍴(⍺) > ⍴⍴(⍵) then return new APLArray [0], ⍴(⍵), repeat [0], ⍴⍴ ⍵
47 | if ⍴⍴(⍺) < ⍴⍴(⍵)
48 | ⍺ = new APLArray( # prepend ones to the shape of ⍺
49 | ⍺.data
50 | repeat([1], ⍴⍴(⍵) - ⍴⍴(⍺)).concat ⍴ ⍺
51 | repeat([0], ⍴⍴(⍵) - ⍴⍴(⍺)).concat ⍺.stride
52 | ⍺.offset
53 | )
54 | if prod(⍴ ⍺) is 0
55 | return new APLArray [1], ⍴(⍵), repeat [0], ⍴⍴ ⍵
56 | findShape = []
57 | for i in [0...⍴⍴ ⍵]
58 | d = ⍴(⍵)[i] - ⍴(⍺)[i] + 1
59 | if d <= 0 then return new APLArray [0], ⍴(⍵), repeat [0], ⍴⍴ ⍵
60 | findShape.push d
61 | stride = strideForShape ⍴ ⍵
62 | data = repeat [0], prod ⍴ ⍵
63 | p = ⍵.offset
64 | q = 0
65 | indices = repeat [0], findShape.length
66 | loop
67 | data[q] = +match ⍺, new APLArray ⍵.data, ⍴(⍺), ⍵.stride, p
68 | a = findShape.length - 1
69 | while a >= 0 and indices[a] + 1 is findShape[a]
70 | p -= indices[a] * ⍵.stride[a]
71 | q -= indices[a] * stride[a]
72 | indices[a--] = 0
73 | if a < 0 then break
74 | p += ⍵.stride[a]
75 | q += stride[a]
76 | indices[a]++
77 | new APLArray data, ⍴ ⍵
78 |
79 | else
80 | nonceError()
81 |
--------------------------------------------------------------------------------
/src/vocabulary/floorceil.coffee:
--------------------------------------------------------------------------------
1 | addVocabulary
2 |
3 | '⌊': withIdentity Infinity, pervasive
4 |
5 | # Floor (`⌊`)
6 | #
7 | # ⌊123 ←→ 123
8 | # ⌊12.3 ←→ 12
9 | # ⌊¯12.3 ←→ ¯13
10 | # ⌊¯123 ←→ ¯123
11 | # ⌊'a' !!! DOMAIN ERROR
12 | # ⌊12j3 ←→ 12j3
13 | # ⌊1.2j2.3 ←→ 1j2
14 | # ⌊1.2j¯2.3 ←→ 1j¯3
15 | # ⌊¯1.2j2.3 ←→ ¯1j2
16 | # ⌊¯1.2j¯2.3 ←→ ¯1j¯3
17 | # ⌊0 5 ¯5 (○1) ¯1.5 ←→ 0 5 ¯5 3 ¯2
18 | monad: Complex.floor
19 |
20 | # Lesser of (`⌊`)
21 | #
22 | # 3⌊5 ←→ 3
23 | # ⌊/⍬ ←→ ¯
24 | dyad: real (y, x) -> Math.min y, x
25 |
26 | '⌈': withIdentity -Infinity, pervasive
27 |
28 | # Ceiling (`⌈`)
29 | #
30 | # ⌈123 ←→ 123
31 | # ⌈12.3 ←→ 13
32 | # ⌈¯12.3 ←→ ¯12
33 | # ⌈¯123 ←→ ¯123
34 | # ⌈'a' !!! DOMAIN ERROR
35 | # ⌈12j3 ←→ 12j3
36 | # ⌈1.2j2.3 ←→ 1j3
37 | # ⌈1.2j¯2.3 ←→ 1j¯2
38 | # ⌈¯1.2j2.3 ←→ ¯1j3
39 | # ⌈¯1.2j¯2.3 ←→ ¯1j¯2
40 | # ⌈0 5 ¯5 (○1) ¯1.5 ←→ 0 5 ¯5 4 ¯1
41 | monad: Complex.ceil
42 |
43 | # Greater of (`⌈`)
44 | #
45 | # 3⌈5 ←→ 5
46 | # ⌈/⍬ ←→ ¯¯
47 | dyad: real (y, x) -> Math.max y, x
48 |
--------------------------------------------------------------------------------
/src/vocabulary/fork.coffee:
--------------------------------------------------------------------------------
1 | addVocabulary
2 |
3 | # [Phrasal forms](http://www.jsoftware.com/papers/fork1.htm)
4 |
5 | # Fork: `(fgh)⍵ ← → (f⍵)g(h⍵)` ; `⍺(fgh)⍵ ← → (⍺f⍵)g(⍺h⍵)`
6 | #
7 | # Arithmetic mean
8 | # (+/÷⍴) 4 5 10 7 ←→ ,6.5
9 | #
10 | # Quadratic equation
11 | # a←1 ⋄ b←¯22 ⋄ c←85
12 | # ... √ ← {⍵*.5}
13 | # ... ((-b)(+,-)√(b*2)-4×a×c) ÷ 2×a
14 | # ... ←→ 17 5
15 | #
16 | # Trains (longer forks)
17 | # (+,-,×,÷) 2 ←→ 2 ¯2 1 .5
18 | # 1 (+,-,×,÷) 2 ←→ 3 ¯1 2 .5
19 | _fork1: (h, g) ->
20 | assert typeof h is 'function'
21 | assert typeof g is 'function'
22 | [h, g]
23 |
24 | _fork2: ([h, g], f) ->
25 | assert typeof h is 'function'
26 | (b, a) -> g h(b, a), f b, a
27 |
--------------------------------------------------------------------------------
/src/vocabulary/format.coffee:
--------------------------------------------------------------------------------
1 | addVocabulary
2 |
3 | '⍕': (⍵, ⍺) ->
4 | if ⍺
5 |
6 | # Format by example or specification (`⍕`)
7 | nonceError()
8 |
9 | else
10 |
11 | # Format (`⍕`)
12 | #
13 | # ⍕123 ←→ 1 3⍴'123'
14 | # ⍕123 456 ←→ 1 7⍴'123 456'
15 | # ⍕123 'a' ←→ 1 5⍴'123 a'
16 | # ⍕12 'ab' ←→ 1 7⍴'12 ab '
17 | # ⍕1 2⍴'a' ←→ 1 2⍴'a'
18 | # ⍕2 2⍴'a' ←→ 2 2⍴'a'
19 | # ⍕2 2⍴5 ←→ 2 3⍴('5 5',
20 | # ... '5 5')
21 | # ⍕2 2⍴0 0 0 'a' ←→ 2 3⍴('0 0',
22 | # ... '0 a')
23 | # ⍕2 2⍴0 0 0 'ab' ←→ 2 6⍴('0 0 ',
24 | # ... '0 ab ')
25 | # ⍕2 2⍴0 0 0 123 ←→ 2 5⍴('0 0',
26 | # ... '0 123')
27 | # ⍕4 3 ⍴ '---' '---' '---' 1 2 3 4 5 6 100 200 300
28 | # ... ←→ 4 17⍴(' --- --- --- ',
29 | # ... ' 1 2 3 ',
30 | # ... ' 4 5 6 ',
31 | # ... ' 100 200 300 ')
32 | # ⍕1 ⍬ 2 '' 3 ←→ 1 11⍴'1 2 3'
33 | # ⍕∞ ←→ 1 1⍴'∞'
34 | # ⍕¯∞ ←→ 1 2⍴'¯∞'
35 | # ⍕¯1 ←→ 1 2⍴'¯1'
36 | # ⍕¯1e¯100J¯2e¯99 ←→ 1 14⍴'¯1e¯100J¯2e¯99'
37 | t = format ⍵
38 | new APLArray t.join(''), [t.length, t[0].length]
39 |
40 | # Format an APL object as an array of strings
41 | format = (a) ->
42 | if typeof a is 'undefined' then ['undefined']
43 | else if a is null then ['null']
44 | else if typeof a is 'string' then [a]
45 | else if typeof a is 'number' then r = [formatNumber a]; r.align = 'right'; r
46 | else if typeof a is 'function' then ['λ']
47 | else if !(a instanceof APLArray) then ['' + a]
48 | else if prod(⍴ a) is 0 then ['']
49 | else
50 | sa = ⍴ a
51 | a = a.toArray()
52 | if !sa.length then return format a[0]
53 | nRows = prod sa[...sa.length - 1]
54 | nCols = sa[sa.length - 1]
55 |
56 | rows = for [0...nRows]
57 | height: 0
58 | bottomMargin: 0
59 |
60 | cols = for [0...nCols]
61 | type: 0 # 0=characters, 1=numbers, 2=subarrays
62 | width: 0
63 | leftMargin: 0
64 | rightMargin: 0
65 |
66 | grid =
67 | for r, i in rows
68 | for c, j in cols
69 | x = a[nCols * i + j]
70 | box = format x
71 | r.height = Math.max r.height, box.length
72 | c.width = Math.max c.width, box[0].length
73 | c.type = Math.max c.type,
74 | if typeof x is 'string' and x.length is 1 then 0
75 | else if !(x instanceof APLArray) then 1
76 | else 2
77 | box
78 |
79 | step = 1
80 | for d in [sa.length - 2..1] by -1
81 | step *= sa[d]
82 | for i in [step - 1...nRows - 1] by step
83 | rows[i].bottomMargin++
84 |
85 | for c, j in cols
86 | if j isnt nCols - 1 and !(c.type is cols[j + 1].type is 0)
87 | c.rightMargin++
88 | if c.type is 2
89 | c.leftMargin++
90 | c.rightMargin++
91 |
92 | result = []
93 | for r, i in rows
94 | for c, j in cols
95 | t = grid[i][j]
96 | if t.align is 'right'
97 | left = repeat ' ', c.leftMargin + c.width - t[0].length
98 | right = repeat ' ', c.rightMargin
99 | else
100 | left = repeat ' ', c.leftMargin
101 | right = repeat ' ', c.rightMargin + c.width - t[0].length
102 | for k in [0...t.length] then t[k] = left + t[k] + right
103 | bottom = repeat ' ', t[0].length
104 | for [t.length...r.height + r.bottomMargin] then t.push bottom
105 | for k in [0...r.height + r.bottomMargin]
106 | result.push((for j in [0...nCols] then grid[i][j][k]).join '')
107 |
108 | result
109 |
--------------------------------------------------------------------------------
/src/vocabulary/grade.coffee:
--------------------------------------------------------------------------------
1 | addVocabulary
2 |
3 | # Grade up/down (`⍋`)
4 | #
5 | # ⍋13 8 122 4 ←→ 3 1 0 2
6 | # a←13 8 122 4 ⋄ a[⍋a] ←→ 4 8 13 122
7 | # ⍋"ZAMBIA" ←→ 1 5 3 4 2 0
8 | # s←"ZAMBIA" ⋄ s[⍋s] ←→ 'AABIMZ'
9 | # t←3 3⍴"BOBALFZAK" ⋄ ⍋t ←→ 1 0 2
10 | # t←3 3⍴4 5 6 1 1 3 1 1 2 ⋄ ⍋t ←→ 2 1 0
11 | #
12 | # t←3 3⍴4 5 6 1 1 3 1 1 2 ⋄ t[⍋t;]
13 | # ... ←→ (3 3⍴ 1 1 2
14 | # ... 1 1 3
15 | # ... 4 5 6)
16 | #
17 | # a←3 2 3⍴2 3 4 0 1 0 1 1 3 4 5 6 1 1 2 10 11 12 ⋄ a[⍋a;;]
18 | # ... ←→ (3 2 3 ⍴
19 | # ... 1 1 2
20 | # ... 10 11 12
21 | # ...
22 | # ... 1 1 3
23 | # ... 4 5 6
24 | # ...
25 | # ... 2 3 4
26 | # ... 0 1 0)
27 | #
28 | # a←3 2 5⍴"joe doe bob jonesbob zwart" ⋄ a[⍋a;;]
29 | # ... ←→ 3 2 5 ⍴ 'bob jonesbob zwartjoe doe '
30 | #
31 | # "ZYXWVUTSRQPONMLKJIHGFEDCBA"⍋"ZAMBIA" ←→ 0 2 4 3 1 5
32 | # ⎕A←"ABCDEFGHIJKLMNOPQRSTUVWXYZ" ⋄ (⌽⎕A)⍋3 3⍴"BOBALFZAK" ←→ 2 0 1
33 | #
34 | # data←6 4⍴"ABLEaBLEACREABELaBELACES"
35 | # ... coll←2 26⍴"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
36 | # ... data[coll⍋data;]
37 | # ... ←→ 6 4 ⍴ 'ABELaBELABLEaBLEACESACRE'
38 | #
39 | # data←6 4⍴"ABLEaBLEACREABELaBELACES"
40 | # ... coll1←"AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz"
41 | # ... data[coll1⍋data;]
42 | # ... ←→ 6 4 ⍴ 'ABELABLEACESACREaBELaBLE'
43 | #
44 | # ⍋0 1 2 3 4 3 6 6 4 9 1 11 12 13 14 15 ←→ 0 1 10 2 3 5 4 8 6 7 9 11 12 13 14 15
45 | '⍋': (⍵, ⍺) -> grade ⍵, ⍺, 1
46 |
47 | # Grade down (`⍒`)
48 | #
49 | # ⍒3 1 8 ←→ 2 0 1
50 | '⍒': (⍵, ⍺) -> grade ⍵, ⍺, -1
51 |
52 | # Helper for `⍋` and `⍒`
53 | grade = (⍵, ⍺, direction) ->
54 | h = {} # maps a character to its index in the collation
55 | if ⍺
56 | if !⍴⍴ ⍺ then rankError()
57 | h = {}
58 | each ⍺, (x, indices) ->
59 | if typeof x isnt 'string' then domainError()
60 | h[x] = indices[indices.length - 1]
61 |
62 | if !⍴⍴ ⍵ then rankError()
63 |
64 | new APLArray [0...⍴(⍵)[0]]
65 | .sort (i, j) ->
66 | p = ⍵.offset
67 | indices = repeat [0], ⍴⍴ ⍵
68 | loop
69 | x = ⍵.data[p + i * ⍵.stride[0]]
70 | y = ⍵.data[p + j * ⍵.stride[0]]
71 | tx = typeof x
72 | ty = typeof y
73 | if tx < ty then return -direction
74 | if tx > ty then return direction
75 | if h[x]? then x = h[x]
76 | if h[y]? then y = h[y]
77 | if x < y then return -direction
78 | if x > y then return direction
79 | a = indices.length - 1
80 | while a > 0 and indices[a] + 1 is ⍴(⍵)[a]
81 | p -= ⍵.stride[a] * indices[a]
82 | indices[a--] = 0
83 | if a <= 0 then break
84 | p += ⍵.stride[a]
85 | indices[a]++
86 | (i > j) - (i < j)
87 |
--------------------------------------------------------------------------------
/src/vocabulary/identity.coffee:
--------------------------------------------------------------------------------
1 | addVocabulary
2 |
3 | # Identity operator (`⍁`)
4 | #
5 | # f←{⍺+2×⍵} ⋄ f/⍬ !!! DOMAIN ERROR
6 | # f←{⍺+2×⍵} ⋄ (f⍁123)/⍬ ←→ 123
7 | # f←{⍺+2×⍵} ⋄ (456⍁f)/⍬ ←→ 456
8 | # f←{⍺+2×⍵} ⋄ g←f⍁789 ⋄ f/⍬ !!! DOMAIN ERROR
9 | # {}⍁1 2 !!! RANK ERROR
10 | # ({}⍁(1 1 1⍴123))/⍬ ←→ 123
11 | '⍁': conjunction (f, x) ->
12 | if f instanceof APLArray then [f, x] = [x, f]
13 | assert typeof f is 'function'
14 | assert x instanceof APLArray
15 | if !x.isSingleton() then rankError()
16 | if ⍴⍴ x then x = APLArray.scalar x.unwrap()
17 | withIdentity x, (⍵, ⍺, axis) -> f ⍵, ⍺, axis
18 |
--------------------------------------------------------------------------------
/src/vocabulary/iota.coffee:
--------------------------------------------------------------------------------
1 | addVocabulary
2 |
3 | '⍳': (⍵, ⍺) ->
4 | if ⍺
5 | # Index of (`⍳`)
6 | #
7 | # 2 5 9 14 20 ⍳ 9 ←→ 2
8 | # 2 5 9 14 20 ⍳ 6 ←→ 5
9 | # "GORSUCH" ⍳ "S" ←→ 3
10 | # "ABCDEFGHIJKLMNOPQRSTUVWXYZ" ⍳ "CARP" ←→ 2 0 17 15
11 | # "ABCDEFGHIJKLMNOPQRSTUVWXYZ" ⍳ "PORK PIE"
12 | # ... ←→ 15 14 17 10 26 15 8 4
13 | # "MON" "TUES" "WED" ⍳ "MON" "THURS" ←→ 0 3
14 | # 1 3 2 0 3 ⍳ ⍳ 5 ←→ 3 0 2 1 5
15 | # "CAT" "DOG" "MOUSE" ⍳ "DOG" "BIRD" ←→ 1 3
16 | # 123 ⍳ 123 !!! RANK ERROR
17 | # (2 2⍴123) ⍳ 123 !!! RANK ERROR
18 | # 123 123 ⍳ 123 ←→ 0
19 | # ⍬ ⍳ 123 234 ←→ 0 0
20 | # 123 234 ⍳ ⍬ ←→ ⍬
21 | if ⍴⍴(⍺) isnt 1 then rankError()
22 | ⍵.map (x) ->
23 | try
24 | rank = ⍴ ⍺
25 | each ⍺, (y, indices) ->
26 | if match x, y
27 | rank = indices
28 | throw 'break'
29 | catch e
30 | if e isnt 'break' then throw e
31 | if rank.length is 1 then rank[0] else new APLArray rank
32 | else
33 | # Index generate (`⍳`)
34 | #
35 | # ⍳5 ←→ 0 1 2 3 4
36 | # ⍴⍳5 ←→ 1 ⍴ 5
37 | # ⍳0 ←→ ⍬
38 | # ⍴⍳0 ←→ ,0
39 | # ⍳2 3 4 ←→ (2 3 4 ⍴
40 | # ... (0 0 0)(0 0 1)(0 0 2)(0 0 3)
41 | # ... (0 1 0)(0 1 1)(0 1 2)(0 1 3)
42 | # ... (0 2 0)(0 2 1)(0 2 2)(0 2 3)
43 | # ... (1 0 0)(1 0 1)(1 0 2)(1 0 3)
44 | # ... (1 1 0)(1 1 1)(1 1 2)(1 1 3)
45 | # ... (1 2 0)(1 2 1)(1 2 2)(1 2 3))
46 | # ⍴⍳2 3 4 ←→ 2 3 4
47 | # ⍳¯1 !!! DOMAIN ERROR
48 | if ⍴⍴(⍵) > 1 then rankError()
49 | a = ⍵.toArray()
50 | for d in a when !isInt d, 0 then domainError()
51 | n = prod a
52 | if !n
53 | data = []
54 | else if a.length is 1
55 | data =
56 | if n <= 0x100 then new Uint8Array n
57 | else if n <= 0x10000 then new Uint16Array n
58 | else if n <= 0x100000000 then new Uint32Array n
59 | else domainError()
60 | for i in [0...n] by 1 then data[i] = i
61 | else
62 | m = Math.max a...
63 | A =
64 | if m <= 0x100 then Uint8Array
65 | else if m <= 0x10000 then Uint16Array
66 | else if m <= 0x100000000 then Uint32Array
67 | else domainError()
68 | itemData = new A n * a.length
69 | u = n
70 | for i in [0...a.length] by 1
71 | u /= a[i]
72 | p = n * i
73 | for j in [0...a[i]] by 1
74 | itemData[p] = j
75 | spread itemData, p, 1, u
76 | p += u
77 | spread itemData, n * i, a[i] * u, n
78 | data = []
79 | itemShape = [a.length]
80 | itemStride = [n]
81 | for i in [0...n] by 1
82 | data.push new APLArray itemData, itemShape, itemStride, i
83 | new APLArray data, a
84 |
--------------------------------------------------------------------------------
/src/vocabulary/leftshoe.coffee:
--------------------------------------------------------------------------------
1 | addVocabulary
2 |
3 | # ⍴⊂2 3⍴⍳6 ←→ ⍬
4 | # ⍴⍴⊂2 3⍴⍳6 ←→ ,0
5 | # ⊂[0]2 3⍴⍳6 ←→ (0 3)(1 4)(2 5)
6 | # ⍴⊂[0]2 3⍴⍳6 ←→ ,3
7 | # ⊂[1]2 3⍴⍳6 ←→ (0 1 2)(3 4 5)
8 | # ⍴⊂[1]2 3⍴⍳6 ←→ ,2
9 | # ⊃⊂[1 0]2 3⍴⍳6 ←→ 3 2⍴0 3 1 4 2 5
10 | # ⍴⊂[1 0]2 3⍴⍳6 ←→ ⍬
11 | # ⍴⊃⊂⊂1 2 3 ←→ ⍬
12 | '⊂': (⍵, ⍺, axes) ->
13 | assert !⍺
14 | axes = if axes? then getAxisList axes, ⍴⍴ ⍵ else [0...⍴⍴ ⍵]
15 | if ⍵.isSimple() then return ⍵
16 | unitShape = for i in axes then ⍴(⍵)[i]
17 | unitStride = for i in axes then ⍵.stride[i]
18 | resultAxes = for i in [0...⍴⍴ ⍵] when i !in axes then i
19 | shape = for i in resultAxes then ⍴(⍵)[i]
20 | stride = for i in resultAxes then ⍵.stride[i]
21 | data = []
22 | each new APLArray(⍵.data, shape, stride, ⍵.offset),
23 | (x, indices, p) ->
24 | data.push new APLArray ⍵.data, unitShape, unitStride, p
25 | new APLArray data, shape
26 |
--------------------------------------------------------------------------------
/src/vocabulary/logic.coffee:
--------------------------------------------------------------------------------
1 | addVocabulary
2 |
3 | '~': pervasive
4 | # Not (`~`)
5 | #
6 | # ~0 1 ←→ 1 0
7 | # ~2 !!! DOMAIN ERROR
8 | monad: (x) -> +!bool x
9 |
10 | '∨': withIdentity 0, pervasive
11 |
12 | # Or (GCD) (`∨`)
13 | #
14 | # 1∨1 ←→ 1
15 | # 1∨0 ←→ 1
16 | # 0∨1 ←→ 1
17 | # 0∨0 ←→ 0
18 | # 0 0 1 1 ∨ 0 1 0 1 ←→ 0 1 1 1
19 | # 12∨18 ←→ 6 # 12=2×2×3, 18=2×3×3
20 | # 299∨323 ←→ 1 # 299=13×23, 323=17×19
21 | # 12345∨12345 ←→ 12345
22 | # 0∨123 ←→ 123
23 | # 123∨0 ←→ 123
24 | # ∨/⍬ ←→ 0
25 | # ¯12∨18 ←→ 6
26 | # 12∨¯18 ←→ 6
27 | # ¯12∨¯18 ←→ 6
28 | # 1.5∨2.5 !!! DOMAIN ERROR
29 | # 'a'∨1 !!! DOMAIN ERROR
30 | # 1∨'a' !!! DOMAIN ERROR
31 | # 'a'∨'b' !!! DOMAIN ERROR
32 | # 135j¯14∨155j34 ←→ 5j12
33 | # 2 3 4∨0j1 1j2 2j3 ←→ 1 1 1
34 | # 2j2 2j4∨5j5 4j4 ←→ 1j1 2
35 | dyad: (y, x) ->
36 | if (!Complex.isint x) or (!Complex.isint y)
37 | domainError '∨ is implemented only for Gaussian integers' # todo
38 | Complex.gcd x, y
39 |
40 | '∧': withIdentity 1, pervasive
41 |
42 | # And (LCM) (`∧`)
43 | #
44 | # 1∧1 ←→ 1
45 | # 1∧0 ←→ 0
46 | # 0∧1 ←→ 0
47 | # 0∧0 ←→ 0
48 | # 0 0 1 1∧0 1 0 1 ←→ 0 0 0 1
49 | # 0 0 0 1 1∧1 1 1 1 0 ←→ 0 0 0 1 0
50 | # t←3 3⍴1 1 1 0 0 0 1 0 1 ⋄ 1∧t ←→ 3 3 ⍴ 1 1 1 0 0 0 1 0 1
51 | # t←3 3⍴1 1 1 0 0 0 1 0 1 ⋄ ∧/t ←→ 1 0 0
52 | # 12∧18 # 12=2×2×3, 18=2×3×3 ←→ 36
53 | # 299∧323 # 299=13×23, 323=17×19 ←→ 96577
54 | # 12345∧12345 ←→ 12345
55 | # 0∧123 ←→ 0
56 | # 123∧0 ←→ 0
57 | # ∧/⍬ ←→ 1
58 | # ¯12∧18 ←→ ¯36
59 | # 12∧¯18 ←→ ¯36
60 | # ¯12∧¯18 ←→ 36
61 | # 1.5∧2.5 !!! DOMAIN ERROR
62 | # 'a'∧1 !!! DOMAIN ERROR
63 | # 1∧'a' !!! DOMAIN ERROR
64 | # 'a'∧'b' !!! DOMAIN ERROR
65 | # 135j¯14∧155j34 ←→ 805j¯1448
66 | # 2 3 4∧0j1 1j2 2j3 ←→ 0j2 3j6 8j12
67 | # 2j2 2j4∧5j5 4j4 ←→ 10j10 ¯4j12
68 | dyad: (y, x) ->
69 | if (!Complex.isint x) or (!Complex.isint y)
70 | domainError '∧ is implemented only for Gaussian integers' # todo
71 | Complex.lcm x, y
72 |
73 | # Nor (`⍱`)
74 | #
75 | # 0⍱0 ←→ 1
76 | # 0⍱1 ←→ 0
77 | # 1⍱0 ←→ 0
78 | # 1⍱1 ←→ 0
79 | # 0⍱2 !!! DOMAIN ERROR
80 | '⍱': pervasive dyad: real (y, x) -> +!(bool(x) | bool(y))
81 |
82 | # Nand (`⍲`)
83 | #
84 | # 0⍲0 ←→ 1
85 | # 0⍲1 ←→ 1
86 | # 1⍲0 ←→ 1
87 | # 1⍲1 ←→ 0
88 | # 0⍲2 !!! DOMAIN ERROR
89 | '⍲': pervasive dyad: real (y, x) -> +!(bool(x) & bool(y))
90 |
--------------------------------------------------------------------------------
/src/vocabulary/poweroperator.coffee:
--------------------------------------------------------------------------------
1 | addVocabulary
2 |
3 | # Power operator (`⍣`)
4 | #
5 | # ({⍵+1}⍣5) 3 ←→ 8
6 | # ({⍵+1}⍣0) 3 ←→ 3
7 | # (⍴⍣3)2 2⍴⍳4 ←→ ,1
8 | # 'a'(,⍣3)'b' ←→ 'aaab'
9 | # 1(+÷)⍣=1 ←→ 1.618033988749895
10 | # c←0 ⋄ 5⍣{c←c+1}0 ⋄ c ←→ 5
11 | '⍣': conjunction (g, f) ->
12 | if f instanceof APLArray and typeof g is 'function'
13 | h = f; f = g; g = h
14 | else
15 | assert typeof f is 'function'
16 |
17 | if typeof g is 'function'
18 | (⍵, ⍺) -> # "power limit" operator
19 | loop
20 | omega1 = f ⍵, ⍺
21 | if g(⍵, omega1).toBool() then return ⍵
22 | ⍵ = omega1
23 | else
24 | n = g.toInt 0
25 | (⍵, ⍺) ->
26 | for [0...n] then ⍵ = f ⍵, ⍺
27 | ⍵
28 |
--------------------------------------------------------------------------------
/src/vocabulary/quad.coffee:
--------------------------------------------------------------------------------
1 | addVocabulary
2 |
3 | 'get_⎕': ->
4 | if typeof window?.prompt is 'function'
5 | new APLArray(prompt('⎕:') or '')
6 | else
7 | nonceError 'Reading from ⎕ is not implemented.'
8 |
9 | 'set_⎕': (x) ->
10 | s = format(x).join('\n') + '\n'
11 | if typeof window?.alert is 'function'
12 | window.alert s
13 | else
14 | process.stdout.write s
15 | x
16 |
17 | 'get_⍞': ->
18 | if typeof window?.prompt is 'function'
19 | prompt('') or ''
20 | else
21 | nonceError 'Reading from ⍞ is not implemented.'
22 |
23 | 'set_⍞': (x) ->
24 | s = format(x).join '\n'
25 | if typeof window?.alert is 'function'
26 | window.alert s
27 | else
28 | process.stdout.write s
29 | x
30 |
31 | # Index origin (`⎕IO`)
32 | #
33 | # The index origin is fixed at 0. Reading it returns 0. Attempts to set it
34 | # to anything other than that fail.
35 | #
36 | # ⎕IO ←→ 0
37 | # ⎕IO←0 ←→ 0
38 | # ⎕IO←1 !!!
39 | 'get_⎕IO': -> APLArray.zero
40 | 'set_⎕IO': (x) -> if match x, APLArray.zero then x else domainError 'The index origin (⎕IO) is fixed at 0'
41 |
42 | # Delay (`⎕DL`)
43 | '⎕DL': cps (⍵, ⍺, _, callback) ->
44 | t0 = +new Date
45 | setTimeout (-> callback new APLArray [new Date - t0]), ⍵.unwrap()
46 | return
47 |
48 | # Regular expression search
49 | #
50 | # 'b(c+)d' ⎕RE 'abcd' ←→ 1 'bcd' (,'c')
51 | # 'B(c+)d' ⎕RE 'abcd' ←→ ⍬
52 | # 'a(b' ⎕RE 'c' !!! DOMAIN ERROR
53 | '⎕RE': (⍵, ⍺) ->
54 | x = ⍺.toSimpleString()
55 | y = ⍵.toSimpleString()
56 | try re = new RegExp x catch e then domainError e.toString()
57 | if m = re.exec y
58 | r = [m.index]
59 | for u in m then r.push new APLArray(u or '')
60 | new APLArray r
61 | else
62 | APLArray.zilde
63 |
64 | # Unicode convert
65 | # (monadic only; tolerant to mixed-type arrays)
66 | #
67 | # ⎕UCS 'a' ←→ 97
68 | # ⎕UCS 'ab' ←→ 97 98
69 | # ⎕UCS 2 2⍴97+⍳4 ←→ 2 2⍴'abcd'
70 | '⎕UCS': (⍵, ⍺) ->
71 | if ⍺? then nonceError()
72 | ⍵.map (x) ->
73 | if isInt x, 0, 0x10000 then y = String.fromCharCode x
74 | else if typeof x is 'string' then y = x.charCodeAt 0
75 | else domainError()
76 |
--------------------------------------------------------------------------------
/src/vocabulary/question.coffee:
--------------------------------------------------------------------------------
1 | addVocabulary
2 |
3 | '?': (⍵, ⍺) ->
4 | if ⍺ then deal ⍵, ⍺ else roll ⍵
5 |
6 | # Roll (`?`)
7 | #
8 | # n←6 ⋄ r←?n ⋄ (0≤r)∧(r
16 | if !isInt ⍵, 1 then domainError()
17 | Math.floor Math.random() * ⍵
18 |
19 | # Deal (`?`)
20 | #
21 | # n←100 ⋄ (+/n?n)=(+/⍳n) ←→ 1 # a permutation (an "n?n" dealing) contains all 0...n
22 | # n←100 ⋄ A←(n÷2)?n ⋄ ∧/(0≤A),A
30 | ⍺ = ⍺.unwrap()
31 | ⍵ = ⍵.unwrap()
32 | if !(isInt(⍵, 0) and isInt ⍺, 0, ⍵ + 1) then domainError()
33 | r = [0...⍵]
34 | for i in [0...⍺] by 1
35 | j = i + Math.floor Math.random() * (⍵ - i)
36 | h = r[i]; r[i] = r[j]; r[j] = h
37 | new APLArray r[...⍺]
38 |
--------------------------------------------------------------------------------
/src/vocabulary/raise.coffee:
--------------------------------------------------------------------------------
1 | addVocabulary
2 |
3 | # Raise error (`↗`)
4 | #
5 | # ↗'CUSTOM ERROR' !!! CUSTOM ERROR
6 | '↗': (⍵) ->
7 | aplError ⍵.toString()
8 |
--------------------------------------------------------------------------------
/src/vocabulary/rho.coffee:
--------------------------------------------------------------------------------
1 | addVocabulary
2 |
3 | '⍴': (⍵, ⍺) ->
4 | if ⍺
5 | # Reshape (`⍴`)
6 | #
7 | # ⍴1 2 3⍴0 ←→ 1 2 3
8 | # ⍴⍴1 2 3⍴0 ←→ ,3
9 | # 3 3⍴⍳4 ←→ 3 3⍴0 1 2 3 0 1 2 3 0
10 | # ⍴3 3⍴⍳4 ←→ 3 3
11 | # ⍬⍴123 ←→ 123
12 | # ⍬⍴⍬ ←→ 0
13 | # 2 3⍴⍬ ←→ 2 3⍴0
14 | # 2 3⍴⍳7 ←→ 2 3⍴0 1 2 3 4 5
15 | # ⍴1e9⍴0 ←→ ,1e9
16 | if ⍴⍴(⍺) > 1 then rankError()
17 | a = ⍺.toArray()
18 | for x in a when !isInt x, 0 then domainError()
19 | n = prod a
20 | if !n
21 | new APLArray [], a
22 | else if (a.length >= ⍴⍴ ⍵) and arrayEquals ⍴(⍵), a[a.length - (⍴⍴ ⍵)...]
23 | # If ⍺ is only prepending axes to ⍴⍵, we can reuse the .data array
24 | new APLArray ⍵.data, a, repeat([0], a.length - ⍴⍴ ⍵).concat(⍵.stride), ⍵.offset
25 | else
26 | data = []
27 | try
28 | each ⍵, (x) ->
29 | if data.length >= n then throw 'break'
30 | data.push x
31 | catch e
32 | if e isnt 'break' then throw e
33 | if data.length
34 | while 2 * data.length < n then data = data.concat data
35 | if data.length isnt n then data = data.concat data[... n - data.length]
36 | else
37 | data = repeat [⍵.getPrototype()], n
38 | new APLArray data, a
39 | else
40 | # Shape of (`⍴`)
41 | #
42 | # ⍴0 ←→ 0⍴0
43 | # ⍴0 0 ←→ 1⍴2
44 | # ⍴⍴0 ←→ 1⍴0
45 | # ⍴⍴⍴0 ←→ 1⍴1
46 | # ⍴⍴⍴0 0 ←→ 1⍴1
47 | # ⍴'a' ←→ 0⍴0
48 | # ⍴'ab' ←→ 1⍴2
49 | # ⍴2 3 4⍴0 ←→ 2 3 4
50 | new APLArray ⍴ ⍵
51 |
--------------------------------------------------------------------------------
/src/vocabulary/rotate.coffee:
--------------------------------------------------------------------------------
1 | addVocabulary
2 |
3 | '⌽': rotate = (⍵, ⍺, axis) ->
4 | assert typeof axis is 'undefined' or axis instanceof APLArray
5 | if ⍺
6 | # Rotate (`⌽`)
7 | #
8 | # 1⌽1 2 3 4 5 6 ←→ 2 3 4 5 6 1
9 | # 3⌽'ABCDEFGH' ←→ 'DEFGHABC'
10 | # 3⌽2 5⍴1 2 3 4 5 6 7 8 9 0 ←→ 2 5⍴4 5 1 2 3 9 0 6 7 8
11 | # ¯2⌽"ABCDEFGH" ←→ 'GHABCDEF'
12 | # 1⌽3 3⍴⍳9 ←→ 3 3⍴1 2 0 4 5 3 7 8 6
13 | # 0⌽1 2 3 4 ←→ 1 2 3 4
14 | # 0⌽1234 ←→ 1234
15 | # 5⌽⍬ ←→ ⍬
16 | axis = if !axis then ⍴⍴(⍵) - 1 else axis.unwrap()
17 | if !isInt axis then domainError()
18 | if ⍴⍴(⍵) and !(0 <= axis < ⍴⍴ ⍵) then indexError()
19 | step = ⍺.unwrap()
20 | if !isInt step then domainError()
21 | if !step then return ⍵
22 | n = ⍴(⍵)[axis]
23 | step = (n + (step % n)) % n # force % to handle negatives properly
24 | if ⍵.empty() or step is 0 then return ⍵
25 | data = []
26 | {shape, stride} = ⍵
27 | p = ⍵.offset
28 | indices = repeat [0], shape.length
29 | loop
30 | data.push ⍵.data[p + ((indices[axis] + step) % shape[axis] - indices[axis]) * stride[axis]]
31 | a = shape.length - 1
32 | while a >= 0 and indices[a] + 1 is shape[a]
33 | p -= indices[a] * stride[a]
34 | indices[a--] = 0
35 | if a < 0 then break
36 | indices[a]++
37 | p += stride[a]
38 | new APLArray data, shape
39 | else
40 | # Reverse (`⌽`)
41 | #
42 | # ⌽1 2 3 4 5 6 ←→ 6 5 4 3 2 1
43 | # ⌽(1 2)(3 4)(5 6) ←→ (5 6)(3 4)(1 2)
44 | # ⌽"BOB WON POTS" ←→ 'STOP NOW BOB'
45 | # ⌽ 2 5⍴1 2 3 4 5 6 7 8 9 0 ←→ 2 5⍴5 4 3 2 1 0 9 8 7 6
46 | # ⌽[0] 2 5⍴1 2 3 4 5 6 7 8 9 0 ←→ 2 5⍴6 7 8 9 0 1 2 3 4 5
47 | if axis
48 | if !axis.isSingleton() then lengthError()
49 | axis = axis.unwrap()
50 | if !isInt axis then domainError()
51 | if !(0 <= axis < ⍴⍴ ⍵) then indexError()
52 | else
53 | axis = [⍴⍴(⍵) - 1]
54 | if ⍴⍴(⍵) is 0 then return ⍵
55 | stride = ⍵.stride[..]
56 | stride[axis] = -stride[axis]
57 | offset = ⍵.offset + (⍴(⍵)[axis] - 1) * ⍵.stride[axis]
58 | new APLArray ⍵.data, ⍴(⍵), stride, offset
59 |
60 | # 1st axis reverse (`⊖`)
61 | #
62 | # ⊖1 2 3 4 5 6 ←→ 6 5 4 3 2 1
63 | # ⊖(1 2) (3 4) (5 6) ←→ (5 6)(3 4)(1 2)
64 | # ⊖'BOB WON POTS' ←→ 'STOP NOW BOB'
65 | # ⊖ 2 5⍴1 2 3 4 5 6 7 8 9 0 ←→ 2 5⍴6 7 8 9 0 1 2 3 4 5
66 | # ⊖[1] 2 5⍴1 2 3 4 5 6 7 8 9 0 ←→ 2 5⍴5 4 3 2 1 0 9 8 7 6
67 | #
68 | # 1st axis rotate (`⊖`)
69 | #
70 | # 1⊖3 3⍴⍳9 ←→ 3 3⍴3 4 5 6 7 8 0 1 2
71 | '⊖': (⍵, ⍺, axis = APLArray.zero) ->
72 | rotate ⍵, ⍺, axis
73 |
--------------------------------------------------------------------------------
/src/vocabulary/slash.coffee:
--------------------------------------------------------------------------------
1 | addVocabulary
2 |
3 | '/': adverb (⍵, ⍺, axis) ->
4 | if typeof ⍵ is 'function'
5 | reduce ⍵, ⍺, axis
6 | else
7 | compressOrReplicate ⍵, ⍺, axis
8 |
9 | '⌿': adverb (⍵, ⍺, axis = APLArray.zero) ->
10 | if typeof ⍵ is 'function'
11 | reduce ⍵, ⍺, axis
12 | else
13 | compressOrReplicate ⍵, ⍺, axis
14 |
15 | # Reduce (`/`)
16 | #
17 | # +/3 ←→ 3
18 | # +/3 5 8 ←→ 16
19 | # +/2 4 6 ←→ 12
20 | # ⌈/82 66 93 13 ←→ 93
21 | # ×/2 3⍴1 2 3 4 5 6 ←→ 6 120
22 | # 2,/'AB' 'CD' 'EF' 'HI' ←→ 'ABCD' 'CDEF' 'EFHI'
23 | # 3,/'AB' 'CD' 'EF' 'HI' ←→ 'ABCDEF' 'CDEFHI'
24 | # -/3 0⍴42 ←→ 3⍴0
25 | #
26 | # N-Wise reduce
27 | #
28 | # 2+/1+⍳10 ←→ 3 5 7 9 11 13 15 17 19
29 | # 5+/1+⍳10 ←→ 15 20 25 30 35 40
30 | # 10+/1+⍳10 ←→ ,55
31 | # 11+/1+⍳10 ←→ ⍬
32 | # 12+/1+⍳10 !!! LENGTH ERROR
33 | # 2-/3 4 9 7 ←→ ¯1 ¯5 2
34 | # ¯2-/3 4 9 7 ←→ 1 5 ¯2
35 | reduce = @reduce = (f, g, axis0) ->
36 | assert typeof f is 'function'
37 | assert typeof g is 'undefined'
38 | assert((typeof axis0 is 'undefined') or (axis0 instanceof APLArray))
39 | (⍵, ⍺) ->
40 | if !⍴⍴ ⍵ then ⍵ = new APLArray [⍵.unwrap()]
41 | axis = if axis0? then axis0.toInt() else ⍴⍴(⍵) - 1
42 | if !(0 <= axis < ⍴⍴ ⍵) then rankError()
43 |
44 | if ⍺
45 | isNWise = true
46 | n = ⍺.toInt()
47 | if n < 0
48 | isBackwards = true
49 | n = -n
50 | else
51 | n = ⍴(⍵)[axis]
52 | isMonadic = true
53 |
54 | shape = ⍴(⍵)[..]
55 | shape[axis] = ⍴(⍵)[axis] - n + 1
56 | rShape = shape
57 | if isNWise
58 | if shape[axis] is 0 then return new APLArray [], rShape
59 | if shape[axis] < 0 then lengthError()
60 | else
61 | rShape = rShape[..]
62 | rShape.splice axis, 1
63 |
64 | if ⍵.empty()
65 | if (z = f.identity)?
66 | assert !⍴⍴ z
67 | return new APLArray z.data, rShape, repeat([0], rShape.length), z.offset
68 | else
69 | domainError()
70 |
71 | data = []
72 | indices = repeat [0], shape.length
73 | p = ⍵.offset
74 | loop
75 | if isBackwards
76 | x = ⍵.data[p]
77 | x = if x instanceof APLArray then x else APLArray.scalar x
78 | for i in [1...n] by 1
79 | y = ⍵.data[p + i * ⍵.stride[axis]]
80 | y = if y instanceof APLArray then y else APLArray.scalar y
81 | x = f x, y
82 | else
83 | x = ⍵.data[p + (n - 1) * ⍵.stride[axis]]
84 | x = if x instanceof APLArray then x else APLArray.scalar x
85 | for i in [n - 2 .. 0] by -1
86 | y = ⍵.data[p + i * ⍵.stride[axis]]
87 | y = if y instanceof APLArray then y else APLArray.scalar y
88 | x = f x, y
89 | if !⍴⍴ x then x = x.unwrap()
90 | data.push x
91 | a = indices.length - 1
92 | while a >= 0 and indices[a] + 1 is shape[a]
93 | p -= indices[a] * ⍵.stride[a]
94 | indices[a--] = 0
95 | if a < 0 then break
96 | p += ⍵.stride[a]
97 | indices[a]++
98 |
99 | new APLArray data, rShape
100 |
101 | # Replicate (`/`)
102 | #
103 | # 0 1 0 1/'ABCD' ←→ 'BD'
104 | # 1 1 1 1 0/12 14 16 18 20 ←→ 12 14 16 18
105 | # MARKS←45 60 33 50 66 19 ⋄ (MARKS≥50)/MARKS ←→ 60 50 66
106 | # MARKS←45 60 33 50 66 19 ⋄ (MARKS=50)/⍳≢MARKS ←→ ,3
107 | # 1/"FREDERIC" ←→ 'FREDERIC'
108 | # 0/"FREDERIC" ←→ ⍬
109 | # 0 1 0 / 1+2 3⍴⍳6 ←→ 2 1⍴2 5
110 | # 1 0 /[0] 1+2 3⍴⍳6 ←→ 1 3⍴1 2 3
111 | # 1 0 ⌿ 1+2 3⍴⍳6 ←→ 1 3⍴1 2 3
112 | # 3 / 5 ←→ 5 5 5
113 | # 2 ¯2 2 / 1+2 3⍴⍳6
114 | # ... ←→ 2 6 ⍴ 1 1 0 0 3 3 4 4 0 0 6 6
115 | # 1 1 ¯2 1 1 / 1 2 (2 2⍴⍳4) 3 4 ←→ 1 2 0 0 3 4
116 | # 2 3 2 / 'ABC' ←→ 'AABBBCC'
117 | # 2 / 'DEF' ←→ 'DDEEFF'
118 | # 5 0 5 / 1 2 3 ←→ 1 1 1 1 1 3 3 3 3 3
119 | # 2 / 1+2 3⍴⍳6 ←→ 2 6⍴ 1 1 2 2 3 3 4 4 5 5 6 6
120 | # 2 ⌿ 1+2 3⍴⍳6 ←→ 4 3⍴ 1 2 3 1 2 3 4 5 6 4 5 6
121 | # 2 3 / 3 1⍴"ABC" ←→ 3 5⍴'AAAAABBBBBCCCCC'
122 | # 2 ¯1 2 /[1] 3 1⍴(7 8 9) ←→ 3 5⍴7 7 0 7 7 8 8 0 8 8 9 9 0 9 9
123 | # 2 ¯1 2 /[1] 3 1⍴"ABC" ←→ 3 5⍴'AA AABB BBCC CC'
124 | # 2 ¯2 2 / 7 ←→ 7 7 0 0 7 7
125 | compressOrReplicate = (⍵, ⍺, axis) ->
126 | if !⍴⍴ ⍵ then ⍵ = new APLArray [⍵.unwrap()]
127 | axis = if axis then axis.toInt 0, ⍴⍴ ⍵ else ⍴⍴(⍵) - 1
128 | if ⍴⍴(⍺) > 1 then rankError()
129 | a = ⍺.toArray()
130 | n = ⍴(⍵)[axis]
131 | if a.length is 1 then a = repeat a, n
132 | if n !in [1, a.length] then lengthError()
133 |
134 | shape = ⍴(⍵)[..]
135 | shape[axis] = 0
136 | b = []
137 | for x, i in a
138 | if !isInt x then domainError()
139 | shape[axis] += Math.abs x
140 | for [0...Math.abs(x)] then b.push(if x > 0 then i else null)
141 | if n is 1
142 | b = (for x in b then (if x? then 0 else x))
143 |
144 | data = []
145 | if shape[axis] isnt 0 and !⍵.empty()
146 | filler = ⍵.getPrototype()
147 | p = ⍵.offset
148 | indices = repeat [0], shape.length
149 | loop
150 | x =
151 | if b[indices[axis]]?
152 | ⍵.data[p + b[indices[axis]] * ⍵.stride[axis]]
153 | else
154 | filler
155 | data.push x
156 |
157 | i = shape.length - 1
158 | while i >= 0 and indices[i] + 1 is shape[i]
159 | if i isnt axis then p -= ⍵.stride[i] * indices[i]
160 | indices[i--] = 0
161 | if i < 0 then break
162 | if i isnt axis then p += ⍵.stride[i]
163 | indices[i]++
164 |
165 | new APLArray data, shape
166 |
--------------------------------------------------------------------------------
/src/vocabulary/squish.coffee:
--------------------------------------------------------------------------------
1 | addVocabulary
2 |
3 | # Index (`⌷`)
4 | #
5 | # `a0 a1...⌷b` is equivalent to `b[a0;a1;...]`
6 | #
7 | # 1⌷3 5 8 ←→ 5
8 | # (3 5 8)[1] ←→ 5
9 | # (3 5 8)[⍬] ←→ ⍬
10 | # (2 2 0)(1 2)⌷3 3⍴⍳9 ←→ 3 2⍴7 8 7 8 1 2
11 | # ¯1⌷3 5 8 !!! INDEX ERROR
12 | # 2⌷111 222 333 444 ←→ 333
13 | # (⊂3 2)⌷111 222 333 444 ←→ 444 333
14 | # (⊂2 3⍴2 0 3 0 1 2)⌷111 222 333 444
15 | # ... ←→ 2 3⍴333 111 444 111 222 333
16 | # 1 0 ⌷3 4⍴11 12 13 14 21 22 23 24 31 32 33 34 ←→ 21
17 | # 1 ⌷3 4⍴11 12 13 14 21 22 23 24 31 32 33 34 ←→ 21 22 23 24
18 | # 2(1 0)⌷3 4⍴11 12 13 14 21 22 23 24 31 32 33 34 ←→ 32 31
19 | # (1 2)0⌷3 4⍴11 12 13 14 21 22 23 24 31 32 33 34 ←→ 21 31
20 | # a←2 2⍴0 ⋄ a[;0]←1 ⋄ a ←→ 2 2⍴1 0 1 0
21 | # a←2 3⍴0 ⋄ a[1;0 2]←1 ⋄ a ←→ 2 3⍴0 0 0 1 0 1
22 | '⌷': squish = (⍵, ⍺, axes) ->
23 | if typeof ⍵ is 'function' then return (x, y) -> ⍵ x, y, ⍺
24 | if !⍺ then nonceError()
25 | if 1 < ⍴⍴ ⍺ then rankError()
26 | a = ⍺.toArray()
27 | if a.length > ⍴⍴ ⍵ then lengthError()
28 |
29 | if axes
30 | axes = axes.toArray()
31 | if a.length isnt axes.length then lengthError()
32 | h = Array ⍴⍴ ⍵
33 | for axis in axes
34 | if !isInt axis then domainError()
35 | if !(0 <= axis < ⍴⍴ ⍵) then rankError()
36 | if h[axis] then rankError 'Duplicate axis'
37 | h[axis] = 1
38 | else
39 | axes = [0...a.length]
40 |
41 | r = ⍵
42 | for i in [a.length - 1..0] by -1
43 | u = if a[i] instanceof APLArray then a[i] else new APLArray [a[i]], []
44 | r = indexAtSingleAxis r, u, axes[i]
45 | r
46 |
47 | # (23 54 38)[0] ←→ 23
48 | # (23 54 38)[1] ←→ 54
49 | # (23 54 38)[2] ←→ 38
50 | # (23 54 38)[3] !!! INDEX ERROR
51 | # (23 54 38)[¯1] !!! INDEX ERROR
52 | # (23 54 38)[0 2] ←→ 23 38
53 | # (2 3⍴100 101 102 110 111 112)[1;2] ←→ 112
54 | # (2 3⍴100 101 102 110 111 112)[1;¯1] !!! INDEX ERROR
55 | # (2 3⍴100 101 102 110 111 112)[10;1] !!! INDEX ERROR
56 | # (2 3⍴100 101 102 110 111 112)[1;] ←→ 110 111 112
57 | # (2 3⍴100 101 102 110 111 112)[;1] ←→ 101 111
58 | # 'hello'[1] ←→ 'e'
59 | # 'ipodlover'[1 2 5 8 3 7 6 0 4] ←→ 'poordevil'
60 | # ('axlrose'[4 3 0 2 5 6 1])[0 1 2 3] ←→ 'oral'
61 | # (1 2 3)[⍬] ←→ ⍬
62 | # ⍴(1 2 3)[1 2 3 0 5⍴0] ←→ 1 2 3 0 5
63 | # (⍳3)[] ←→ ⍳3
64 | # ⍴(3 3⍴⍳9)[⍬;⍬] ←→ 0 0
65 | #
66 | # " X"[(3 3⍴⍳9)∊1 3 6 7 8] ←→ 3 3⍴(' X ',
67 | # ... 'X ',
68 | # ... 'XXX')
69 | _index: (alphaAndAxes, ⍵) ->
70 | [⍺, axes] = alphaAndAxes.toArray()
71 | squish ⍵, ⍺, axes
72 |
73 | # a←⍳5 ⋄ a[1 3]←7 8 ⋄ a ←→ 0 7 2 8 4
74 | # a←⍳5 ⋄ a[1 3]←7 ⋄ a ←→ 0 7 2 7 4
75 | # a←⍳5 ⋄ a[1] ←7 8 ⋄ a !!! RANK ERROR
76 | # a←1 2 3 ⋄ a[1]←4 ⋄ a ←→ 1 4 3
77 | # a←2 2⍴⍳4 ⋄ a[0;0]←4 ⋄ a ←→ 2 2⍴4 1 2 3
78 | # a←5 5⍴0 ⋄ a[1 3;2 4]←2 2⍴1+⍳4 ⋄ a ←→ 5 5⍴(0 0 0 0 0
79 | # ... 0 0 1 0 2
80 | # ... 0 0 0 0 0
81 | # ... 0 0 3 0 4
82 | # ... 0 0 0 0 0)
83 | # a←'this is a test' ⋄ a[0 5]←'TI' ←→ 'This Is a test'
84 | # Data←0 4 8 ⋄ 10+ (Data[0 2]← 7 9) ←→ 17 14 19
85 | # a←3 4⍴⍳12 ⋄ a[;1 2]←99 ←→ 3 4⍴0 99 99 3 4 99 99 7 8 99 99 11
86 | # a←1 2 3 ⋄ a[⍬]←4 ⋄ a ←→ 1 2 3
87 | # a←3 3⍴⍳9 ⋄ a[⍬;1 2]←789 ⋄ a ←→ 3 3⍴⍳9
88 | # a←1 2 3 ⋄ a[]←4 5 6 ⋄ a ←→ 4 5 6
89 | _substitute: (args) ->
90 | [value, ⍺, ⍵, axes] =
91 | for x in args.toArray()
92 | if x instanceof APLArray then x else new APLArray [x], []
93 |
94 | if 1 < ⍴⍴ ⍺ then rankError()
95 | a = ⍺.toArray()
96 | if a.length > ⍴⍴ ⍵ then lengthError()
97 |
98 | if axes
99 | if 1 < ⍴⍴ axes then rankError()
100 | axes = axes.toArray()
101 | if a.length isnt axes.length then lengthError()
102 | else
103 | axes = [0...a.length]
104 |
105 | subs = squish (vocabulary['⍳'] new APLArray ⍴ ⍵), ⍺, new APLArray axes
106 | if value.isSingleton()
107 | value = new APLArray [value], ⍴(subs), repeat [0], ⍴⍴(subs)
108 | data = ⍵.toArray()
109 | stride = strideForShape ⍴ ⍵
110 | each2 subs, value, (u, v) ->
111 | if v instanceof APLArray and !⍴⍴ v then v = v.unwrap()
112 | if u instanceof APLArray
113 | p = 0 # pointer in data
114 | for x, i in u.toArray() then p += x * stride[i]
115 | data[p] = v
116 | else
117 | data[u] = v
118 |
119 | new APLArray data, ⍴ ⍵
120 |
121 | indexAtSingleAxis = (⍵, sub, ax) ->
122 | assert ⍵ instanceof APLArray
123 | assert sub instanceof APLArray
124 | assert isInt ax
125 | assert 0 <= ax < ⍴⍴ ⍵
126 | u = sub.toArray()
127 | n = ⍴(⍵)[ax]
128 | for x in u
129 | if !isInt x then domainError()
130 | if !(0 <= x < n) then indexError()
131 | isUniform = false
132 | if u.length >= 2
133 | isUniform = true
134 | d = u[1] - u[0]
135 | for i in [2...u.length] by 1 when u[i] - u[i - 1] isnt d then (isUniform = false; break)
136 | if isUniform
137 | shape = ⍴(⍵)[..]
138 | shape.splice ax, 1, ⍴(sub)...
139 | stride = ⍵.stride[..]
140 | subStride = strideForShape ⍴ sub
141 | for x, i in subStride then subStride[i] = x * d * ⍵.stride[ax]
142 | stride.splice ax, 1, subStride...
143 | offset = ⍵.offset + u[0] * ⍵.stride[ax]
144 | new APLArray ⍵.data, shape, stride, offset
145 | else
146 | shape1 = ⍴(⍵)[..]; shape1.splice ax, 1
147 | stride1 = ⍵.stride[..]; stride1.splice ax, 1
148 | data = []
149 | each sub, (x) ->
150 | chunk = new APLArray ⍵.data, shape1, stride1, ⍵.offset + x * ⍵.stride[ax]
151 | data.push chunk.toArray()...
152 | shape = shape1[..]
153 | stride = strideForShape shape
154 | shape.splice ax, 0, ⍴(sub)...
155 | subStride = strideForShape ⍴ sub
156 | k = prod shape1
157 | for i in [0...subStride.length] by 1 then subStride[i] *= k
158 | stride.splice ax, 0, subStride...
159 | new APLArray data, shape, stride
160 |
--------------------------------------------------------------------------------
/src/vocabulary/take.coffee:
--------------------------------------------------------------------------------
1 | addVocabulary
2 |
3 | '↑': (⍵, ⍺) ->
4 | if ⍺
5 | take ⍵, ⍺
6 | else
7 | first ⍵
8 |
9 | # Take (`↑`)
10 | #
11 | # 5↑'ABCDEFGH' ←→ 'ABCDE'
12 | # ¯3↑'ABCDEFGH' ←→ 'FGH'
13 | # 3↑22 2 19 12 ←→ 22 2 19
14 | # ¯1↑22 2 19 12 ←→ ,12
15 | # ⍴1↑(2 2⍴⍳4)(⍳10) ←→ ,1
16 | # 2↑1 ←→ 1 0
17 | # 5↑40 92 11 ←→ 40 92 11 0 0
18 | # ¯5↑40 92 11 ←→ 0 0 40 92 11
19 | # 3 3↑1 1⍴0 ←→ 3 3⍴0 0 0 0 0 0 0 0 0
20 | # 5↑"abc" ←→ 'abc '
21 | # ¯5↑"abc" ←→ ' abc'
22 | # 3 3↑1 1⍴"a" ←→ 3 3⍴'a '
23 | # 2 3↑1+4 3⍴⍳12 ←→ 2 3⍴1 2 3 4 5 6
24 | # ¯1 3↑1+4 3⍴⍳12 ←→ 1 3⍴10 11 12
25 | # 1 2↑1+4 3⍴⍳12 ←→ 1 2⍴1 2
26 | # 3↑⍬ ←→ 0 0 0
27 | # ¯2↑⍬ ←→ 0 0
28 | # 0↑⍬ ←→ ⍬
29 | # 3 3↑1 ←→ 3 3⍴1 0 0 0 0 0 0 0 0
30 | # 2↑3 3⍴⍳9 ←→ 2 3⍴⍳6
31 | # ¯2↑3 3⍴⍳9 ←→ 2 3⍴3+⍳6
32 | # 4↑3 3⍴⍳9 ←→ 4 3⍴(⍳9),0 0 0
33 | # ⍬↑3 3⍴⍳9 ←→ 3 3⍴⍳9
34 | take = (⍵, ⍺) ->
35 | if ⍴⍴(⍺) > 1 then rankError()
36 | if !⍴⍴ ⍵ then ⍵ = new APLArray [⍵.unwrap()], (if !⍴⍴ ⍺ then [1] else repeat [1], ⍴(⍺)[0])
37 | a = ⍺.toArray()
38 | if a.length > ⍴⍴ ⍵ then rankError()
39 | for x in a when typeof x isnt 'number' or x isnt Math.floor x then domainError()
40 |
41 | mustCopy = false
42 | shape = ⍴(⍵)[..]
43 | for x, i in a
44 | shape[i] = Math.abs x
45 | if shape[i] > ⍴(⍵)[i]
46 | mustCopy = true
47 |
48 | if mustCopy
49 | stride = Array shape.length
50 | stride[stride.length - 1] = 1
51 | for i in [stride.length - 2 .. 0] by -1
52 | stride[i] = stride[i + 1] * shape[i + 1]
53 | data = repeat [⍵.getPrototype()], prod shape
54 | copyShape = shape[..]
55 | p = ⍵.offset
56 | q = 0
57 | for x, i in a
58 | copyShape[i] = Math.min ⍴(⍵)[i], Math.abs x
59 | if x < 0
60 | if x < -⍴(⍵)[i]
61 | q -= (x + ⍴(⍵)[i]) * stride[i]
62 | else
63 | p += (x + ⍴(⍵)[i]) * ⍵.stride[i]
64 | if prod copyShape
65 | copyIndices = repeat [0], copyShape.length
66 | loop
67 | data[q] = ⍵.data[p]
68 | axis = copyShape.length - 1
69 | while axis >= 0 and copyIndices[axis] + 1 is copyShape[axis]
70 | p -= copyIndices[axis] * ⍵.stride[axis]
71 | q -= copyIndices[axis] * stride[axis]
72 | copyIndices[axis--] = 0
73 | if axis < 0 then break
74 | p += ⍵.stride[axis]
75 | q += stride[axis]
76 | copyIndices[axis]++
77 | new APLArray data, shape, stride
78 | else
79 | offset = ⍵.offset
80 | for x, i in a
81 | if x < 0
82 | offset += (⍴(⍵)[i] + x) * ⍵.stride[i]
83 | new APLArray ⍵.data, shape, ⍵.stride, offset
84 |
85 | # First (`↑`)
86 | #
87 | # ↑(1 2 3)(4 5 6) ←→ 1 2 3
88 | # ↑(1 2)(3 4 5) ←→ 1 2
89 | # ↑'AB' ←→ 'A'
90 | # ↑123 ←→ 123
91 | # ↑⍬ ←→ 0
92 | #! ↑'' ←→ ' '
93 | first = (⍵) ->
94 | x = if ⍵.empty() then ⍵.getPrototype() else ⍵.data[⍵.offset]
95 | if x instanceof APLArray then x else new APLArray [x], []
96 |
--------------------------------------------------------------------------------
/src/vocabulary/transpose.coffee:
--------------------------------------------------------------------------------
1 | addVocabulary
2 |
3 | '⍉': (⍵, ⍺) ->
4 | if ⍺
5 | # Transpose (`⍉`)
6 | #
7 | # (2 2⍴⍳4)⍉2 2 2 2⍴⍳16 !!! RANK ERROR
8 | # 0⍉3 5 8 ←→ 3 5 8
9 | # 1 0⍉2 2 2⍴⍳8 !!! LENGTH ERROR
10 | # ¯1⍉1 2 !!! DOMAIN ERROR
11 | # 'a'⍉1 2 !!! DOMAIN ERROR
12 | # 2⍉1 2 !!! RANK ERROR
13 | # 2 0 1⍉2 3 4⍴⍳24 ←→ 3 4 2⍴0 12 1 13 2 14 3 15 4 16 5 17 6 18 7 19 8 20 9 21 10 22 11 23
14 | # 2 0 0⍉2 3 4⍴⍳24 !!! RANK ERROR
15 | # 0 0⍉3 3⍴⍳9 ←→ 0 4 8
16 | # 0 0⍉2 3⍴⍳9 ←→ 0 4
17 | # 0 0 0⍉3 3 3⍴⍳27 ←→ 0 13 26
18 | # 0 1 0⍉3 3 3⍴⍳27 ←→ 3 3⍴0 3 6 10 13 16 20 23 26
19 | if ⍴⍴(⍺) > 1 then rankError()
20 | if !⍴⍴ ⍺ then ⍺ = new APLArray [⍺.unwrap()]
21 | n = ⍴⍴ ⍵
22 | if ⍴(⍺)[0] isnt n then lengthError()
23 | shape = []
24 | stride = []
25 | for x, i in ⍺.toArray()
26 | if !isInt x, 0 then domainError()
27 | if x >= n then rankError()
28 | if shape[x]?
29 | shape[x] = Math.min shape[x], ⍴(⍵)[i]
30 | stride[x] += ⍵.stride[i]
31 | else
32 | shape[x] = ⍴(⍵)[i]
33 | stride[x] = ⍵.stride[i]
34 | for u in shape when !u? then rankError()
35 | new APLArray ⍵.data, shape, stride, ⍵.offset
36 | else
37 | # Transpose (`⍉`)
38 | #
39 | # ⍉2 3⍴1 2 3 6 7 8 ←→ 3 2⍴1 6 2 7 3 8
40 | # ⍴⍉2 3⍴1 2 3 6 7 8 ←→ 3 2
41 | # ⍉1 2 3 ←→ 1 2 3
42 | # ⍉2 3 4⍴⍳24 ←→ (4 3 2⍴
43 | # ... 0 12 4 16 8 20
44 | # ... 1 13 5 17 9 21
45 | # ... 2 14 6 18 10 22
46 | # ... 3 15 7 19 11 23)
47 | # ⍉⍬ ←→ ⍬
48 | # ⍉'' ←→ ''
49 | new APLArray ⍵.data, reversed(⍴ ⍵), reversed(⍵.stride), ⍵.offset
50 |
--------------------------------------------------------------------------------
/src/vocabulary/variant.coffee:
--------------------------------------------------------------------------------
1 | addVocabulary
2 |
3 | # Variant operator (`⍠`)
4 | #
5 | # ({'monadic'}⍠{'dyadic'}) 0 ←→ 'monadic'
6 | # 0 ({'monadic'}⍠{'dyadic'}) 0 ←→ 'dyadic'
7 | '⍠': conjunction (f, g) ->
8 | assert typeof f is 'function'
9 | assert typeof g is 'function'
10 | (⍵, ⍺, axis) ->
11 | (if ⍺? then f else g) ⍵, ⍺, axis
12 |
--------------------------------------------------------------------------------
/src/vocabulary/vhelpers.coffee:
--------------------------------------------------------------------------------
1 | # pervasive() is a higher-order function
2 | #
3 | # Consider a function that accepts and returns only scalars. To make it
4 | # pervasive means to make it work with any-dimensional arrays, too.
5 | #
6 | # What pervasive() actually does is to take two versions of a scalar function
7 | # (a monadic and a dyadic one), make them pervasive, and combine them into a
8 | # single function that dispatches based on the number of arguments.
9 | pervasive = ({monad, dyad}) ->
10 | pervadeMonadic =
11 | if monad
12 | (x) ->
13 | if x instanceof APLArray
14 | x.map pervadeMonadic
15 | else
16 | r = monad x
17 | if typeof r is 'number' and isNaN r then domainError 'NaN'
18 | r
19 | else
20 | nonceError
21 | pervadeDyadic =
22 | if dyad
23 | (x, y) ->
24 | # tx, ty: 0=unwrapped scalar; 1=singleton array; 2=non-singleton array
25 | tx = if x instanceof APLArray then (if x.isSingleton() then 1 else 2) else 0
26 | ty = if y instanceof APLArray then (if y.isSingleton() then 1 else 2) else 0
27 | switch 16 * tx + ty
28 | when 0x00
29 | r = dyad x, y
30 | if typeof r is 'number' and isNaN r then domainError 'NaN'
31 | r
32 | when 0x01, 0x02 then y.map (yi) -> pervadeDyadic x, yi
33 | when 0x10, 0x20 then x.map (xi) -> pervadeDyadic xi, y
34 | when 0x12 then xi = x.data[x.offset]; y.map (yi) -> pervadeDyadic xi, yi
35 | when 0x21, 0x11 then yi = y.data[y.offset]; x.map (xi) -> pervadeDyadic xi, yi # todo: use the larger shape for '11'
36 | when 0x22
37 | if ⍴⍴(x) isnt ⍴⍴(y) then rankError()
38 | for axis in [0...⍴⍴ x] by 1 when ⍴(x)[axis] isnt ⍴(y)[axis] then lengthError()
39 | x.map2 y, pervadeDyadic
40 | else assert 0
41 | else
42 | nonceError
43 | (⍵, ⍺) ->
44 | assert ⍵ instanceof APLArray
45 | assert ⍺ instanceof APLArray or !⍺?
46 | (if ⍺? then pervadeDyadic else pervadeMonadic) ⍵, ⍺
47 |
48 | macro real (f) ->
49 | (macro.codeToNode ->
50 | (x, y, axis) ->
51 | if typeof x is 'number' and (!y? or typeof y is 'number')
52 | fBody
53 | else
54 | domainError()
55 | ).subst
56 | x: macro.csToNode(f.params[0]?.name?.value ? @tmp()).expressions[0]
57 | y: macro.csToNode(f.params[1]?.name?.value ? @tmp()).expressions[0]
58 | axis: macro.csToNode(f.params[2]?.name?.value ? @tmp()).expressions[0]
59 | fBody: f.body
60 |
61 | numeric = (f, g) -> (x, y, axis) ->
62 | if typeof x is 'number' and (!y? or typeof y is 'number')
63 | f x, y, axis
64 | else
65 | x = complexify x
66 | if y?
67 | y = complexify y
68 | g x, y, axis
69 |
70 | match = (x, y) ->
71 | if x instanceof APLArray
72 | if y !instanceof APLArray then false
73 | else
74 | if ⍴⍴(x) isnt ⍴⍴(y) then return false
75 | for axis in [0...⍴⍴ x] by 1
76 | if ⍴(x)[axis] isnt ⍴(y)[axis] then return false
77 | r = true
78 | each2 x, y, (xi, yi) -> if !match xi, yi then r = false
79 | r
80 | else
81 | if y instanceof APLArray then false
82 | else
83 | if x instanceof Complex and y instanceof Complex
84 | x.re is y.re and x.im is y.im
85 | else
86 | x is y
87 |
88 | numApprox = (x, y) ->
89 | x is y or Math.abs(x - y) < 1e-11
90 |
91 | # approx() is like match(), but it is tolerant to precision errors;
92 | # used for comparing expected and actual results in doctests
93 | approx = (x, y) ->
94 | if x instanceof APLArray
95 | if y !instanceof APLArray then false
96 | else
97 | if ⍴⍴(x) isnt ⍴⍴(y) then return false
98 | for axis in [0...⍴⍴ x] by 1
99 | if ⍴(x)[axis] isnt ⍴(y)[axis] then return false
100 | r = true
101 | each2 x, y, (xi, yi) -> if !approx xi, yi then r = false
102 | r
103 | else
104 | if y instanceof APLArray then false
105 | else if !(x? and y?) then false
106 | else
107 | if typeof x is 'number' then x = new Complex x
108 | if typeof y is 'number' then y = new Complex y
109 | if x instanceof Complex
110 | y instanceof Complex and numApprox(x.re, y.re) and numApprox(x.im, y.im)
111 | else
112 | x is y
113 |
114 | bool = (x) ->
115 | if x !in [0, 1] then domainError() else x
116 |
117 | getAxisList = (axes, rank) ->
118 | assert isInt rank, 0
119 | if !axes? then return []
120 | assert axes instanceof APLArray
121 | if ⍴⍴(axes) isnt 1 or ⍴(axes)[0] isnt 1 then syntaxError() # [sic]
122 | a = axes.unwrap()
123 | if a instanceof APLArray
124 | a = a.toArray()
125 | for x, i in a
126 | if !isInt x, 0, rank then domainError()
127 | if x in a[...i] then domainError 'Non-unique axes'
128 | a
129 | else if isInt a, 0, rank
130 | [a]
131 | else
132 | domainError()
133 |
134 | withIdentity = (x, f) ->
135 | f.identity = if x instanceof APLArray then x else APLArray.scalar x
136 | f
137 |
138 | adverb = (f) -> f.isAdverb = true; f
139 | conjunction = (f) -> f.isConjunction = true; f
140 | cps = (f) -> f.cps = true; f
141 |
--------------------------------------------------------------------------------
/test/collectdoctests.coffee:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env coffee
2 |
3 | fs = require 'fs'
4 | glob = require 'glob'
5 |
6 | trim = (s) -> s.replace /(^ +| +$)/g, ''
7 |
8 | tests = []
9 | for f in glob.sync __dirname + '/../src/**/*.*'
10 | lines = fs.readFileSync(f, 'utf8').split '\n'
11 | i = 0
12 | while i < lines.length
13 | line = lines[i++]
14 | while i < lines.length and (m = lines[i].match(/^ *[#⍝] *\.\.\.(.*)$/))
15 | line += '\n' + m[1]
16 | i++
17 | if m = line.match /^ *[#⍝] ([^]*)(←→|!!!)([^]+)$/
18 | tests.push [trim(m[1]), trim(m[2]), trim(m[3])]
19 |
20 | process.stdout.write "[\n#{tests.map(JSON.stringify).join ',\n'}\n]\n"
21 |
--------------------------------------------------------------------------------
/test/rundoctest.coffee:
--------------------------------------------------------------------------------
1 | @runDocTest = ([code, mode, expectation], exec, approx) ->
2 | if mode is '←→'
3 | try
4 | y = exec expectation
5 | catch e
6 | return {
7 | success: false
8 | error: e
9 | reason: "Cannot compute expected value #{JSON.stringify expectation}"
10 | }
11 | try
12 | x = exec code
13 | if not approx x, y
14 | return {
15 | success: false
16 | reason: "Expected #{JSON.stringify y} but got #{JSON.stringify x}"
17 | }
18 | catch e
19 | return {
20 | success: false
21 | error: e
22 | }
23 | else if mode is '!!!'
24 | try
25 | exec code
26 | return {
27 | success: false
28 | reason: "It should have thrown an error, but it didn't."
29 | }
30 | catch e
31 | if expectation and e.name[...expectation.length] isnt expectation
32 | return {
33 | success: false
34 | error: e
35 | reason: "It should have failed with #{
36 | JSON.stringify expectation}, but it failed with #{
37 | JSON.stringify e.message}"
38 | }
39 | else
40 | return {
41 | success: false
42 | reason: "Unrecognised expectation: #{JSON.stringify expectation}"
43 | }
44 | {success: true}
45 |
--------------------------------------------------------------------------------
/test/rundoctests.coffee:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env coffee
2 |
3 | # Reads testcases in JSON format from stdin and runs them.
4 |
5 | {approx} = exec = require '../lib/apl'
6 | {runDocTest} = require './rundoctest'
7 |
8 | s = ''
9 | process.stdin.resume()
10 | process.stdin.setEncoding 'utf8'
11 | process.stdin.on 'data', (chunk) -> s += chunk
12 | process.stdin.on 'end', ->
13 | tests = eval s
14 | nExecuted = nFailed = 0
15 | t0 = Date.now()
16 | lastTestTimestamp = 0
17 | for [code, mode, expectation] in tests
18 | nExecuted++
19 | outcome = runDocTest [code, mode, expectation], exec, approx
20 | if not outcome.success
21 | nFailed++
22 | process.stdout.write """
23 | Test failed: #{JSON.stringify code}
24 | #{JSON.stringify expectation}\n
25 | """
26 | if outcome.reason then process.stdout.write outcome.reason + '\n'
27 | if outcome.error then process.stdout.write outcome.error.stack + '\n'
28 | if Date.now() - lastTestTimestamp > 100
29 | process.stdout.write(
30 | "#{nExecuted}/#{tests.length}#{if nFailed then " (#{nFailed} failed)" else ''}\r"
31 | )
32 | lastTestTimestamp = Date.now()
33 | process.stdout.write(
34 | (if nFailed then "#{nFailed} out of #{nExecuted} tests failed"
35 | else "All #{nExecuted} tests passed") +
36 | " in #{Date.now() - t0} ms.\n"
37 | )
38 | process.exit(+!!nFailed)
39 |
--------------------------------------------------------------------------------
/web-src/README:
--------------------------------------------------------------------------------
1 | This directory contains artefacts for the web demo. They are processed in
2 | various ways by ../build and the result is ultimately put in ../web/
3 |
--------------------------------------------------------------------------------
/web-src/apl385.css:
--------------------------------------------------------------------------------
1 | @font-face { font-family: apl; src: url(apl385.ttf) format("truetype"); }
2 |
--------------------------------------------------------------------------------
/web-src/apl385.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PlanetAPL/node-apl/3bbd7ddfe2378be316d0a9b62b098728f324b1a0/web-src/apl385.ttf
--------------------------------------------------------------------------------
/web-src/examples-gen.coffee:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env coffee
2 | # Collect data from ../examples and generate examples.js for inclusion into index.html
3 | fs = require 'fs'
4 | glob = require 'glob'
5 | {basename} = require 'path'
6 | fs.writeFileSync "#{__dirname}/examples.js", """
7 | // Generated code, do not edit
8 | window.examples = [
9 | #{(
10 | for f in glob.sync "#{__dirname}/../examples/*.apl"
11 | ' ' + JSON.stringify [
12 | basename(f).replace(/^\d+-|\.apl$/g, '')
13 | fs.readFileSync(f, 'utf8').replace(/^#!.*\n+|\n+$/g, '').replace(/\n* *⎕ *← *(.*)$/, '\n$1')
14 | ]
15 | ).join ',\n'}
16 | ];
17 | """
18 |
--------------------------------------------------------------------------------
/web-src/index.coffee:
--------------------------------------------------------------------------------
1 | jQuery ($) ->
2 |
3 | # Bookmarkable source code
4 | hashParams = {}
5 | if location.hash
6 | for nameValue in location.hash.substring(1).split ','
7 | [name, value] = nameValue.split '='
8 | hashParams[name] = unescape value
9 | $('#code').text(hashParams.code or '').focus()
10 |
11 | $('#permalink').tipsy(gravity: 'e', opacity: 1, delayIn: 1000)
12 | .bind 'mouseover focus', ->
13 | $(@).attr 'href', '#code=' + escape $('#code').val()
14 | false
15 |
16 |
17 |
18 | # "Execute" button
19 | execute = ->
20 | try
21 | s = $('#code').val()
22 | if s.toLowerCase() in [')test', ')t']
23 | $('#result').removeClass('error').text 'Running tests...'
24 | setTimeout runDocTests, 1
25 | else
26 | result = apl s
27 | $('#result').removeClass('error').text "#{apl.format(result).join '\n'}\n"
28 | catch err
29 | console?.error?(err.stack)
30 | $('#result').addClass('error').text err
31 | return
32 |
33 | $('#go').tipsy(gravity: 'e', opacity: 1, delayIn: 1000)
34 | .closest('form').submit -> execute(); false
35 |
36 | if hashParams.run then $('#go').click()
37 |
38 |
39 |
40 | # Symbols table
41 | hSymbolDefs =
42 | '+': 'Conjugate, Add'
43 | '-': 'Negate, Subtract'
44 | '×': 'Sign of, Multiply'
45 | '÷': 'Reciprocal, Divide'
46 | '⌈': 'Ceiling, Greater of'
47 | '⌊': 'Floor, Lesser of'
48 | '∣': 'Absolute value, Residue'
49 | '⍳': 'Index generator, Index of'
50 | '?': 'Roll, Deal'
51 | '*': 'Exponential, To the power of'
52 | '⍟': 'Natural logarithm, Logarithm to the base'
53 | '○': 'Pi times, Circular and hyperbolic functions'
54 | '!': 'Factorial, Binomial'
55 | '⌹': 'Matrix inverse, Matrix divide'
56 | '⍠': 'Variant operator'
57 | '<': 'Less than'
58 | '≤': 'Less than or equal'
59 | '=': 'Equal'
60 | '≥': 'Greater than or equal'
61 | '>': 'Greater than'
62 | '≠': 'Not equal'
63 | '≡': 'Depth, Match'
64 | '≢': 'Tally, Not match'
65 | '∊': 'Enlist, Membership'
66 | '⍷': 'Find'
67 | '∪': 'Unique, Union'
68 | '∩': 'Intersection'
69 | '~': 'Not, Without'
70 | '∨': 'Or (Greatest Common Divisor)'
71 | '∧': 'And (Least Common Multiple)'
72 | '⍱': 'Nor'
73 | '⍲': 'Nand'
74 | '⍴': 'Shape of, Reshape'
75 | ',': 'Ravel, Catenate'
76 | '⍪': 'First axis catenate'
77 | '⌽': 'Reverse, Rotate'
78 | '⊖': 'First axis rotate'
79 | '⍉': 'Transpose'
80 | '↑': 'First, Take'
81 | '↓': 'Drop'
82 | '⊂': 'Enclose, Partition'
83 | '⊃': 'Mix, Pick'
84 | '⌷': 'Index'
85 | '⍋': 'Grade up'
86 | '⍒': 'Grade down'
87 | '⊤': 'Encode'
88 | '⊥': 'Decode'
89 | '⍕': 'Format, Format by specification'
90 | '⍎': 'Execute'
91 | '⊣': 'Stop, Left'
92 | '⊢': 'Pass, Right'
93 | '⎕': 'Evaluated input, Output with a newline'
94 | '⍞': 'Character input, Bare output'
95 | '¨': 'Each'
96 | '∘': 'Compose'
97 | '/': 'Reduce'
98 | '⌿': '1st axis reduce'
99 | '\\': 'Scan'
100 | '⍀': '1st axis scan'
101 | '⍣': 'Power operator'
102 | '⍨': 'Commute'
103 | '¯': 'Negative number sign'
104 | '∞': 'Infinity'
105 | '⍝': 'Comment'
106 | '←': 'Assignment'
107 | '⍬': 'Zilde'
108 | '⋄': 'Statement separator'
109 | '⍺': 'Left formal parameter'
110 | '⍵': 'Right formal parameter'
111 | APL: 'Press backquote (`) followed by another key to insert an APL symbol, e.g. `r inserts rho (⍴)'
112 |
113 |
114 |
115 | # Keyboard
116 | layout =
117 | default: [
118 | '` 1 2 3 4 5 6 7 8 9 0 - ='
119 | 'q w e r t y u i o p [ ] \\'
120 | 'a s d f g h j k l ; \' {enter}'
121 | '{shift} z x c v b n m , . / {bksp}'
122 | '{alt} {space} {exec!!}'
123 | ]
124 | shift: [
125 | '~ ! @ # $ % ^ & * ( ) _ +'
126 | 'Q W E R T Y U I O P { } |'
127 | 'A S D F G H J K L : " {enter}'
128 | '{shift} Z X C V B N M < > ? {bksp}'
129 | '{alt} {space} {exec!!}'
130 | ]
131 | alt: [
132 | '{empty} ¨ ¯ < ≤ = ≥ > ≠ ∨ ∧ ÷ ×'
133 | '{empty} ⍵ ∊ ⍴ ~ ↑ ↓ ⍳ ○ ⍟ ← → ⍀'
134 | '⍺ ⌈ ⌊ ⍫ ∇ ∆ ∘ {empty} ⎕ ⋄ {empty} {enter}'
135 | '{shift} ⊂ ⊃ ∩ ∪ ⊥ ⊤ ∣ ⍪ {empty} ⌿ {bksp}'
136 | '{alt} {space} {exec!!}'
137 | ]
138 | 'alt-shift': [
139 | '⍨ ∞ ⍁ ⍂ ⍠ ≈ ⌸ ⍯ ⍣ ⍱ ⍲ ≢ ≡'
140 | '⌹ ⍹ ⍷ ⍤ {empty} ⌶ ⊖ ⍸ ⍬ ⌽ ⊣ ⊢ ⍉'
141 | '⍶ {empty} {empty} {empty} ⍒ ⍋ ⍝ {empty} ⍞ {empty} {empty} {enter}'
142 | '{shift} ⊆ ⊇ ⋔ ⍦ ⍎ ⍕ ⌷ « » ↗ {bksp}'
143 | '{alt} {space} {exec!!}'
144 | ]
145 |
146 | # Key mappings
147 | combos = '`': {}
148 | asciiKeys = layout.default.concat(layout.shift).join(' ').split ' '
149 | aplKeys = layout.alt.concat(layout['alt-shift']).join(' ').split ' '
150 | for k, i in asciiKeys
151 | v = aplKeys[i]
152 | if not (/^\{\w+\}$/.test(k) or /^\{\w+\}$/.test(v))
153 | combos['`'][k] = v
154 |
155 | $.keyboard.keyaction.exec = execute
156 | $.keyboard.defaultOptions.combos = {}
157 | $.keyboard.comboRegex = /(`)(.)/mig
158 | $('textarea').keyboard
159 | layout: 'custom'
160 | useCombos: false
161 | display: {bksp: 'Bksp', shift: '⇧', alt: 'APL', enter: 'Enter', exec: '⍎'}
162 | autoAccept: true
163 | usePreview: false
164 | customLayout: layout
165 | useCombos: true
166 | combos: combos
167 |
168 | $('textarea').addTyping().focus()
169 |
170 | $('#code').keydown (event) -> if event.keyCode is 13 and event.ctrlKey then $('#go').click(); false
171 |
172 | tipsyOpts =
173 | title: ->
174 | hSymbolDefs[$(@).text()] ? ''
175 | gravity: 's'
176 | delayIn: 1000
177 | opacity: 1
178 |
179 | $('.ui-keyboard').on 'mouseover', '.ui-keyboard-button', (event) ->
180 | $b = $(event.target).closest '.ui-keyboard-button'
181 | if not $b.data 'tipsyInitialised'
182 | $b.data('tipsyInitialised', true).tipsy(tipsyOpts).tipsy 'show'
183 | false
184 |
185 |
186 |
187 | # Examples
188 | for [name, code], i in window.examples
189 | $('#examples').append(" #{name}")
190 |
191 | $('#examples').on 'click', 'a', ->
192 | [name, code] = window.examples[parseInt $(@).attr('href').replace /#example(\d+)$/, '$1']
193 | $('#code').val(code).focus()
194 | false
195 |
196 |
197 |
198 | # Tests
199 | runDocTests = ->
200 | $('#result').removeClass('error').html ''
201 | nExecuted = nFailed = 0
202 | t0 = Date.now()
203 | for [code, mode, expectation] in aplTests
204 | nExecuted++
205 | outcome = runDocTest [code, mode, expectation], apl, apl.approx
206 | if not outcome.success
207 | nFailed++
208 | s = """
209 | Test failed: #{JSON.stringify code}
210 | #{JSON.stringify expectation}\n
211 | """
212 | if outcome.reason then s += outcome.reason + '\n'
213 | if outcome.error then s += outcome.error.stack + '\n'
214 | $('#result').text $('#result').text() + s
215 | $('#result').text $('#result').text() + (
216 | (if nFailed then "#{nFailed} out of #{nExecuted} tests failed"
217 | else "All #{nExecuted} tests passed") +
218 | " in #{Date.now() - t0} ms.\n"
219 | )
220 | return
221 |
222 | return
223 |
--------------------------------------------------------------------------------
/web-src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | width: 1000px;
3 | margin: 10px auto;
4 | font-family: apl;
5 | }
6 |
7 | textarea, input {
8 | font-family: apl;
9 | }
10 |
11 | h1 {
12 | font-family: apl;
13 | }
14 |
15 | a {
16 | padding: 1px 1px;
17 | margin: 1px 0;
18 | text-decoration: none;
19 | color: #008;
20 | }
21 |
22 | a:hover {
23 | background-color: #fd8;
24 | }
25 |
26 | #linkToMobile {
27 | float: right;
28 | }
29 |
30 | #permalink {
31 | float: right;
32 | }
33 |
34 | #code {
35 | font-size: large;
36 | vertical-align: bottom;
37 | height: 200px;
38 | width: 100%;
39 | }
40 |
41 | #go {
42 | float: right;
43 | width: 100px;
44 | font-size: 48px;
45 | background-color: #acf;
46 | border-color: #acf;
47 | border-radius: 4px;
48 | cursor: pointer;
49 | }
50 |
51 | #result {
52 | margin-top: 20px;
53 | white-space: pre;
54 | }
55 |
56 | #result.error {
57 | color: red;
58 | }
59 |
60 | .ui-keyboard {
61 | position: fixed !important;
62 | top: auto !important;
63 | bottom: 0 !important;
64 | left: 50% !important;
65 | right: auto !important;
66 | margin-left: -15em !important;
67 | background-color: #ccc;
68 | border-top-left-radius: 6px;
69 | border-top-right-radius: 6px;
70 | border: solid #aaa 1px;
71 | border-width: 1px 1px 0 1px;
72 | }
73 |
74 | .ui-keyboard-button {
75 | background-color: #eee;
76 | font-family: apl;
77 | font-size: large;
78 | border-radius: 6px;
79 | border-style: solid;
80 | border-width: 2px;
81 | border-color: #ccc #aaa #aaa #ccc;
82 | }
83 |
84 | .ui-state-hover {
85 | border-color: #cc8 !important;
86 | }
87 |
88 | .ui-keyboard-space {
89 | min-width: 16em !important;
90 | width: auto !important;
91 | }
92 |
93 | .ui-keyboard-shift, .ui-keyboard-alt {
94 | background-color: #fd8;
95 | }
96 |
97 | .ui-keyboard-shift.ui-state-active, .ui-keyboard-alt.ui-state-active {
98 | background-color: #db6;
99 | border-color: #ccc #aaa #aaa #ccc;
100 | }
101 |
102 | .ui-keyboard-exec {
103 | background-color: #acf !important;
104 | min-width: 4em !important;
105 | width: auto !important;
106 | }
107 |
--------------------------------------------------------------------------------
/web-src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ngn/apl demo
6 |
7 |
8 |
9 | mobile version
10 |
11 | #permalink
12 | Examples:
13 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/web-src/jquery.fieldselection.min.js:
--------------------------------------------------------------------------------
1 |
2 | (function(){var fieldSelection={getSelection:function(){var e=this.jquery?this[0]:this;return(('selectionStart'in e&&function(){var l=e.selectionEnd-e.selectionStart;return{start:e.selectionStart,end:e.selectionEnd,length:l,text:e.value.substr(e.selectionStart,l)};})||(document.selection&&function(){e.focus();var r=document.selection.createRange();if(r==null){return{start:0,end:e.value.length,length:0}}
3 | var re=e.createTextRange();var rc=re.duplicate();re.moveToBookmark(r.getBookmark());rc.setEndPoint('EndToStart',re);return{start:rc.text.length,end:rc.text.length+r.text.length,length:r.text.length,text:r.text};})||function(){return{start:0,end:e.value.length,length:0};})();},replaceSelection:function(){var e=this.jquery?this[0]:this;var text=arguments[0]||'';return(('selectionStart'in e&&function(){e.value=e.value.substr(0,e.selectionStart)+text+e.value.substr(e.selectionEnd,e.value.length);return this;})||(document.selection&&function(){e.focus();document.selection.createRange().text=text;return this;})||function(){e.value+=text;return this;})();}};jQuery.each(fieldSelection,function(i){jQuery.fn[i]=this;});})();
--------------------------------------------------------------------------------
/web-src/jquery.keyboard.extension-typing.js:
--------------------------------------------------------------------------------
1 | /*
2 | * jQuery UI Virtual Keyboard Typing Simulator v1.3 for Keyboard v1.8.14+ only
3 | *
4 | * By Rob Garrison (aka Mottie & Fudgey)
5 | * Licensed under the MIT License
6 | *
7 | * Use this extension with the Virtual Keyboard to simulate
8 | * typing for tutorials or whatever else use you can find
9 | *
10 | * Requires:
11 | * jQuery
12 | * Keyboard plugin : https://github.com/Mottie/Keyboard
13 | *
14 | * Setup:
15 | * $('.ui-keyboard-input')
16 | * .keyboard(options)
17 | * .addTyping(typing-options);
18 | *
19 | * // or if targeting a specific keyboard
20 | * $('#keyboard1')
21 | * .keyboard(options)
22 | * .addTyping(typing-options);
23 | *
24 | * Basic Usage:
25 | * // To disable manual typing on the virtual keyboard, just set "showTyping" option to false
26 | * $('#keyboard-input').keyboard(options).addTyping({ showTyping: false });
27 | *
28 | * // Change the default typing delay (time the virtual keyboard highlights the manually typed key) - default = 250 milliseconds
29 | * $('#keyboard-input').keyboard(options).addTyping({ delay: 500 });
30 | *
31 | * // get keyboard object, open it, then start typing simulation
32 | * $('#keyboard-input').getkeyboard().reveal().typeIn('Hello World', 700);
33 | *
34 | * // get keyboard object, open it, type in "This is a test" with 700ms delay between types, then accept & close the keyboard
35 | * $('#keyboard-input').getkeyboard().reveal().typeIn('This is a test', 700, function(){ $('#keyboard-input').getkeyboard().close(true); });
36 | */
37 |
38 | // EXAMPLES:
39 | // $('#inter').getkeyboard().reveal().typeIn('\tHello \b\n\tWorld', 500);
40 | // $('#meta').getkeyboard().reveal().typeIn('abCDd11123\u2648\u2649\u264A\u264B', 700, function(){ alert('all done!'); });
41 |
42 | (function($){
43 | $.fn.addTyping = function(options){
44 | //Set the default values, use comma to separate the settings, example:
45 | var defaults = {
46 | showTyping : true,
47 | delay : 250
48 | };
49 | return this.each(function(){
50 | // make sure a keyboard is attached
51 | var base = $(this).data('keyboard');
52 | if (!base) { return; }
53 |
54 | // variables
55 | base.typing_options = $.extend({}, defaults, options);
56 | base.typing_keymap = {
57 | ' ' : 'space',
58 | '"' : '34',
59 | "'" : '39',
60 | ' ' : 'space',
61 | '\b' : 'bksp',
62 | '\n' : 'Enter',
63 | '\r' : 'Enter',
64 | '\t' : 'tab'
65 | };
66 | base.typing_xref = {
67 | 8 : 'bksp',
68 | 9 : 'tab',
69 | 13 : 'enter',
70 | 32 : 'space'
71 | };
72 | base.typing_event = base.typing_flag = false;
73 | // no manual typing simulation if lockInput is true; but the typeIn() function still works
74 | // if (base.options.lockInput) { base.typing_options.showTyping = false; }
75 |
76 | base.typing_setup = function(){
77 | if (base.typing_flag) { return; }
78 | base.typing_flag = true;
79 | var el = (base.$preview) ? base.$preview : base.$el;
80 |
81 | el
82 | .bind('keyup.keyboard', function(e){
83 | if (e.which >= 37 && e.which <=40) { return; } // ignore arrow keys
84 | if (e.which === 16) { base.shiftActive = false; }
85 | if (e.which === 18) { base.altActive = false; }
86 | if (e.which === 16 || e.which === 18) {
87 | base.showKeySet();
88 | setTimeout(function(){ base.$preview.focus(); }, 200); // Alt key will shift focus to the menu - doesn't work in Windows
89 | return;
90 | }
91 | })
92 | // change keyset when either shift or alt is held down
93 | .bind('keydown.keyboard', function(e){
94 | e.temp = false; // prevent repetitive calls while keydown repeats.
95 | if (e.which === 16) { e.temp = (base.shiftActive) ? false : true; base.shiftActive = true; }
96 | // it should be ok to reset e.temp, since both alt and shift will call this function separately
97 | if (e.which === 18) { e.temp = (base.altActive) ? false : true; base.altActive = true; }
98 | if (e.temp) {
99 | base.showKeySet();
100 | base.$preview.focus(); // Alt shift focus to the menu
101 | }
102 | base.typing_event = true;
103 | // Simulate key press for tab and backspace since they don't fire the keypress event
104 | if (e.which === 8 || e.which === 9) {
105 | base.typeIn( '', base.typing_options.delay || 250, function(){
106 | base.typing_event = false;
107 | }, e); // pass event object
108 | }
109 |
110 | })
111 | .bind('keypress.keyboard', function(e){
112 | // Simulate key press on virtual keyboard
113 | if (base.typing_event && !base.options.lockInput) {
114 | base.typeIn( '', base.typing_options.delay || 250, function(){
115 | base.typing_event = false;
116 | }, e); // pass event object
117 | }
118 | });
119 | }
120 |
121 | // Store typing text
122 | base.typeIn = function(txt, delay, callback, e){
123 | if (!base.isVisible) {
124 | // keyboard was closed
125 | base.typing_options.init = false;
126 | clearTimeout(base.typing_timer);
127 | return;
128 | }
129 | var o = base.typing_options, tar, m, n, k, key, ks, meta, set;
130 | if (base.typing_options.init !== true) {
131 | o.init = true;
132 | o.text = txt;
133 | o.len = txt.length;
134 | o.delay = delay || 300;
135 | o.current = 0; // position in text string
136 | o.callback = callback;
137 | }
138 |
139 | // function that loops through and types each character
140 | txt = o.text.substring( o.current, ++o.current );
141 | ks = base.$keyboard.find('.ui-keyboard-keyset');
142 | k = (base.typing_keymap.hasOwnProperty(txt)) ? base.typing_keymap[txt] : txt;
143 |
144 | // typing_event is true when typing on the actual keyboard - look for actual key
145 | // All of this breaks when the CapLock is on... unable to find a cross-browser method that works.
146 | tar = '.ui-keyboard-button[data-value="' + k + '"]';
147 | if (base.typing_event && e) {
148 | if (base.typing_xref.hasOwnProperty(e.keyCode || e.which)) {
149 | // special named keys: bksp, tab and enter
150 | tar = '.ui-keyboard-' + base.typing_xref[e.keyCode || e.which];
151 | } else {
152 | m = String.fromCharCode(e.charCode || e.which);
153 | tar = (base.mappedKeys.hasOwnProperty(m)) ? '.ui-keyboard-button[data-value="' + base.mappedKeys[m] + '"]' : '.ui-keyboard-' + (e.charCode || e.which);
154 | }
155 | }
156 | // find key
157 | key = ks.filter(':visible').find(tar);
158 | if (key.length) {
159 | // key is visible, simulate typing
160 | base.typing_simulateKey(key,txt);
161 | } else {
162 | // key not found, check if it is in the keymap (tab, space, enter, etc)
163 | if (base.typing_event) {
164 | key = ks.find(tar);
165 | } else {
166 | // key not found, check if it is in the keymap (tab, space, enter, etc)
167 | n = (base.typing_keymap.hasOwnProperty(txt)) ? base.typing_keymap[txt] : txt.charCodeAt(0);
168 | if (n === 'bksp') { txt = n; }
169 | // find actual key on keyboard
170 | key = ks.find('.ui-keyboard-' + n);
171 | }
172 |
173 | // find the keyset
174 | set = key.closest('.ui-keyboard-keyset');
175 |
176 | // figure out which keyset the key is in then simulate clicking on that meta key, then on the key
177 | if (set.attr('name')) {
178 | // get meta key name
179 | meta = set.attr('name');
180 | // show correct key set
181 | base.shiftActive = /shift/.test(meta);
182 | base.altActive = /alt/.test(meta);
183 | base.metaActive = base.lastKeyset[2] = (meta).match(/meta\d+/) || false;
184 | // make the plugin think we're passing it a jQuery object with a name
185 |
186 | base.showKeySet({ name : base.metaActive});
187 |
188 | // Add the key
189 | base.typing_simulateKey(key,txt);
190 | } else {
191 | if (!base.typing_event) {
192 | // Key doesn't exist on the keyboard, so just enter it
193 | base.insertText(txt);
194 | base.checkCombos();
195 | }
196 | }
197 |
198 | }
199 |
200 | if (o.current < o.len){
201 | if (!base.isVisible) { return; } // keyboard was closed, abort!!
202 | setTimeout(function(){ base.typeIn(); }, o.delay);
203 | } else {
204 | o.init = false;
205 | if ($.isFunction(o.callback)) {
206 | // ensure all typing animation is done before the callback
207 | setTimeout(function(){
208 | o.callback(base);
209 | }, o.delay);
210 | }
211 | return;
212 | }
213 | };
214 |
215 | // mouseover the key, add the text directly, then mouseout on the key
216 | base.typing_simulateKey = function(el,txt){
217 | var e = el.length;
218 | if (e) { el.filter(':visible').trigger('mouseenter.keyboard'); }
219 | base.typing_timer = setTimeout(function(){
220 | if (e) { setTimeout(function(){ el.trigger('mouseleave.keyboard'); }, base.typing_options.delay/3); }
221 | if (!base.isVisible) { return; }
222 | if (!base.typing_event) {
223 | base.insertText(txt);
224 | base.checkCombos();
225 | }
226 | }, base.typing_options.delay/3);
227 | };
228 |
229 | if (base.typing_options.showTyping) {
230 | // visible event is fired before this extension is initialized, so check!
231 | if (base.options.alwaysOpen && base.isVisible) {
232 | base.typing_setup();
233 | }
234 | // capture and simulate typing
235 | base.$el.bind('visible.keyboard', function(e){
236 | base.typing_setup();
237 | });
238 | }
239 |
240 | });
241 | };
242 | })(jQuery);
243 |
--------------------------------------------------------------------------------
/web-src/jquery.tipsy.js:
--------------------------------------------------------------------------------
1 | // tipsy, facebook style tooltips for jquery
2 | // version 1.0.0a
3 | // (c) 2008-2010 jason frame [jason@onehackoranother.com]
4 | // releated under the MIT license
5 |
6 | (function($) {
7 |
8 | function fixTitle($ele) {
9 | if ($ele.attr('title') || typeof($ele.attr('original-title')) != 'string') {
10 | $ele.attr('original-title', $ele.attr('title') || '').removeAttr('title');
11 | }
12 | }
13 |
14 | function Tipsy(element, options) {
15 | this.$element = $(element);
16 | this.options = options;
17 | this.enabled = true;
18 | fixTitle(this.$element);
19 | }
20 |
21 | Tipsy.prototype = {
22 | show: function() {
23 | var title = this.getTitle();
24 | if (title && this.enabled) {
25 | var $tip = this.tip();
26 |
27 | $tip.find('.tipsy-inner')[this.options.html ? 'html' : 'text'](title);
28 | $tip[0].className = 'tipsy'; // reset classname in case of dynamic gravity
29 | $tip.remove().css({top: 0, left: 0, visibility: 'hidden', display: 'block'}).appendTo(document.body);
30 |
31 | var pos = $.extend({}, this.$element.offset(), {
32 | width: this.$element[0].offsetWidth,
33 | height: this.$element[0].offsetHeight
34 | });
35 |
36 | var actualWidth = $tip[0].offsetWidth, actualHeight = $tip[0].offsetHeight;
37 | var gravity = (typeof this.options.gravity == 'function')
38 | ? this.options.gravity.call(this.$element[0])
39 | : this.options.gravity;
40 |
41 | var tp;
42 | switch (gravity.charAt(0)) {
43 | case 'n':
44 | tp = {top: pos.top + pos.height + this.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2};
45 | break;
46 | case 's':
47 | tp = {top: pos.top - actualHeight - this.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2};
48 | break;
49 | case 'e':
50 | tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth - this.options.offset};
51 | break;
52 | case 'w':
53 | tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width + this.options.offset};
54 | break;
55 | }
56 |
57 | if (gravity.length == 2) {
58 | if (gravity.charAt(1) == 'w') {
59 | tp.left = pos.left + pos.width / 2 - 15;
60 | } else {
61 | tp.left = pos.left + pos.width / 2 - actualWidth + 15;
62 | }
63 | }
64 |
65 | $tip.css(tp).addClass('tipsy-' + gravity);
66 |
67 | if (this.options.fade) {
68 | $tip.stop().css({opacity: 0, display: 'block', visibility: 'visible'}).animate({opacity: this.options.opacity});
69 | } else {
70 | $tip.css({visibility: 'visible', opacity: this.options.opacity});
71 | }
72 | }
73 | },
74 |
75 | hide: function() {
76 | if (this.options.fade) {
77 | this.tip().stop().fadeOut(function() { $(this).remove(); });
78 | } else {
79 | this.tip().remove();
80 | }
81 | },
82 |
83 | getTitle: function() {
84 | var title, $e = this.$element, o = this.options;
85 | fixTitle($e);
86 | var title, o = this.options;
87 | if (typeof o.title == 'string') {
88 | title = $e.attr(o.title == 'title' ? 'original-title' : o.title);
89 | } else if (typeof o.title == 'function') {
90 | title = o.title.call($e[0]);
91 | }
92 | title = ('' + title).replace(/(^\s*|\s*$)/, "");
93 | return title || o.fallback;
94 | },
95 |
96 | tip: function() {
97 | if (!this.$tip) {
98 | this.$tip = $('').html('');
99 | }
100 | return this.$tip;
101 | },
102 |
103 | validate: function() {
104 | if (!this.$element[0].parentNode) {
105 | this.hide();
106 | this.$element = null;
107 | this.options = null;
108 | }
109 | },
110 |
111 | enable: function() { this.enabled = true; },
112 | disable: function() { this.enabled = false; },
113 | toggleEnabled: function() { this.enabled = !this.enabled; }
114 | };
115 |
116 | $.fn.tipsy = function(options) {
117 |
118 | if (options === true) {
119 | return this.data('tipsy');
120 | } else if (typeof options == 'string') {
121 | return this.data('tipsy')[options]();
122 | }
123 |
124 | options = $.extend({}, $.fn.tipsy.defaults, options);
125 |
126 | function get(ele) {
127 | var tipsy = $.data(ele, 'tipsy');
128 | if (!tipsy) {
129 | tipsy = new Tipsy(ele, $.fn.tipsy.elementOptions(ele, options));
130 | $.data(ele, 'tipsy', tipsy);
131 | }
132 | return tipsy;
133 | }
134 |
135 | function enter() {
136 | var tipsy = get(this);
137 | tipsy.hoverState = 'in';
138 | if (options.delayIn == 0) {
139 | tipsy.show();
140 | } else {
141 | setTimeout(function() { if (tipsy.hoverState == 'in') tipsy.show(); }, options.delayIn);
142 | }
143 | };
144 |
145 | function leave() {
146 | var tipsy = get(this);
147 | tipsy.hoverState = 'out';
148 | if (options.delayOut == 0) {
149 | tipsy.hide();
150 | } else {
151 | setTimeout(function() { if (tipsy.hoverState == 'out') tipsy.hide(); }, options.delayOut);
152 | }
153 | };
154 |
155 | if (!options.live) this.each(function() { get(this); });
156 |
157 | if (options.trigger != 'manual') {
158 | var binder = options.live ? 'live' : 'bind',
159 | eventIn = options.trigger == 'hover' ? 'mouseenter' : 'focus',
160 | eventOut = options.trigger == 'hover' ? 'mouseleave' : 'blur';
161 | this[binder](eventIn, enter)[binder](eventOut, leave);
162 | }
163 |
164 | return this;
165 |
166 | };
167 |
168 | $.fn.tipsy.defaults = {
169 | delayIn: 0,
170 | delayOut: 0,
171 | fade: false,
172 | fallback: '',
173 | gravity: 'n',
174 | html: false,
175 | live: false,
176 | offset: 0,
177 | opacity: 0.8,
178 | title: 'title',
179 | trigger: 'hover'
180 | };
181 |
182 | // Overwrite this method to provide options on a per-element basis.
183 | // For example, you could store the gravity in a 'tipsy-gravity' attribute:
184 | // return $.extend({}, options, {gravity: $(ele).attr('tipsy-gravity') || 'n' });
185 | // (remember - do not modify 'options' in place!)
186 | $.fn.tipsy.elementOptions = function(ele, options) {
187 | return $.metadata ? $.extend({}, options, $(ele).metadata()) : options;
188 | };
189 |
190 | $.fn.tipsy.autoNS = function() {
191 | return $(this).offset().top > ($(document).scrollTop() + $(window).height() / 2) ? 's' : 'n';
192 | };
193 |
194 | $.fn.tipsy.autoWE = function() {
195 | return $(this).offset().left > ($(document).scrollLeft() + $(window).width() / 2) ? 'e' : 'w';
196 | };
197 |
198 | })(jQuery);
199 |
--------------------------------------------------------------------------------
/web-src/keyboard.css:
--------------------------------------------------------------------------------
1 | /* keyboard - jQuery UI Widget */
2 | .ui-keyboard { padding: .3em; position: absolute; left: 0; top: 0; z-index: 16000; }
3 | .ui-keyboard-has-focus { z-index: 16001; }
4 | .ui-keyboard div { font-size: 1.1em; }
5 | .ui-keyboard-button { height: 2em; width: 2em; min-width: 1em; margin: .1em; cursor: pointer; overflow: hidden; line-height: 2em; -moz-user-focus: ignore; }
6 | .ui-keyboard-button span { padding: 0; margin: 0; white-space:nowrap; display: inline-block; }
7 | .ui-keyboard-button-endrow { clear: left; }
8 | .ui-keyboard-widekey { min-width: 4em; width: auto; }
9 | .ui-keyboard-space { width: 15em; }
10 | .ui-keyboard-space span, .ui-keyboard-empty span { font: 0/0 a; text-shadow: none; color: transparent; } /* see http://nicolasgallagher.com/another-css-image-replacement-technique/ */
11 | .ui-keyboard-preview-wrapper { text-align: center; }
12 | .ui-keyboard-preview { text-align: left; margin: 0 0 3px 0; display: inline; width: 99%;} /* width is calculated in IE, since 99% = 99% full browser width =( */
13 | .ui-keyboard-keyset { text-align: center; white-space: nowrap; }
14 | .ui-keyboard-input { text-align: left; }
15 | .ui-keyboard-input-current { -moz-box-shadow: 1px 1px 10px #00f; -webkit-box-shadow: 1px 1px 10px #00f; box-shadow: 1px 1px 10px #00f; }
16 | .ui-keyboard-placeholder { color: #888; }
17 | .ui-keyboard-nokeyboard { color: #888; border-color: #888; } /* disabled or readonly inputs, or use input[disabled='disabled'] { color: #f00; } */
18 | .ui-keyboard-button.disabled { opacity: 0.5; filter: alpha(opacity=50); } /* used by the acceptValid option to make the accept button appear faded */
19 | .ui-keyboard-spacer { display: inline-block; width: 1px; height: 0; }
20 |
21 | /* combo key styling - toggles diacritics on/off */
22 | .ui-keyboard-button.ui-keyboard-combo.ui-state-default { border-color: #ffaf0f; }
23 |
24 | /* (in)valid inputs */
25 | button.ui-keyboard-accept.ui-keyboard-valid-input { border-color: #0c0; background: #080; color: #fff; }
26 | button.ui-keyboard-accept.ui-keyboard-valid-input:hover { background: #0a0; }
27 | button.ui-keyboard-accept.ui-keyboard-invalid-input { border-color: #c00; background: #800; color: #fff; }
28 | button.ui-keyboard-accept.ui-keyboard-invalid-input:hover { background: #a00; }
29 |
30 | /*** jQuery Mobile definitions ***/
31 | /* jQuery Mobile styles - need wider buttons because of font size and text-overflow:ellipsis */
32 | .ui-bar .ui-keyboard-button { width: 3em; display: inline-block; }
33 | .ui-bar .ui-keyboard-widekey { width: 5.5em; }
34 | .ui-bar .ui-keyboard-space { width: 15em; }
35 | .ui-bar .ui-keyboard-space span { visibility: hidden; } /* hides the ellipsis */
36 | .ui-bar .ui-keyboard-keyset { line-height: 0.5em; }
37 | .ui-bar input.ui-input-text, .ui-bar textarea.ui-input-text { width: 95%; }
38 |
39 | /* over-ride padding set by mobile ui theme - needed because the mobile script wraps button text with several more spans */
40 | .ui-bar .ui-btn-inner { height: 2em; padding: 0.2em 0; margin: 0; }
41 | .ui-bar .ui-btn { margin: 0; font-size: 13px; } /* mobile default size is 13px */
42 |
43 | /* Media Queries (optimized for jQuery UI themes; may be slightly off in jQuery Mobile themes) */
44 | /* 240 x 320 (small phone) */
45 | @media all and (max-width: 319px) {
46 | .ui-keyboard div { font-size: 9px; }
47 | .ui-keyboard .ui-keyboard-input { font-size: 12px; }
48 | /* I don't own an iPhone so I have no idea how small this really is... is it even clickable with your finger? */
49 | .ui-bar .ui-btn { margin: 0; font-size: 9px; }
50 | .ui-bar .ui-keyboard-button { width: 1.8em; height: 2.5em; }
51 | .ui-bar .ui-keyboard-widekey { width: 4em; }
52 | .ui-bar .ui-keyboard-space { width: 8em; }
53 | .ui-bar .ui-btn-inner { height: 2.5em; padding: 0.3em 0; }
54 | }
55 |
56 | /* 320 x 480 (iPhone) */
57 | @media all and (min-width: 320px) and (max-width: 479px) {
58 | .ui-keyboard div { font-size: 9px; }
59 | .ui-keyboard .ui-keyboard-input { font-size: 14px; }
60 | /* I don't own an iPhone so I have no idea how small this really is... is it even clickable with your finger? */
61 | .ui-bar .ui-btn { margin: 0; font-size: 11px; }
62 | .ui-bar .ui-keyboard-button { width: 1.8em; height: 3em; }
63 | .ui-bar .ui-keyboard-widekey { width: 4.5em; }
64 | .ui-bar .ui-keyboard-space { width: 10em; }
65 | .ui-bar .ui-btn-inner { height: 3em; padding: 0.7em 0; }
66 | }
67 |
68 | /* 480 x 640 (small tablet) */
69 | @media all and (min-width: 480px) and (max-width: 767px) {
70 | .ui-keyboard div { font-size: 13px; }
71 | .ui-keyboard .ui-keyboard-input { font-size: 14px; }
72 | .ui-bar .ui-btn { margin: 0; font-size: 10px; }
73 | .ui-bar .ui-keyboard-button { height: 2.5em; }
74 | .ui-bar .ui-btn-inner { height: 2.5em; padding: 0.5em 0; }
75 | }
--------------------------------------------------------------------------------
/web-src/tipsy.css:
--------------------------------------------------------------------------------
1 | .tipsy { padding: 5px; font-size: 10px; position: absolute; z-index: 100000; }
2 | .tipsy-inner { padding: 5px 8px 4px 8px; background-color: black; color: white; max-width: 200px; text-align: center; }
3 | .tipsy-inner { border-radius: 3px; -moz-border-radius:3px; -webkit-border-radius:3px; }
4 | .tipsy-arrow { position: absolute; background: url(tipsy.gif) no-repeat top left; width: 9px; height: 5px; }
5 | .tipsy-n .tipsy-arrow { top: 0; left: 50%; margin-left: -4px; }
6 | .tipsy-nw .tipsy-arrow { top: 0; left: 10px; }
7 | .tipsy-ne .tipsy-arrow { top: 0; right: 10px; }
8 | .tipsy-s .tipsy-arrow { bottom: 0; left: 50%; margin-left: -4px; background-position: bottom left; }
9 | .tipsy-sw .tipsy-arrow { bottom: 0; left: 10px; background-position: bottom left; }
10 | .tipsy-se .tipsy-arrow { bottom: 0; right: 10px; background-position: bottom left; }
11 | .tipsy-e .tipsy-arrow { top: 50%; margin-top: -4px; right: 0; width: 5px; height: 9px; background-position: top right; }
12 | .tipsy-w .tipsy-arrow { top: 50%; margin-top: -4px; left: 0; width: 5px; height: 9px; }
13 |
--------------------------------------------------------------------------------
/web-src/tipsy.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PlanetAPL/node-apl/3bbd7ddfe2378be316d0a9b62b098728f324b1a0/web-src/tipsy.gif
--------------------------------------------------------------------------------