├── .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 | [![Build Status](https://travis-ci.org/ngn/apl.png?branch=master)](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 | 18 | 20 | 39 | 46 | 47 | 49 | 50 | 52 | image/svg+xml 53 | 55 | 56 | 57 | 58 | 59 | 64 | 71 | 72 | 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 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 |
          
          
         
Shift       Bksp
AltEnterExec
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 | # +(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 |

ngn/apl demo

11 | 12 |
Examples:
13 |
14 |

15 | 16 | 17 |

18 |
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 --------------------------------------------------------------------------------