├── CHANNELS.md
├── DESIGN.md
├── GOALS.md
├── README.md
├── REF.md
├── TESTS.md
├── apl
├── README.md
├── a
│ ├── commands.go
│ ├── help.go
│ ├── register.go
│ └── runtime.go
├── apl.go
├── array.go
├── array_test.go
├── assign.go
├── big
│ ├── bigfloat
│ │ ├── LICENSE.md
│ │ ├── README
│ │ ├── exp.go
│ │ ├── log.go
│ │ ├── misc.go
│ │ ├── pow.go
│ │ └── sqrt.go
│ ├── complex.go
│ ├── float.go
│ ├── int.go
│ ├── rat.go
│ └── register.go
├── bool.go
├── channel.go
├── domain.go
├── domain
│ ├── array.go
│ ├── channel.go
│ ├── compare.go
│ ├── domain.go
│ ├── functions.go
│ ├── identifier.go
│ ├── image.go
│ ├── list.go
│ ├── numeric.go
│ ├── object.go
│ ├── operator.go
│ ├── scalar.go
│ ├── string.go
│ └── type.go
├── error.go
├── eval.go
├── fmt.go
├── fmt_test.go
├── function.go
├── http
│ └── register.go
├── image.go
├── index.go
├── int.go
├── io
│ ├── README.md
│ ├── env.go
│ ├── fs.go
│ ├── mount.go
│ ├── read.go
│ ├── register.go
│ └── var.go
├── lambda.go
├── list.go
├── multiline.go
├── numbers
│ ├── complex.go
│ ├── complexs.go
│ ├── exception.go
│ ├── float.go
│ ├── floats.go
│ ├── numbers_test.go
│ ├── register.go
│ ├── time.go
│ └── times.go
├── object.go
├── op.go
├── operators
│ ├── assign.go
│ ├── at.go
│ ├── axis.go
│ ├── commute.go
│ ├── dot.go
│ ├── each.go
│ ├── identity.go
│ ├── jot.go
│ ├── power.go
│ ├── rank.go
│ ├── reduce.go
│ ├── register.go
│ ├── select.go
│ └── stencil.go
├── parse.go
├── parse_test.go
├── primitives
│ ├── apl_test.go
│ ├── array.go
│ ├── axis.go
│ ├── boolean.go
│ ├── channel.go
│ ├── comma.go
│ ├── compare.go
│ ├── decode.go
│ ├── dict.go
│ ├── doc_test.go
│ ├── domino.go
│ ├── elementary.go
│ ├── enclose.go
│ ├── find.go
│ ├── format.go
│ ├── gen.go
│ ├── grade.go
│ ├── index.go
│ ├── iota.go
│ ├── match.go
│ ├── query.go
│ ├── register.go
│ ├── reverse.go
│ ├── rho.go
│ ├── selection.go
│ ├── table.go
│ ├── tack.go
│ ├── take.go
│ ├── transpose.go
│ ├── type.go
│ ├── uniform_test.go
│ └── unique.go
├── register.go
├── rpc
│ ├── README.md
│ ├── init.go
│ ├── register.go
│ ├── rpc.go
│ └── server.go
├── scan
│ ├── scan.go
│ └── scan_test.go
├── string.go
├── strings
│ └── register.go
├── table.go
├── tower.go
├── train.go
├── uniform.go
├── value.go
├── var.go
└── xgo
│ ├── convert.go
│ ├── function.go
│ ├── method.go
│ ├── register.go
│ └── type.go
├── build
├── cmd
├── apl.go
├── apl
│ ├── README.md
│ ├── apl_test.go
│ ├── main.go
│ └── testdata
│ │ ├── a.apl
│ │ ├── a.out
│ │ ├── abc.apl
│ │ ├── abc.out
│ │ ├── e.apl
│ │ ├── e.err
│ │ ├── finn.apl
│ │ ├── finn.out
│ │ ├── fmt.apl
│ │ ├── fmt.out
│ │ ├── obj.apl
│ │ ├── obj.out
│ │ ├── parse.apl
│ │ ├── parse.out
│ │ ├── table.apl
│ │ └── table.out
├── iv.go
├── iv
│ ├── README.md
│ ├── iv_test.go
│ ├── main.go
│ └── testdata
│ │ ├── a.iv
│ │ ├── a.out
│ │ ├── colsum.iv
│ │ ├── colsum.out
│ │ ├── rank.iv
│ │ ├── rank.out
│ │ ├── shape.iv
│ │ └── shape.out
└── testing.go
├── go.mod
└── log.svg
/GOALS.md:
--------------------------------------------------------------------------------
1 | # APL\iv
2 | APL\iv is an implementation of APL written in go, depending only on the standard library.
3 |
4 | As such, it is a package that can be included into any go project.
5 | This may be as small as `cmd/apl` or a huge application where APL simply acts as a hidden debugging interface with all it's expressive powers.
6 |
7 | - extendable and embeddable
8 | - extend APL by implementing extensions in go
9 | - embed APL by spreading it over already existing go programs
10 | - *If that sounds too complicated and uses too many words:* `(APL\iv÷Go)←→Lua÷C`
11 | - compatibility and portability: it runs everywhere go runs
12 | - cross compiling from x to y out of the box, e.g. `GOOS=linux GOARCH=mipsle go install` working on any host including windows is a matter of course
13 |
14 | # Goals
15 | - #1: Build the smallest APL machine. Small with respect to m³
16 |
17 | # Compatibility
18 | - The compatibility goal is to be mostly conforming to APL2/Dyalog core language substracting nested arrays
19 | - The parser adds some more restrictions
20 | - function variables have to be lowercase: `f←+/`, nouns are uppercase
21 | - lambdas (dfns) exist but no user defined operators directly in APL (go extension can define operators)
22 | - minor issues:
23 | - `/\ etc` are implemented as operators. These are really nasty.
24 | - assignment is also implemented as an operator. But `{indexed, modified, selective}` assignment should work.
25 |
26 | # Non-compatibility
27 | - Workspace, Namespace, Quadfunctions, I-Beams, user functions others than lambdas.
28 | All this does not exist, is or will be done differently.
29 |
30 | # Additions
31 | - Replaceable numeric tower
32 | - bool and int (index) is always there as `apl.Bool` and `apl.Index`
33 | - the default tower is compiled in with `numbers.Register(a)` and provides `int64, float64, complex128, time (time.Time + time.Duration)`
34 | - big numbers can be used with `big.Register(a)` which adds two more towers:
35 | - big: bigint, bigrat
36 | - precise: bigfloat, bigcomplex
37 | - towers are separated, they cannot be mixed within an expression, but can be changed at runtime (if they are compiled in)
38 | - `big→set 1`
39 | - Overloading primitives and operators
40 | - adding the packages `primitives.Register(a)` and `operators.Register(a)` adds the default implementation to the interpreter.
41 | - a user package (e.g. to interface any application dependend go type) may overload each primitive function or operator.
42 | - Lists
43 | - Instead of nested arrays, there is a list type similar to K's.
44 | - A list needs a terminating semicolon: `(1;2;3;)` or nested `(1;(2;3;);4;5;)`
45 | - Dicts and tables
46 | - Also influence by K are dictionaries and tables (and the time type mentioned above).
47 | - A `Dict` is a special implementation of the more general `Object`
48 | - Go interface
49 | - go structs are mapped into APL by implementing an `Object`. Working on the from APL side feels like working on a Dict.
50 | - struct methods are mapped to fields of the dict that have function type.
51 | - package xgo `xgo.Register(a)` adds helpers to translate from go to apl types.
52 | - Streaming
53 | - the type `Channel` combines two go channels for sequential reading and writing of any `apl.Value` to a concurrent process, rpc call or go routine
54 |
55 | # Speed and size
56 | Of course it should be fast and compact. However the primary goal is implementation speed. This is hard enough.
57 |
58 | # Disclaimer
59 | The author has never used APL. It's a chicken and egg problem.
60 | Primary source for the implementation is the APL2 Language Reference, Dyalog 17 Language Reference guide and on some occasions the ISO spec. All testing has been done with tryapl.org. Thank you a lot, Dyalog for this.
61 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # APL\iv interpreter and stream processor
2 |
3 |
4 |
5 |
6 | # contents
7 | - [apl: an extendable and embeddable APL interpreter written in go](apl)
8 | - Documentation
9 | - [REF.md](REF.md) reference of all standard primitives and operators
10 | - [TESTS.md](TESTS.md) output of test coverage which gives an overview of the state of affairs
11 | - [GOALS.md](GOALS.md) describes the target of the project
12 | - [DESIGN.md](DESIGN.md) description of the go implementation and how to write extra packages
13 |
14 | # programs
15 | - [cmd/apl](cmd/apl): APL interpreter as a command line program
16 | - [cmd/iv](cmd/iv): a program similar to awk with an APL backend but for streaming n-dimensional data
17 |
18 | # A random loop through pattern space
19 | ```
20 | ⎕IO←0
21 | j←{(⍳2*⍺){⎕←(?∘⍴⌷⊢)⍸~1↓⌽0,(⍺⍴2)⊤⍵}⍣≡1⍴⍵} ⍝ not done..
22 | ```
23 |
--------------------------------------------------------------------------------
/apl/README.md:
--------------------------------------------------------------------------------
1 | # APL\iv - core package
2 |
3 | This is the core package of the interpreter.
4 |
5 | ## Usage:
6 |
7 | go get github.com/ktye/iv/apl/...
8 |
9 | ```go
10 | // import github.com/ktye/iv/apl
11 |
12 | a := apl.New(os.Stdout)
13 | numbers.Register(a)
14 | primitives.Register(a)
15 | operators.Register(a)
16 |
17 | err := a.ParseAndEval("⍳3")
18 | ```
19 |
20 | The core package and all additional packages in it's subdirectories require only the Go standard library.
21 | Extra packages with external dependencies can be found in `iv/aplextra`.
22 |
23 | ## Packages
24 | - [a](a/) access to the go runtime
25 | - [big](big/) big numbers as an alternative
26 | - [io](io/) filesystem access
27 | - [rpc](rpc/) remote procedure calls and ipc communication
28 | - [strings](strings/) wrapper of go strings library
29 | - [xgo](xgo/) generic interface to go types
30 |
--------------------------------------------------------------------------------
/apl/a/commands.go:
--------------------------------------------------------------------------------
1 | package a
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/ktye/iv/apl"
7 | "github.com/ktye/iv/apl/numbers"
8 | "github.com/ktye/iv/apl/scan"
9 | )
10 |
11 | // toCommand attaches the Rewrite method to the function.
12 | type toCommand func([]scan.Token) []scan.Token
13 |
14 | func (f toCommand) Rewrite(t []scan.Token) []scan.Token {
15 | return f(t)
16 | }
17 |
18 | func symbol(s string) scan.Token {
19 | return scan.Token{T: scan.Symbol, S: s}
20 | }
21 |
22 | // rw0 is a scan.Command that rewrite the symbol with a 0 argument.
23 | // Example:
24 | // /q is rewritten to a→q 0
25 | type rw0 string
26 |
27 | func (r rw0) Rewrite(t []scan.Token) []scan.Token {
28 | sym := scan.Token{T: scan.Identifier, S: "a→" + string(r)}
29 | num := scan.Token{T: scan.Number, S: "0"}
30 | tokens := make([]scan.Token, len(t)+2)
31 | tokens[0] = sym
32 | tokens[1] = num
33 | copy(tokens[2:], t)
34 | return tokens
35 | }
36 |
37 | // printvar prints a string representation of the value.
38 | // If the value is a string that is a valid variable name, it is dereferenced.
39 | // This allows to print the definition of lambda functions.
40 | func printvar(a *apl.Apl, _, R apl.Value) (apl.Value, error) {
41 | s, ok := R.(apl.String)
42 | if !ok {
43 | return apl.String(R.String(a.Format)), nil
44 | }
45 | v := a.Lookup(string(s))
46 | if v == nil {
47 | return s, nil
48 | }
49 | return apl.String(v.String(a.Format)), nil
50 | }
51 |
52 | func printCmd(t []scan.Token) []scan.Token {
53 | return append([]scan.Token{scan.Token{T: scan.Identifier, S: "a→p"}}, t...)
54 | }
55 |
56 | // Timer is used to time an expression. It is called by the rewrite command /t
57 | // If the argument is a time, it returns the elapsed duration since that time.
58 | // Otherwise it returns the current time.
59 | func timer(a *apl.Apl, _, R apl.Value) (apl.Value, error) {
60 | t, ok := R.(numbers.Time)
61 | if !ok {
62 | return numbers.Time(time.Now()), nil
63 | }
64 | dt := time.Since(time.Time(t))
65 | y0, _ := time.Parse("15h04", "00h00") // see apl/numbers/time.go
66 | return numbers.Time(y0.Add(dt)), nil
67 | }
68 |
69 | // timeCmd rewrites the tokens to calculate the duration.
70 | // T__← a→t 0 ⋄ [TOKENS] ⋄ a→t T__
71 | func timeCmd(t []scan.Token) []scan.Token {
72 | t__ := scan.Token{T: scan.Identifier, S: "T__"}
73 | asn := symbol("←")
74 | tim := scan.Token{T: scan.Identifier, S: "a→t"}
75 | num := scan.Token{T: scan.Number, S: "0"}
76 | dia := scan.Token{T: scan.Diamond, S: "⋄"}
77 |
78 | tokens := []scan.Token{t__, asn, tim, num, dia}
79 | tokens = append(tokens, t...)
80 | return append(tokens, dia, tim, t__)
81 | }
82 |
--------------------------------------------------------------------------------
/apl/a/help.go:
--------------------------------------------------------------------------------
1 | package a
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "io/ioutil"
7 |
8 | "github.com/ktye/iv/apl"
9 | )
10 |
11 | // help returns the help text in a channel.
12 | func help(a *apl.Apl, _, R apl.Value) (apl.Value, error) {
13 | var buf bytes.Buffer
14 | fmt.Fprintf(&buf, "Commands:")
15 | for _, c := range a.Scanner.Commands() {
16 | fmt.Fprintf(&buf, " %s", c)
17 | }
18 | fmt.Fprintf(&buf, "\n\n")
19 |
20 | a.Doc(&buf)
21 | return apl.LineReader(ioutil.NopCloser(&buf)), nil
22 | }
23 |
--------------------------------------------------------------------------------
/apl/a/register.go:
--------------------------------------------------------------------------------
1 | // Package a provides an interface to the internals of the interpreter and the go runtime.
2 | //
3 | // Some of the functions defined return information about the go runtime.
4 | // This includes the parent application if the interpreter is built-in.
5 | //
6 | // c 0 return number of CPUs
7 | // g 0 return number of go routines
8 | // m 0 return runtime.MemStats as a dictionary
9 | // v 0 return go version
10 | package a
11 |
12 | import (
13 | "fmt"
14 | "os"
15 |
16 | "github.com/ktye/iv/apl"
17 | "github.com/ktye/iv/apl/scan"
18 | )
19 |
20 | // Register adds the a package to the interpreter.
21 | func Register(p *apl.Apl, name string) {
22 | if name == "" {
23 | name = "a"
24 | }
25 | pkg := map[string]apl.Value{
26 | "c": apl.ToFunction(cpus),
27 | "g": apl.ToFunction(goroutines),
28 | "h": apl.ToFunction(help),
29 | "m": apl.ToFunction(Memstats),
30 | "p": apl.ToFunction(printvar),
31 | "q": apl.ToFunction(quit),
32 | "t": apl.ToFunction(timer),
33 | "v": apl.ToFunction(goversion),
34 | }
35 | cmd := map[string]scan.Command{
36 | "h": rw0("h"),
37 | "p": toCommand(printCmd),
38 | "q": rw0("q"),
39 | "t": toCommand(timeCmd),
40 | }
41 | p.AddCommands(cmd)
42 | p.RegisterPackage(name, pkg)
43 | }
44 |
45 | // Quit accepts a string or a number.
46 | // Nonempty strings return an exit code != 0 and print the error to stderr.
47 | // Numbers are used as exit code.
48 | func quit(p *apl.Apl, _, R apl.Value) (apl.Value, error) {
49 | if n, ok := R.(apl.Number); ok {
50 | if idx, ok := n.ToIndex(); ok == false {
51 | return nil, fmt.Errorf("a q: exit code must be convertible to int")
52 | } else {
53 | os.Exit(idx)
54 | }
55 | }
56 | if s, ok := R.(apl.String); ok {
57 | if s == "" {
58 | os.Exit(0)
59 | } else {
60 | fmt.Fprintln(os.Stderr, s)
61 | os.Exit(1)
62 | }
63 | }
64 | return nil, fmt.Errorf("a q: argument must be a string or an int: %T", R)
65 | }
66 |
--------------------------------------------------------------------------------
/apl/a/runtime.go:
--------------------------------------------------------------------------------
1 | package a
2 |
3 | import (
4 | "reflect"
5 | "runtime"
6 |
7 | "github.com/ktye/iv/apl"
8 | "github.com/ktye/iv/apl/xgo"
9 | )
10 |
11 | // Memstats returns runtime.MemStats as an object.
12 | func Memstats(p *apl.Apl, L, R apl.Value) (apl.Value, error) {
13 | var m runtime.MemStats
14 | runtime.ReadMemStats(&m)
15 | return xgo.Convert(reflect.ValueOf(m))
16 | }
17 |
18 | func cpus(p *apl.Apl, _, R apl.Value) (apl.Value, error) {
19 | return apl.Int(runtime.NumCPU()), nil
20 | }
21 |
22 | func goroutines(p *apl.Apl, _, R apl.Value) (apl.Value, error) {
23 | return apl.Int(runtime.NumGoroutine()), nil
24 | }
25 |
26 | func goversion(p *apl.Apl, _, R apl.Value) (apl.Value, error) {
27 | return apl.String(runtime.Version()), nil
28 | }
29 |
--------------------------------------------------------------------------------
/apl/apl.go:
--------------------------------------------------------------------------------
1 | // Package apl implements an APL interpreter.
2 | package apl
3 |
4 | import (
5 | "io"
6 | "io/ioutil"
7 | "reflect"
8 |
9 | "github.com/ktye/iv/apl/scan"
10 | )
11 |
12 | // New starts a new interpreter.
13 | func New(w io.Writer) *Apl {
14 | a := Apl{
15 | stdout: w,
16 | env: newEnv(),
17 | Origin: 1,
18 | Format: Format{Fmt: make(map[reflect.Type]string)},
19 | //PP: 0,
20 | //Fmt: make(map[reflect.Type]string),
21 | primitives: make(map[Primitive][]PrimitiveHandler),
22 | operators: make(map[string][]Operator),
23 | symbols: make(map[rune]string),
24 | pkg: make(map[string]*env),
25 | }
26 | a.parser.a = &a
27 | return &a
28 | }
29 |
30 | // Apl stores the interpreter state.
31 | type Apl struct {
32 | scan.Scanner
33 | Format Format
34 | parser
35 | stdout io.Writer
36 | stdimg ImageWriter
37 | Tower Tower
38 | Origin int
39 | //PP int
40 | //Fmt map[reflect.Type]string
41 | env *env
42 | primitives map[Primitive][]PrimitiveHandler
43 | operators map[string][]Operator
44 | symbols map[rune]string
45 | pkg map[string]*env
46 | scaninit bool
47 | }
48 |
49 | type Format struct {
50 | PP int
51 | Fmt map[reflect.Type]string
52 | }
53 |
54 | // LoadPkg loads a package from a file.
55 | // It temporarily removes the current environment, executes the package file with EvalFile
56 | // and stores the resulting environment in a package with the name of pkg.
57 | func (a *Apl) LoadPkg(r io.Reader, file string, pkg string) (err error) {
58 | save := a.env
59 | a.env = newEnv()
60 | defer func() {
61 | a.env = save
62 | }()
63 |
64 | err = a.EvalFile(r, file)
65 | if err != nil {
66 | return err
67 | }
68 | a.pkg[pkg] = a.env
69 | return nil
70 | }
71 |
72 | // Parse parses a line of input into the current context.
73 | // It returns a Program which can be evaluated.
74 | func (a *Apl) Parse(line string) (Program, error) {
75 | tokens, err := a.Scan(line)
76 | if err != nil {
77 | return nil, err
78 | }
79 |
80 | p, err := a.parse(tokens)
81 | if err != nil {
82 | return nil, err
83 | } else {
84 | return p, nil
85 | }
86 | }
87 |
88 | func (a *Apl) ParseAndEval(line string) error {
89 | if p, err := a.Parse(line); err != nil {
90 | return err
91 | } else {
92 | return a.Eval(p)
93 | }
94 | }
95 |
96 | func (a *Apl) Scan(line string) ([]scan.Token, error) {
97 | // On the first call, the scanner needs to be told all symbols that
98 | // have been registered.
99 | if a.scaninit == false {
100 | m := make(map[rune]string)
101 | for r, s := range a.symbols {
102 | m[r] = s
103 | }
104 | a.SetSymbols(m)
105 | a.scaninit = true
106 | }
107 | return a.Scanner.Scan(line)
108 | }
109 |
110 | func (a *Apl) SetOutput(w io.Writer) {
111 | a.stdout = w
112 | }
113 |
114 | func (a *Apl) GetOutput() io.Writer {
115 | if a.stdout == nil {
116 | return ioutil.Discard
117 | }
118 | return a.stdout
119 | }
120 |
121 | func (a *Apl) SetImage(w ImageWriter) {
122 | a.stdimg = w
123 | }
124 |
125 | func newEnv() *env {
126 | return &env{vars: map[string]Value{}}
127 | }
128 |
--------------------------------------------------------------------------------
/apl/array_test.go:
--------------------------------------------------------------------------------
1 | package apl
2 |
3 | import "testing"
4 |
5 | func TestIdx(t *testing.T) {
6 | testCases := []struct {
7 | shape []int
8 | idx []int
9 | n int
10 | }{
11 | {[]int{2}, []int{1}, 1},
12 | {[]int{2, 2, 4}, []int{1, 1, 3}, 15},
13 | {[]int{3, 4, 2}, []int{1, 3, 0}, 14},
14 | }
15 |
16 | // Test IdxConverter
17 | for _, tc := range testCases {
18 | ic, idx := NewIdxConverter(tc.shape)
19 | ic.Indexes(tc.n, idx)
20 | for i := range idx {
21 | if idx[i] != tc.idx[i] {
22 | t.Fatalf("expected %v got %v", tc.idx, idx)
23 | }
24 | }
25 | n := ic.Index(idx)
26 | if n != tc.n {
27 | t.Fatalf("expected %d got %d", tc.n, n)
28 | }
29 | }
30 |
31 | // Test ArraySize and IncArrayIndex
32 | for _, tc := range testCases {
33 | ar := MixedArray{Dims: tc.shape}
34 | ic, idx := NewIdxConverter(tc.shape)
35 | for i := 0; i < ar.Size(); i++ {
36 | n := ic.Index(idx)
37 | if n != i {
38 | t.Fatalf("expected %d got %d", i, n)
39 | }
40 | IncArrayIndex(idx, tc.shape)
41 | }
42 | for i := range idx {
43 | if idx[i] != 0 {
44 | t.Fatalf("idx should be zeros: %v", idx)
45 | }
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/apl/assign.go:
--------------------------------------------------------------------------------
1 | package apl
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | )
7 |
8 | // Assignment contains the unevaluated left argument of an assignment.
9 | // It may be an identifier or an expression containing an identifier.
10 | // The identifier may be followed by a modifying function.
11 | type assignment struct {
12 | e expr
13 | }
14 |
15 | func (as assignment) String(f Format) string {
16 | return "specification " + as.e.String(f)
17 | }
18 | func (as assignment) Copy() Value { return as } // This does not do a deep copy.
19 |
20 | func (as assignment) Eval(a *Apl) (Value, error) {
21 | return as, nil
22 | }
23 |
24 | // Assignment is the evaluated left part of an assignment.
25 | // It contains the Identifier, it's indexes and a modification function.
26 | type Assignment struct {
27 | Identifier string
28 | Identifiers []string // Multiple identifiers for vector assignment
29 | Indexes Value // Should be convertible to an Index vector
30 | Modifier Function
31 | }
32 |
33 | func (as *Assignment) Copy() Value {
34 | r := Assignment{
35 | Identifier: as.Identifier,
36 | }
37 | if as.Identifiers != nil {
38 | r.Identifiers = make([]string, len(as.Identifiers))
39 | copy(r.Identifiers, as.Identifiers)
40 | }
41 | if as.Indexes != nil {
42 | r.Indexes = as.Indexes.Copy()
43 | }
44 | if as.Modifier != nil {
45 | r.Modifier = as.Modifier
46 | }
47 | return &r
48 | }
49 |
50 | func (as *Assignment) String(f Format) string {
51 | s := ""
52 | if as.Indexes != nil {
53 | s = "indexed/selective "
54 | }
55 | if as.Modifier != nil {
56 | s += "modified "
57 | }
58 | id := as.Identifier
59 | if as.Identifiers != nil {
60 | id = strings.Join(as.Identifiers, " ")
61 | }
62 | return "assignment to " + id
63 | }
64 |
65 | // EvalAssign evalutes the left part of an assignment and
66 | // returns it as an Assignment value.
67 | // It handles indexed, selective and modified assignment.
68 | func evalAssign(a *Apl, e expr, modifier Function) (Value, error) {
69 | as := Assignment{
70 | Modifier: modifier,
71 | }
72 |
73 | // A function assignment can have no selections or modifications.
74 | if f, ok := e.(fnVar); ok {
75 | as.Identifier = string(f)
76 | return &as, nil
77 | }
78 |
79 | // Modified assignment masks the left argument in an assignment expr.
80 | if as, ok := e.(assignment); ok {
81 | e = as.e
82 | }
83 |
84 | // Vector assignment can only contain a vector of numVars.
85 | if ae, ok := e.(array); ok {
86 | as.Identifiers = make([]string, len(ae))
87 | for i, v := range ae {
88 | if nv, ok := v.(numVar); ok {
89 | as.Identifiers[i] = nv.name
90 | } else {
91 | return nil, fmt.Errorf("vector assignment can contain only numVars: %T", v)
92 | }
93 | }
94 | return &as, nil
95 | }
96 |
97 | // The identifier is the right-most argument in the expression.
98 | // The selection function (if present) is the function left to the Identifier.
99 | selection := false
100 | r := e
101 | search:
102 | for {
103 | switch v := r.(type) {
104 | case numVar:
105 | as.Identifier = v.name
106 | break search
107 | case *function:
108 | r = v.right
109 | v.selection = true
110 | selection = true
111 | default:
112 | return nil, fmt.Errorf("unknown type in assignment expression: %T", r)
113 | }
114 | }
115 |
116 | if selection {
117 | if idx, err := e.Eval(a); err != nil {
118 | return nil, err
119 | } else {
120 | as.Indexes = idx
121 | }
122 | }
123 |
124 | return &as, nil
125 | }
126 |
--------------------------------------------------------------------------------
/apl/big/bigfloat/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Alberto Donizetti
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/apl/big/bigfloat/README:
--------------------------------------------------------------------------------
1 | This is a copy of github.com/ALTree/bigfloat
--------------------------------------------------------------------------------
/apl/big/bigfloat/exp.go:
--------------------------------------------------------------------------------
1 | package bigfloat
2 |
3 | import (
4 | "math"
5 | "math/big"
6 | )
7 |
8 | // Exp returns a big.Float representation of exp(z). Precision is
9 | // the same as the one of the argument. The function returns +Inf
10 | // when z = +Inf, and 0 when z = -Inf.
11 | func Exp(z *big.Float) *big.Float {
12 |
13 | // exp(0) == 1
14 | if z.Sign() == 0 {
15 | return big.NewFloat(1).SetPrec(z.Prec())
16 | }
17 |
18 | // Exp(+Inf) = +Inf
19 | if z.IsInf() && z.Sign() > 0 {
20 | return big.NewFloat(math.Inf(+1)).SetPrec(z.Prec())
21 | }
22 |
23 | // Exp(-Inf) = 0
24 | if z.IsInf() && z.Sign() < 0 {
25 | return big.NewFloat(0).SetPrec(z.Prec())
26 | }
27 |
28 | guess := new(big.Float)
29 |
30 | // try to get initial estimate using IEEE-754 math
31 | zf, _ := z.Float64()
32 | if zfs := math.Exp(zf); zfs == math.Inf(+1) || zfs == 0 {
33 | // too big or too small for IEEE-754 math,
34 | // perform argument reduction using
35 | // e^{2z} = (e^z)²
36 | halfZ := new(big.Float).Mul(z, big.NewFloat(0.5))
37 | halfExp := Exp(halfZ.SetPrec(z.Prec() + 64))
38 | return new(big.Float).Mul(halfExp, halfExp).SetPrec(z.Prec())
39 | } else {
40 | // we got a nice IEEE-754 estimate
41 | guess.SetFloat64(zfs)
42 | }
43 |
44 | // f(t)/f'(t) = t*(log(t) - z)
45 | f := func(t *big.Float) *big.Float {
46 | x := new(big.Float)
47 | x.Sub(Log(t), z)
48 | return x.Mul(x, t)
49 | }
50 |
51 | x := newton(f, guess, z.Prec())
52 |
53 | return x
54 | }
55 |
--------------------------------------------------------------------------------
/apl/big/bigfloat/log.go:
--------------------------------------------------------------------------------
1 | package bigfloat
2 |
3 | import (
4 | "math"
5 | "math/big"
6 | )
7 |
8 | // Log returns a big.Float representation of the natural logarithm of
9 | // z. Precision is the same as the one of the argument. The function
10 | // panics if z is negative, returns -Inf when z = 0, and +Inf when z =
11 | // +Inf
12 | func Log(z *big.Float) *big.Float {
13 |
14 | // panic on negative z
15 | if z.Sign() == -1 {
16 | panic("Log: argument is negative")
17 | }
18 |
19 | // Log(0) = -Inf
20 | if z.Sign() == 0 {
21 | return big.NewFloat(math.Inf(-1)).SetPrec(z.Prec())
22 | }
23 |
24 | prec := z.Prec() + 64 // guard digits
25 |
26 | one := big.NewFloat(1).SetPrec(prec)
27 | two := big.NewFloat(2).SetPrec(prec)
28 | four := big.NewFloat(4).SetPrec(prec)
29 |
30 | // Log(1) = 0
31 | if z.Cmp(one) == 0 {
32 | return big.NewFloat(0).SetPrec(z.Prec())
33 | }
34 |
35 | // Log(+Inf) = +Inf
36 | if z.IsInf() {
37 | return big.NewFloat(math.Inf(+1)).SetPrec(z.Prec())
38 | }
39 |
40 | x := new(big.Float).SetPrec(prec)
41 |
42 | // if 0 < z < 1 we compute log(z) as -log(1/z)
43 | var neg bool
44 | if z.Cmp(one) < 0 {
45 | x.Quo(one, z)
46 | neg = true
47 | } else {
48 | x.Set(z)
49 | }
50 |
51 | // We scale up x until x >= 2**(prec/2), and then we'll be allowed
52 | // to use the AGM formula for Log(x).
53 | //
54 | // Double x until the condition is met, and keep track of the
55 | // number of doubling we did (needed to scale back later).
56 |
57 | lim := new(big.Float)
58 | lim.SetMantExp(two, int(prec/2))
59 |
60 | k := 0
61 | for x.Cmp(lim) < 0 {
62 | x.Mul(x, x)
63 | k++
64 | }
65 |
66 | // Compute the natural log of x using the fact that
67 | // log(x) = π / (2 * AGM(1, 4/x))
68 | // if
69 | // x >= 2**(prec/2),
70 | // where prec is the desired precision (in bits)
71 | pi := pi(prec)
72 | agm := agm(one, x.Quo(four, x)) // agm = AGM(1, 4/x)
73 |
74 | x.Quo(pi, x.Mul(two, agm)) // reuse x, we don't need it
75 |
76 | if neg {
77 | x.Neg(x)
78 | }
79 |
80 | // scale the result back multiplying by 2**-k
81 | // reuse lim to reduce allocations.
82 | x.Mul(x, lim.SetMantExp(one, -k))
83 |
84 | return x.SetPrec(z.Prec())
85 | }
86 |
--------------------------------------------------------------------------------
/apl/big/bigfloat/misc.go:
--------------------------------------------------------------------------------
1 | package bigfloat
2 |
3 | import "math/big"
4 |
5 | // agm returns the arithmetic-geometric mean of a and b.
6 | // a and b must have the same precision.
7 | func agm(a, b *big.Float) *big.Float {
8 |
9 | if a.Prec() != b.Prec() {
10 | panic("agm: different precisions")
11 | }
12 |
13 | prec := a.Prec()
14 |
15 | // do not overwrite a and b
16 | a2 := new(big.Float).Copy(a).SetPrec(prec + 64)
17 | b2 := new(big.Float).Copy(b).SetPrec(prec + 64)
18 |
19 | if a2.Cmp(b2) == -1 {
20 | a2, b2 = b2, a2
21 | }
22 | // a2 >= b2
23 |
24 | // set lim to 2**(-prec)
25 | lim := new(big.Float)
26 | lim.SetMantExp(big.NewFloat(1).SetPrec(prec+64), -int(prec+1))
27 |
28 | half := big.NewFloat(0.5)
29 | t := new(big.Float)
30 |
31 | for t.Sub(a2, b2).Cmp(lim) != -1 {
32 | t.Copy(a2)
33 | a2.Add(a2, b2).Mul(a2, half)
34 | b2 = Sqrt(b2.Mul(b2, t))
35 | }
36 |
37 | return a2.SetPrec(prec)
38 | }
39 |
40 | var piCache *big.Float
41 | var piCachePrec uint
42 | var enablePiCache bool = true
43 |
44 | func init() {
45 | if !enablePiCache {
46 | return
47 | }
48 |
49 | piCache, _, _ = new(big.Float).SetPrec(1024).Parse("3."+
50 | "14159265358979323846264338327950288419716939937510"+
51 | "58209749445923078164062862089986280348253421170679"+
52 | "82148086513282306647093844609550582231725359408128"+
53 | "48111745028410270193852110555964462294895493038196"+
54 | "44288109756659334461284756482337867831652712019091"+
55 | "45648566923460348610454326648213393607260249141273"+
56 | "72458700660631558817488152092096282925409171536444", 10)
57 |
58 | piCachePrec = 1024
59 | }
60 |
61 | // pi returns pi to prec bits of precision
62 | func pi(prec uint) *big.Float {
63 |
64 | if prec <= piCachePrec && enablePiCache {
65 | return new(big.Float).Copy(piCache).SetPrec(prec)
66 | }
67 |
68 | // Following R. P. Brent, Multiple-precision zero-finding
69 | // methods and the complexity of elementary function evaluation,
70 | // in Analytic Computational Complexity, Academic Press,
71 | // New York, 1975, Section 8.
72 |
73 | half := big.NewFloat(0.5)
74 | two := big.NewFloat(2).SetPrec(prec + 64)
75 |
76 | // initialization
77 | a := big.NewFloat(1).SetPrec(prec + 64) // a = 1
78 | b := new(big.Float).Mul(Sqrt(two), half) // b = 1/√2
79 | t := big.NewFloat(0.25).SetPrec(prec + 64) // t = 1/4
80 | x := big.NewFloat(1).SetPrec(prec + 64) // x = 1
81 |
82 | // limit is 2**(-prec)
83 | lim := new(big.Float)
84 | lim.SetMantExp(big.NewFloat(1).SetPrec(prec+64), -int(prec+1))
85 |
86 | // temp variables
87 | y := new(big.Float)
88 | for y.Sub(a, b).Cmp(lim) != -1 { // assume a > b
89 | y.Copy(a)
90 | a.Add(a, b).Mul(a, half) // a = (a+b)/2
91 | b = Sqrt(b.Mul(b, y)) // b = √(ab)
92 |
93 | y.Sub(a, y) // y = a - y
94 | y.Mul(y, y).Mul(y, x) // y = x(a-y)²
95 | t.Sub(t, y) // t = t - x(a-y)²
96 | x.Mul(x, two) // x = 2x
97 | }
98 |
99 | a.Mul(a, a).Quo(a, t) // π = a² / t
100 | a.SetPrec(prec)
101 |
102 | if enablePiCache {
103 | piCache.Copy(a)
104 | piCachePrec = prec
105 | }
106 |
107 | return a
108 | }
109 |
110 | // returns an approximate (to precision dPrec) solution to
111 | // f(t) = 0
112 | // using the Newton Method.
113 | // fOverDf needs to be a fuction returning f(t)/f'(t).
114 | // t must not be changed by fOverDf.
115 | // guess is the initial guess (and it's not preserved).
116 | func newton(fOverDf func(z *big.Float) *big.Float, guess *big.Float, dPrec uint) *big.Float {
117 |
118 | prec, guard := guess.Prec(), uint(64)
119 | guess.SetPrec(prec + guard)
120 |
121 | for prec < 2*dPrec {
122 | guess.Sub(guess, fOverDf(guess))
123 | prec *= 2
124 | guess.SetPrec(prec + guard)
125 | }
126 |
127 | return guess.SetPrec(dPrec)
128 | }
129 |
--------------------------------------------------------------------------------
/apl/big/bigfloat/pow.go:
--------------------------------------------------------------------------------
1 | package bigfloat
2 |
3 | import "math/big"
4 |
5 | // Pow returns a big.Float representation of z**w. Precision is the same as the one
6 | // of the first argument. The function panics when z is negative.
7 | func Pow(z *big.Float, w *big.Float) *big.Float {
8 |
9 | if z.Sign() < 0 {
10 | panic("Pow: negative base")
11 | }
12 |
13 | // Pow(z, 0) = 1.0
14 | if w.Sign() == 0 {
15 | return big.NewFloat(1).SetPrec(z.Prec())
16 | }
17 |
18 | // Pow(z, 1) = z
19 | // Pow(+Inf, n) = +Inf
20 | if w.Cmp(big.NewFloat(1)) == 0 || z.IsInf() {
21 | return new(big.Float).Copy(z)
22 | }
23 |
24 | // Pow(z, -w) = 1 / Pow(z, w)
25 | if w.Sign() < 0 {
26 | x := new(big.Float)
27 | zExt := new(big.Float).Copy(z).SetPrec(z.Prec() + 64)
28 | wNeg := new(big.Float).Neg(w)
29 | return x.Quo(big.NewFloat(1), Pow(zExt, wNeg)).SetPrec(z.Prec())
30 | }
31 |
32 | // w integer fast path (disabled because introduces rounding
33 | // errors)
34 | if false && w.IsInt() {
35 | wi, _ := w.Int64()
36 | return powInt(z, int(wi))
37 | }
38 |
39 | // compute w**z as exp(z log(w))
40 | x := new(big.Float).SetPrec(z.Prec() + 64)
41 | logZ := Log(new(big.Float).Copy(z).SetPrec(z.Prec() + 64))
42 | x.Mul(w, logZ)
43 | x = Exp(x)
44 | return x.SetPrec(z.Prec())
45 |
46 | }
47 |
48 | // fast path for z**w when w is an integer
49 | func powInt(z *big.Float, w int) *big.Float {
50 |
51 | // get mantissa and exponent of z
52 | mant := new(big.Float)
53 | exp := z.MantExp(mant)
54 |
55 | // result's exponent
56 | exp = exp * w
57 |
58 | // result's mantissa
59 | x := big.NewFloat(1).SetPrec(z.Prec())
60 |
61 | // Classic right-to-left binary exponentiation
62 | for w > 0 {
63 | if w%2 == 1 {
64 | x.Mul(x, mant)
65 | }
66 | w >>= 1
67 | mant.Mul(mant, mant)
68 | }
69 |
70 | return new(big.Float).SetMantExp(x, exp)
71 | }
72 |
--------------------------------------------------------------------------------
/apl/big/bigfloat/sqrt.go:
--------------------------------------------------------------------------------
1 | // Package bigfloat provides the implementation of a few additional operations for the
2 | // standard library big.Float type.
3 | package bigfloat
4 |
5 | import (
6 | "math"
7 | "math/big"
8 | )
9 |
10 | // Sqrt returns a big.Float representation of the square root of
11 | // z. Precision is the same as the one of the argument. The function
12 | // panics if z is negative, returns ±0 when z = ±0, and +Inf when z =
13 | // +Inf.
14 | func Sqrt(z *big.Float) *big.Float {
15 |
16 | // panic on negative z
17 | if z.Sign() == -1 {
18 | panic("Sqrt: argument is negative")
19 | }
20 |
21 | // √±0 = ±0
22 | if z.Sign() == 0 {
23 | return big.NewFloat(float64(z.Sign()))
24 | }
25 |
26 | // √+Inf = +Inf
27 | if z.IsInf() {
28 | return big.NewFloat(math.Inf(+1))
29 | }
30 |
31 | // Compute √(a·2**b) as
32 | // √(a)·2**b/2 if b is even
33 | // √(2a)·2**b/2 if b > 0 is odd
34 | // √(0.5a)·2**b/2 if b < 0 is odd
35 | //
36 | // The difference in the odd exponent case is due to the fact that
37 | // exp/2 is rounded in different directions when exp is negative.
38 | mant := new(big.Float)
39 | exp := z.MantExp(mant)
40 | switch exp % 2 {
41 | case 1:
42 | mant.Mul(big.NewFloat(2), mant)
43 | case -1:
44 | mant.Mul(big.NewFloat(0.5), mant)
45 | }
46 |
47 | // Solving x² - z = 0 directly requires a Quo call, but it's
48 | // faster for small precisions.
49 | //
50 | // Solving 1/x² - z = 0 avoids the Quo call and is much faster for
51 | // high precisions.
52 | //
53 | // Use sqrtDirect for prec <= 128 and sqrtInverse for prec > 128.
54 | var x *big.Float
55 | if z.Prec() <= 128 {
56 | x = sqrtDirect(mant)
57 | } else {
58 | x = sqrtInverse(mant)
59 | }
60 |
61 | // re-attach the exponent and return
62 | return x.SetMantExp(x, exp/2)
63 |
64 | }
65 |
66 | // compute √z using newton to solve
67 | // t² - z = 0 for t
68 | func sqrtDirect(z *big.Float) *big.Float {
69 | // f(t)/f'(t) = 0.5(t² - z)/t
70 | half := big.NewFloat(0.5)
71 | f := func(t *big.Float) *big.Float {
72 | x := new(big.Float).Mul(t, t) // x = t²
73 | x.Sub(x, z) // x = t² - z
74 | x.Mul(half, x) // x = 0.5(t² - z)
75 | return x.Quo(x, t) // return x = 0.5(t² - z)/t
76 | }
77 |
78 | // initial guess
79 | zf, _ := z.Float64()
80 | guess := big.NewFloat(math.Sqrt(zf))
81 |
82 | return newton(f, guess, z.Prec())
83 | }
84 |
85 | // compute √z using newton to solve
86 | // 1/t² - z = 0 for x and then inverting.
87 | func sqrtInverse(z *big.Float) *big.Float {
88 | // f(t)/f'(t) = -0.5t(1 - zt²)
89 | nhalf := big.NewFloat(-0.5)
90 | one := big.NewFloat(1)
91 | f := func(t *big.Float) *big.Float {
92 | u := new(big.Float)
93 | u.Mul(t, t) // u = t²
94 | u.Mul(u, z) // u = zt²
95 | u.Sub(one, u) // u = 1 - zt²
96 | u.Mul(u, nhalf) // u = -0.5(1 - zt²)
97 | return new(big.Float).Mul(t, u) // x = -0.5t(1 - zt²)
98 | }
99 |
100 | // initial guess
101 | zf, _ := z.Float64()
102 | guess := big.NewFloat(1 / math.Sqrt(zf))
103 |
104 | // There's another operation after newton,
105 | // so we need to force it to return at least
106 | // a few guard digits. Use 32.
107 | x := newton(f, guess, z.Prec()+32)
108 | return x.Mul(z, x).SetPrec(z.Prec())
109 | }
110 |
--------------------------------------------------------------------------------
/apl/big/register.go:
--------------------------------------------------------------------------------
1 | package big
2 |
3 | import (
4 | "fmt"
5 | "math/big"
6 | "reflect"
7 |
8 | "github.com/ktye/iv/apl"
9 | "github.com/ktye/iv/apl/numbers"
10 | )
11 |
12 | func Register(a *apl.Apl, name string) {
13 | pkg := map[string]apl.Value{
14 | "set": apl.ToFunction(settower),
15 | }
16 | if name == "" {
17 | name = "big"
18 | }
19 | a.RegisterPackage(name, pkg)
20 | }
21 |
22 | // SetBigTower sets the numerical tower to Int->Rat.
23 | func SetBigTower(a *apl.Apl) {
24 | m := make(map[reflect.Type]*apl.Numeric)
25 | m[reflect.TypeOf(Int{})] = &apl.Numeric{
26 | Class: 0,
27 | Parse: ParseInt,
28 | Uptype: intToRat,
29 | }
30 | m[reflect.TypeOf(Rat{})] = &apl.Numeric{
31 | Class: 1,
32 | Parse: ParseRat,
33 | Uptype: func(n apl.Number) (apl.Number, bool) { return n, false },
34 | }
35 | t := apl.Tower{
36 | Numbers: m,
37 | Import: func(n apl.Number) apl.Number {
38 | if b, ok := n.(apl.Bool); ok {
39 | if b {
40 | return Int{big.NewInt(1)}
41 | }
42 | return Int{big.NewInt(0)}
43 | } else if n, ok := n.(apl.Int); ok {
44 | return Int{big.NewInt(int64(n))}
45 | }
46 | return n
47 | },
48 | Uniform: func(v []apl.Value) (apl.Value, bool) { return nil, false },
49 | }
50 | if err := a.SetTower(t); err != nil {
51 | panic(err)
52 | }
53 | }
54 |
55 | // SetPreciseTower sets the numerical tower to Float->Complex with the given precision.
56 | func SetPreciseTower(a *apl.Apl, prec uint) {
57 | m := make(map[reflect.Type]*apl.Numeric)
58 | m[reflect.TypeOf(Float{})] = &apl.Numeric{
59 | Class: 0,
60 | Parse: func(s string) (apl.Number, bool) { return ParseFloat(s, prec) },
61 | Uptype: floatToComplex,
62 | }
63 | m[reflect.TypeOf(Complex{})] = &apl.Numeric{
64 | Class: 1,
65 | Parse: func(s string) (apl.Number, bool) { return ParseComplex(s, prec) },
66 | Uptype: func(n apl.Number) (apl.Number, bool) { return n, false },
67 | }
68 | t := apl.Tower{
69 | Numbers: m,
70 | Import: func(n apl.Number) apl.Number {
71 | if b, ok := n.(apl.Bool); ok {
72 | if b {
73 | return Float{big.NewFloat(1).SetPrec(prec)}
74 | }
75 | return Float{big.NewFloat(0).SetPrec(prec)}
76 | } else if n, ok := n.(apl.Int); ok {
77 | f := Float{big.NewFloat(float64(n)).SetPrec(prec)}
78 | return f
79 | }
80 | return n
81 | },
82 | Uniform: func(v []apl.Value) (apl.Value, bool) { return nil, false },
83 | }
84 | if err := a.SetTower(t); err != nil {
85 | panic(err)
86 | }
87 | }
88 |
89 | func settower(a *apl.Apl, L, R apl.Value) (apl.Value, error) {
90 | n, ok := R.(apl.Number)
91 | if ok == false {
92 | return nil, fmt.Errorf("set needs a number (0, 1, 256...)")
93 | }
94 | idx, ok := n.ToIndex()
95 | if ok == false {
96 | return nil, fmt.Errorf("set needs an integer")
97 | }
98 | if idx < 0 {
99 | return nil, fmt.Errorf("set: precision must be positive")
100 | } else if idx == 0 {
101 | numbers.Register(a)
102 | } else if idx == 1 {
103 | SetBigTower(a)
104 | } else {
105 | SetPreciseTower(a, uint(idx))
106 | }
107 | return R, nil
108 | }
109 |
110 | func getformat(f apl.Format, num apl.Value) (string, bool) {
111 | if f.Fmt == nil {
112 | return "", false
113 | }
114 | s := f.Fmt[reflect.TypeOf(num)]
115 | if len(s) > 0 && s[0] == '-' {
116 | return s[1:], true
117 | }
118 | return s, false
119 | }
120 |
--------------------------------------------------------------------------------
/apl/bool.go:
--------------------------------------------------------------------------------
1 | package apl
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | type Bool bool
8 |
9 | func (b Bool) String(f Format) string {
10 | if f.PP < 0 {
11 | if b {
12 | return "1b"
13 | }
14 | return "0b"
15 | }
16 | if b {
17 | return "1"
18 | }
19 | return "0"
20 | }
21 | func (b Bool) Copy() Value { return b }
22 |
23 | func (i Bool) Less(v Value) (Bool, bool) {
24 | j, ok := v.(Bool)
25 | if ok == false {
26 | return false, false
27 | }
28 | return i == false && j == true, true
29 | }
30 |
31 | func (i Bool) ToIndex() (int, bool) {
32 | if i {
33 | return 1, true
34 | }
35 | return 0, true
36 | }
37 |
38 | // BoolArray is a uniform array of type bool.
39 | type BoolArray struct {
40 | Dims []int
41 | Bools []bool
42 | }
43 |
44 | func (b BoolArray) String(f Format) string {
45 | return ArrayString(f, b)
46 | }
47 |
48 | func (b BoolArray) Copy() Value {
49 | r := BoolArray{Dims: CopyShape(b), Bools: make([]bool, len(b.Bools))}
50 | copy(r.Bools, b.Bools)
51 | return r
52 | }
53 |
54 | func (b BoolArray) At(i int) Value {
55 | return Bool(b.Bools[i])
56 | }
57 |
58 | func (b BoolArray) Shape() []int {
59 | return b.Dims
60 | }
61 |
62 | func (b BoolArray) Size() int {
63 | return len(b.Bools)
64 | }
65 |
66 | func (b BoolArray) Zero() Value {
67 | return Bool(false)
68 | }
69 |
70 | func (b BoolArray) Set(i int, v Value) error {
71 | if i < 0 || i > len(b.Bools) {
72 | return fmt.Errorf("index out of range")
73 | }
74 | if c, ok := v.(Bool); ok {
75 | b.Bools[i] = bool(c)
76 | return nil
77 | }
78 | return fmt.Errorf("cannot assign %T to BoolArray", v)
79 | }
80 |
81 | func (s BoolArray) Make(shape []int) Uniform {
82 | return BoolArray{
83 | Dims: shape,
84 | Bools: make([]bool, Prod(shape)),
85 | }
86 | }
87 |
88 | func makeBoolArray(v []Value) BoolArray {
89 | b := make([]bool, len(v))
90 | for i, e := range v {
91 | b[i] = bool(e.(Bool))
92 | }
93 | return BoolArray{
94 | Dims: []int{len(v)},
95 | Bools: b,
96 | }
97 | }
98 |
99 | func (b BoolArray) Reshape(shape []int) Value {
100 | res := BoolArray{
101 | Dims: shape,
102 | Bools: make([]bool, Prod(shape)),
103 | }
104 | k := 0
105 | for i := range res.Bools {
106 | res.Bools[i] = b.Bools[k]
107 | k++
108 | if k == len(b.Bools) {
109 | k = 0
110 | }
111 | }
112 | return res
113 | }
114 |
--------------------------------------------------------------------------------
/apl/domain.go:
--------------------------------------------------------------------------------
1 | package apl
2 |
3 | // Domain represents the application domain of a function or operator.
4 | // Calling To converts the left and right input arguments and returns true,
5 | // if the types or values are compatible with the function or operator.
6 | // Otherwise they return false.
7 | // If To returns false, it must return the original input values.
8 | // This is important for a Not combination to work properly.
9 | // String is used in documentation for a concise type/value description.
10 | //
11 | // Standard Domain implementations and universal combination functions
12 | // are in the domain package.
13 | type Domain interface {
14 | To(*Apl, Value, Value) (Value, Value, bool)
15 | String(Format) string
16 | }
17 |
--------------------------------------------------------------------------------
/apl/domain/channel.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import "github.com/ktye/iv/apl"
4 |
5 | // IsChannel tests if the value is a channel.
6 | func IsChannel(child SingleDomain) SingleDomain {
7 | return channel{child}
8 | }
9 |
10 | type channel struct {
11 | child SingleDomain
12 | }
13 |
14 | func (c channel) To(a *apl.Apl, V apl.Value) (apl.Value, bool) {
15 | if _, ok := V.(apl.Channel); ok {
16 | return propagate(a, V, c.child)
17 | }
18 | return V, false
19 | }
20 |
21 | func (c channel) String(f apl.Format) string {
22 | name := "channel"
23 | if c.child == nil {
24 | return name
25 | }
26 | return name + " " + c.child.String(f)
27 | }
28 |
--------------------------------------------------------------------------------
/apl/domain/compare.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | /* TODO move to numbers package
4 | import (
5 | "errors"
6 | "fmt"
7 | "math"
8 | "math/cmplx"
9 | "reflect"
10 |
11 | "github.com/ktye/iv/apl"
12 | )
13 |
14 | var ErrNaN error
15 |
16 | type ErrInf int
17 |
18 | func (e ErrInf) Error() string {
19 | if e < 0 {
20 | return "-Inf"
21 | } else {
22 | return "Inf"
23 | }
24 | }
25 |
26 | func init() {
27 | ErrNaN = errors.New("NaN")
28 | }
29 |
30 | // IsFloatErr returns ErrInf(1), ErrInf(-1) or ErrNaN, if the
31 | // Float or Complex argument is not finite.
32 | func IsFloatErr(f apl.Value) error {
33 | switch t := f.(type) {
34 | case apl.Float:
35 | v := float64(t)
36 | if math.IsInf(v, 1) {
37 | return ErrInf(1)
38 | } else if math.IsInf(v, -1) {
39 | return ErrInf(-1)
40 | } else if math.IsNaN(v) {
41 | return ErrNaN
42 | }
43 | return nil
44 | case apl.Complex:
45 | c := complex128(t)
46 | if cmplx.IsInf(c) {
47 | return ErrInf(1)
48 | } else if cmplx.IsNaN(c) {
49 | return ErrNaN
50 | }
51 | return nil
52 | default:
53 | return fmt.Errorf("IsFloatErr expected Float or Complex: %T", f)
54 | }
55 | }
56 |
57 | // CompareScalars compares and b and returns
58 | // a == b, a < b.
59 | // Non-numeric values are checked for equality.
60 | // If they have the same type and are a Lesser, they
61 | // are also checke for a < b.
62 | // Numeric types are converted to the same type before comparison.
63 | func CompareScalars(a, b apl.Value) (bool, bool, error) {
64 | // Equal types and equal.
65 | if a == b {
66 | return true, false, nil
67 | }
68 | // EqualTypes have lesser.
69 | if reflect.TypeOf(a) == reflect.TypeOf(b) {
70 | if al, ok := a.(Lesser); ok {
71 | le := al.Less(b)
72 | return false, le, nil
73 | }
74 | }
75 | // Convert both to numbers.
76 | num := number{nil, true}
77 | an, aok := num.To(nil, a)
78 | bn, bok := num.To(nil, b)
79 | if aok == false || bok == false {
80 | return false, false, fmt.Errorf("cannot compare %T with %T", a, b)
81 | }
82 |
83 | an, bn = MustPromote(an, bn)
84 | switch an := an.(type) {
85 | case apl.Bool:
86 | if an == bn.(apl.Bool) {
87 | return true, false, nil
88 | } else if bool(an) == false && bool(bn.(apl.Bool)) == true {
89 | return false, true, nil
90 | }
91 | return false, false, nil
92 | case apl.Int:
93 | i := bn.(apl.Int)
94 | return an == i, an < i, nil
95 | case apl.Float:
96 | f := bn.(apl.Float)
97 | if math.IsNaN(float64(f)) || math.IsNaN(float64(an)) {
98 | return false, false, ErrNaN
99 | }
100 | return an == bn, an < f, nil
101 | case apl.Complex:
102 | c := bn.(apl.Complex)
103 | if cmplx.IsNaN(complex128(an)) || cmplx.IsNaN(complex128(c)) {
104 | return false, false, ErrNaN
105 | }
106 | if an == c {
107 | return true, false, nil
108 | }
109 | if imag(an) != 0 || imag(c) != 0 {
110 | return false, false, fmt.Errorf("cannot compare complex numbers")
111 | }
112 | return real(an) == real(c), real(an) < real(c), nil
113 | }
114 | return false, false, fmt.Errorf("cannot compare %T with %T", a, b)
115 | }
116 |
117 | // Lesser is used for scalar comparison.
118 | // Custom types may implement it.
119 | // It must be called only after checking that the types are identical.
120 | // It returns true, if the receiver is less that b.
121 | type Lesser interface {
122 | Less(b interface{}) bool
123 | }
124 |
125 | // MustPromote returns two numbers of the same type, by possibly uptyping one of them.
126 | // mustPromote can only be called with input types apl.Bool, apl.Int, apl.Float and apl.Complex.
127 | func MustPromote(L, R apl.Value) (apl.Value, apl.Value) {
128 | to := ToBool(nil)
129 | if _, ok := L.(apl.Complex); ok {
130 | to = ToComplex(nil)
131 | } else if _, ok := R.(apl.Complex); ok {
132 | to = ToComplex(nil)
133 | } else if _, ok := L.(apl.Float); ok {
134 | to = ToFloat(nil)
135 | } else if _, ok := R.(apl.Float); ok {
136 | to = ToFloat(nil)
137 | } else if _, ok := L.(apl.Int); ok {
138 | to = ToInt(nil)
139 | } else if _, ok := R.(apl.Int); ok {
140 | to = ToInt(nil)
141 | }
142 | L, _ = to.To(nil, L)
143 | R, _ = to.To(nil, R)
144 | return L, R
145 | }
146 | */
147 |
--------------------------------------------------------------------------------
/apl/domain/functions.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import (
4 | "github.com/ktye/iv/apl"
5 | )
6 |
7 | func Function(child SingleDomain) SingleDomain {
8 | return function{child}
9 | }
10 |
11 | type function struct{ child SingleDomain }
12 |
13 | func (f function) To(a *apl.Apl, V apl.Value) (apl.Value, bool) {
14 | if _, ok := V.(apl.Function); ok == false {
15 | return V, false
16 | }
17 | if f.child == nil {
18 | return V, true
19 | }
20 | if v, ok := f.child.To(a, V); ok {
21 | return v, true
22 | }
23 | return V, false
24 | }
25 | func (f function) String(af apl.Format) string {
26 | if f.child == nil {
27 | return "function"
28 | }
29 | return "function " + f.child.String(af)
30 | }
31 |
32 | func IsPrimitive(p string) SingleDomain {
33 | return primitive(p)
34 | }
35 |
36 | type primitive string
37 |
38 | func (p primitive) To(a *apl.Apl, V apl.Value) (apl.Value, bool) {
39 | if pf, ok := V.(apl.Primitive); ok && string(pf) == string(p) {
40 | return V, true
41 | }
42 | return V, false
43 | }
44 | func (p primitive) String(f apl.Format) string {
45 | return string(p)
46 | }
47 |
--------------------------------------------------------------------------------
/apl/domain/identifier.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import "github.com/ktye/iv/apl"
4 |
5 | func IsIdentifier(child SingleDomain) SingleDomain {
6 | return identifier{child}
7 | }
8 |
9 | type identifier struct{ child SingleDomain }
10 |
11 | func (id identifier) To(a *apl.Apl, V apl.Value) (apl.Value, bool) {
12 | _, ok := V.(apl.Identifier)
13 | if ok == false {
14 | return V, false
15 | }
16 | return propagate(a, V, id.child)
17 | }
18 |
19 | func (id identifier) String(f apl.Format) string {
20 | name := "identfier"
21 | if id.child == nil {
22 | return name
23 | }
24 | return name + " " + id.child.String(f)
25 | }
26 |
--------------------------------------------------------------------------------
/apl/domain/image.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import (
4 | img "image"
5 | "image/color"
6 |
7 | "github.com/ktye/iv/apl"
8 | )
9 |
10 | // ToImage can convert an int array to an image.
11 | // It also accepts a list with two int arrags: the image data as indexes into a palette, and the palette itself.
12 | func ToImage(child SingleDomain) SingleDomain {
13 | return image{child, true}
14 | }
15 |
16 | func IsImage(child SingleDomain) SingleDomain {
17 | return image{child, false}
18 | }
19 |
20 | type image struct {
21 | child SingleDomain
22 | conv bool
23 | }
24 |
25 | func (i image) String(f apl.Format) string {
26 | name := "image"
27 | if i.conv {
28 | name = "toimage"
29 | }
30 | if i.child == nil {
31 | return name
32 | }
33 | return name + " " + i.child.String(f)
34 | }
35 |
36 | func (im image) To(a *apl.Apl, V apl.Value) (apl.Value, bool) {
37 | _, ok := V.(apl.Image)
38 | if im.conv == false && ok == false {
39 | return V, false
40 | } else if im.conv == false && ok {
41 | return propagate(a, V, im.child)
42 | } else if ok {
43 | return propagate(a, V, im.child)
44 | }
45 |
46 | var pal []int
47 | ia, ok := indexarray{nil, true}.To(a, V)
48 | if ok == false {
49 | if l, ok := V.(apl.List); ok == false || len(l) != 2 {
50 | return V, false
51 | } else {
52 | idx, ok := indexarray{nil, true}.To(a, l[0])
53 | pa, pok := indexarray{nil, true}.To(a, l[1])
54 | if ok == false || pok == false {
55 | return V, false
56 | }
57 | ia = idx
58 | pal = pa.(apl.IntArray).Ints
59 | }
60 | return V, false
61 | }
62 |
63 | if m, ok := ints2image(ia.(apl.IntArray), pal); ok {
64 | return propagate(a, m, im.child)
65 | } else {
66 | return V, false
67 | }
68 | }
69 |
70 | func ints2image(ia apl.IntArray, pal []int) (apl.Image, bool) {
71 | res := apl.Image{}
72 | shape := ia.Shape()
73 | if len(shape) != 2 {
74 | return res, false
75 | }
76 |
77 | p := make(color.Palette, len(pal))
78 | for i, n := range pal {
79 | p[i] = toColor(n)
80 | }
81 |
82 | var r img.Rectangle
83 | r.Max.X = shape[1]
84 | r.Max.Y = shape[0]
85 |
86 | var pm *img.Paletted
87 | var im *img.RGBA
88 | if pal == nil {
89 | im = img.NewRGBA(r)
90 | } else {
91 | pm = img.NewPaletted(r, p)
92 | }
93 | i := 0
94 | for y := 0; y < r.Max.Y; y++ {
95 | for x := 0; x < r.Max.X; x++ {
96 | c := ia.Ints[i]
97 | if pal == nil {
98 | im.Set(x, y, toColor(c))
99 | } else if c >= len(pal) {
100 | pm.SetColorIndex(x, y, 0)
101 | } else {
102 | pm.SetColorIndex(x, y, uint8(c))
103 | }
104 | i++
105 | }
106 | }
107 | if pal == nil {
108 | res.Image = im
109 | } else {
110 | res.Image = pm
111 | }
112 | res.Dims = apl.CopyShape(ia)
113 | return res, true
114 | }
115 |
116 | // toColor is copied from apl/image.go
117 | func toColor(i int) color.Color {
118 | u := uint32(i)
119 | return color.RGBA{uint8(u & 0xFF0000 >> 16), uint8(u & 0xFF00 >> 8), uint8(u & 0xFF), ^uint8(u >> 24)}
120 | }
121 |
--------------------------------------------------------------------------------
/apl/domain/list.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import "github.com/ktye/iv/apl"
4 |
5 | // IsList tests if the value is a list.
6 | func IsList(child SingleDomain) SingleDomain {
7 | return list{child, false}
8 | }
9 |
10 | func ToList(child SingleDomain) SingleDomain {
11 | return list{child, true}
12 | }
13 |
14 | type list struct {
15 | child SingleDomain
16 | conv bool
17 | }
18 |
19 | func (v list) To(a *apl.Apl, V apl.Value) (apl.Value, bool) {
20 | _, ok := V.(apl.List)
21 | if v.conv == false && ok == false {
22 | return V, false
23 | } else if ok == true {
24 | return propagate(a, V, v.child)
25 | }
26 |
27 | // enlist
28 | return propagate(a, apl.List{V}, v.child)
29 | }
30 | func (v list) String(f apl.Format) string {
31 | name := "list"
32 | if v.conv {
33 | name = "tolist"
34 | }
35 | if v.child == nil {
36 | return name
37 | }
38 | return name + " " + v.child.String(f)
39 | }
40 |
--------------------------------------------------------------------------------
/apl/domain/numeric.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import "github.com/ktye/iv/apl"
4 |
5 | // ToNumber accepts scalars and single size arrays.
6 | // and converts them to scalars if they contain one of the types:
7 | // apl.Bool, apl.Int, apl.Float or apl.Complex.
8 | func ToNumber(child SingleDomain) SingleDomain {
9 | return number{child, true}
10 | }
11 |
12 | // IsNumber accepts scalars if they contain of of the types:
13 | // apl.Bool, apl.Int, apl.Float or apl.Complex
14 | func IsNumber(child SingleDomain) SingleDomain {
15 | return number{child, false}
16 | }
17 |
18 | type number struct {
19 | child SingleDomain
20 | convert bool
21 | }
22 |
23 | func (n number) To(a *apl.Apl, V apl.Value) (apl.Value, bool) {
24 | if _, ok := V.(apl.Number); ok {
25 | return propagate(a, V, n.child)
26 | }
27 | if n.convert == false {
28 | return V, false
29 | }
30 | if ar, ok := V.(apl.Array); ok {
31 | if n.convert == false {
32 | return V, false
33 | }
34 | if n := ar.Size(); n != 1 {
35 | return V, false
36 | }
37 | v := ar.At(0)
38 | if _, ok := v.(apl.Number); ok {
39 | return propagate(a, v, n.child)
40 | }
41 | }
42 | return V, false
43 | }
44 | func (n number) String(f apl.Format) string {
45 | name := "number"
46 | if n.convert {
47 | name = "tonumber"
48 | }
49 | if n.child == nil {
50 | return name
51 | }
52 | return name + " " + n.child.String(f)
53 | }
54 |
55 | // ToIndex converts the scalar to an Index.
56 | func ToIndex(child SingleDomain) SingleDomain {
57 | return index{child}
58 | }
59 |
60 | type index struct {
61 | child SingleDomain
62 | }
63 |
64 | func (idx index) To(a *apl.Apl, V apl.Value) (apl.Value, bool) {
65 | if b, ok := V.(apl.Bool); ok {
66 | if b == true {
67 | return apl.Int(1), true
68 | }
69 | return apl.Int(0), true
70 | }
71 | if n, ok := V.(apl.Int); ok {
72 | return n, true
73 | }
74 | if n, ok := V.(apl.Number); ok == false {
75 | return V, false
76 | } else {
77 | if i, ok := n.ToIndex(); ok == false {
78 | return V, false
79 | } else {
80 | return propagate(a, apl.Int(i), idx.child)
81 | }
82 | }
83 | }
84 | func (idx index) String(f apl.Format) string {
85 | if idx.child == nil {
86 | return "index"
87 | } else {
88 | return "index " + idx.child.String(f)
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/apl/domain/object.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import "github.com/ktye/iv/apl"
4 |
5 | // IsObject accepts objects
6 | func IsObject(child SingleDomain) SingleDomain {
7 | return objtype{child}
8 | }
9 |
10 | type objtype struct{ child SingleDomain }
11 |
12 | func (s objtype) To(a *apl.Apl, V apl.Value) (apl.Value, bool) {
13 | if _, ok := V.(apl.Object); ok {
14 | return propagate(a, V, s.child)
15 | }
16 | return V, false
17 | }
18 | func (s objtype) String(f apl.Format) string {
19 | if s.child == nil {
20 | return "object"
21 | }
22 | return "object" + " " + s.child.String(f)
23 | }
24 |
25 | // IsTable accepts objects
26 | func IsTable(child SingleDomain) SingleDomain {
27 | return table{child}
28 | }
29 |
30 | type table struct{ child SingleDomain }
31 |
32 | func (s table) To(a *apl.Apl, V apl.Value) (apl.Value, bool) {
33 | if _, ok := V.(apl.Table); ok {
34 | return propagate(a, V, s.child)
35 | }
36 | return V, false
37 | }
38 | func (s table) String(f apl.Format) string {
39 | if s.child == nil {
40 | return "table"
41 | }
42 | return "table" + " " + s.child.String(f)
43 | }
44 |
--------------------------------------------------------------------------------
/apl/domain/operator.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import "github.com/ktye/iv/apl"
4 |
5 | // MonadicOp is used to define a monadic operator.
6 | func MonadicOp(l SingleDomain) apl.Domain {
7 | return monop{l}
8 | }
9 |
10 | type monop struct {
11 | left SingleDomain
12 | }
13 |
14 | func (m monop) To(a *apl.Apl, L, R apl.Value) (apl.Value, apl.Value, bool) {
15 | if m.left == nil {
16 | return L, R, true
17 | }
18 | if v, ok := m.left.To(a, L); ok {
19 | return v, R, true
20 | }
21 | return L, R, false
22 | }
23 | func (m monop) DyadicOp() bool { return false }
24 | func (m monop) String(f apl.Format) string {
25 | if m.left == nil {
26 | return "LO any"
27 | }
28 | return "LO " + m.left.String(f)
29 | }
30 |
31 | // DyadicOp is used to define a dyadic operator.
32 | func DyadicOp(child apl.Domain) apl.Domain {
33 | return dyop{child}
34 | }
35 |
36 | type dyop struct {
37 | child apl.Domain
38 | }
39 |
40 | func (d dyop) To(a *apl.Apl, L, R apl.Value) (apl.Value, apl.Value, bool) {
41 | if L == nil {
42 | return L, R, false
43 | }
44 | if d.child == nil {
45 | return L, R, true
46 | }
47 | return d.child.To(a, L, R)
48 | }
49 | func (d dyop) DyadicOp() bool { return true }
50 | func (d dyop) String(f apl.Format) string {
51 | if d.child == nil {
52 | return "any"
53 | }
54 | return d.child.String(f)
55 | }
56 |
--------------------------------------------------------------------------------
/apl/domain/scalar.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import "github.com/ktye/iv/apl"
4 |
5 | // ToScalar accepts scalars and converts single element arrays to scalars.
6 | func ToScalar(child SingleDomain) SingleDomain {
7 | return scalar{child, true}
8 | }
9 |
10 | // IsScalar accepts values that are not arrays.
11 | func IsScalar(child SingleDomain) SingleDomain {
12 | return scalar{child, false}
13 | }
14 |
15 | type scalar struct {
16 | child SingleDomain
17 | convert bool
18 | }
19 |
20 | func (s scalar) To(a *apl.Apl, V apl.Value) (apl.Value, bool) {
21 | v := V
22 | if ar, ok := V.(apl.Array); ok {
23 | if s.convert == false {
24 | return V, false
25 | }
26 | if n := ar.Size(); n != 1 {
27 | return V, false
28 | }
29 | v = ar.At(0)
30 | }
31 | return propagate(a, v, s.child)
32 | }
33 | func (s scalar) String(f apl.Format) string {
34 | name := "scalar"
35 | if s.convert {
36 | name = "toscalar"
37 | }
38 | if s.child == nil {
39 | return name
40 | }
41 | return name + " " + s.child.String(f)
42 | }
43 |
--------------------------------------------------------------------------------
/apl/domain/string.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import (
4 | "github.com/ktye/iv/apl"
5 | )
6 |
7 | // IsString accepts strings
8 | func IsString(child SingleDomain) SingleDomain {
9 | return stringtype{child}
10 | }
11 |
12 | type stringtype struct{ child SingleDomain }
13 |
14 | func (s stringtype) To(a *apl.Apl, V apl.Value) (apl.Value, bool) {
15 | if v, ok := V.(apl.String); ok {
16 | if s.child == nil {
17 | return v, true
18 | }
19 | return s.child.To(a, v)
20 | }
21 | return V, false
22 | }
23 | func (s stringtype) String(f apl.Format) string {
24 | if s.child == nil {
25 | return "string"
26 | }
27 | return "string" + " " + s.child.String(f)
28 | }
29 |
30 | // IsStringArray accepts uniform.Strings
31 | func IsStringArray(child SingleDomain) SingleDomain {
32 | return stringstype{child, false}
33 | }
34 |
35 | func ToStringArray(child SingleDomain) SingleDomain {
36 | return stringstype{child, true}
37 | }
38 |
39 | type stringstype struct {
40 | child SingleDomain
41 | convert bool
42 | }
43 |
44 | func (s stringstype) To(a *apl.Apl, V apl.Value) (apl.Value, bool) {
45 | if _, ok := V.(apl.StringArray); ok {
46 | return propagate(a, V, s.child)
47 | } else {
48 | if s.convert == false {
49 | return V, false
50 | }
51 | if str, ok := V.(apl.String); ok {
52 | return propagate(a, apl.StringArray{
53 | Dims: []int{1},
54 | Strings: []string{string(str)},
55 | }, s.child)
56 |
57 | } else if ar, ok := V.(apl.Array); ok {
58 | str := make([]string, ar.Size())
59 | for i := range str {
60 | if sv, ok := ar.At(i).(apl.String); ok {
61 | str[i] = string(sv)
62 | } else {
63 | return V, false
64 | }
65 | }
66 | return propagate(a, apl.StringArray{
67 | Dims: apl.CopyShape(ar),
68 | Strings: str,
69 | }, s.child)
70 | } else {
71 | return V, false
72 | }
73 | }
74 | }
75 | func (s stringstype) String(f apl.Format) string {
76 | name := "string array"
77 | if s.convert {
78 | name = "to string array"
79 | }
80 | if s.child == nil {
81 | return name
82 | }
83 | return name + " " + s.child.String(f)
84 | }
85 |
--------------------------------------------------------------------------------
/apl/domain/type.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import (
4 | "reflect"
5 |
6 | "github.com/ktye/iv/apl"
7 | )
8 |
9 | // IsType tests if the value has a concrete type.
10 | func IsType(t reflect.Type, child SingleDomain) SingleDomain {
11 | return typ{t, child, false}
12 | }
13 |
14 | // ToType tries converts the value to the given type.
15 | func ToType(t reflect.Type, child SingleDomain) SingleDomain {
16 | return typ{t, child, true}
17 | }
18 |
19 | type typ struct {
20 | t reflect.Type
21 | child SingleDomain
22 | conv bool
23 | }
24 |
25 | func (t typ) String(f apl.Format) string {
26 | name := "type"
27 | if t.conv {
28 | name = "totype"
29 | }
30 | name += " " + t.t.String()
31 | if t.child == nil {
32 | return name
33 | }
34 | return name + " " + t.child.String(f)
35 | }
36 |
37 | func (t typ) To(a *apl.Apl, V apl.Value) (apl.Value, bool) {
38 | if reflect.TypeOf(V) == t.t {
39 | return propagate(a, V, t.child)
40 | }
41 | if t.conv == false {
42 | return V, false
43 | }
44 | // TODO: The function should also be used by the convert primitive (⌶).
45 | return V, false // TODO type conversions.
46 | }
47 |
--------------------------------------------------------------------------------
/apl/error.go:
--------------------------------------------------------------------------------
1 | package apl
2 |
3 | // Error carries an error value.
4 | // It is used by go routines to signal errors.
5 | // To send err over Channel c, use: c[0]<-Error{e}
6 | type Error struct {
7 | E error
8 | }
9 |
10 | func (e Error) String(f Format) string {
11 | if e.E == nil {
12 | return ""
13 | }
14 | return e.E.Error()
15 | }
16 | func (e Error) Copy() Value { return e }
17 |
--------------------------------------------------------------------------------
/apl/eval.go:
--------------------------------------------------------------------------------
1 | package apl
2 |
3 | import (
4 | "bufio"
5 | "fmt"
6 | "io"
7 | "runtime/debug"
8 | "strings"
9 | )
10 |
11 | // Program contains a slice of parsed expressions.
12 | type Program []expr
13 |
14 | // Eval executes an apl program.
15 | // It can be called in a loop for every line of input.
16 | func (a *Apl) Eval(p Program) (err error) {
17 | defer func() {
18 | if r := recover(); r != nil {
19 | err = fmt.Errorf("panic: %s\n%s", r, string(debug.Stack()))
20 | }
21 | }()
22 | write := func(val Value) {
23 | switch v := val.(type) {
24 | case Image:
25 | if a.stdimg != nil {
26 | a.stdimg.WriteImage(v)
27 | } else {
28 | fmt.Fprintln(a.stdout, val.String(a.Format))
29 | }
30 | default:
31 | fmt.Fprintln(a.stdout, val.String(a.Format))
32 | }
33 | }
34 |
35 | var val Value
36 | for _, expr := range p {
37 | val, err = expr.Eval(a)
38 | if err != nil {
39 | return err
40 | }
41 | if isAssignment(expr) == false {
42 | switch v := val.(type) {
43 | case Channel:
44 | i := 0
45 | for e := range v[0] {
46 | if i == 0 {
47 | i++
48 | if _, ok := e.(Image); ok && a.stdimg != nil {
49 | a.stdimg.StartLoop()
50 | defer a.stdimg.StopLoop()
51 | }
52 | }
53 | write(e)
54 | }
55 | default:
56 | write(val)
57 | }
58 | }
59 | }
60 | return nil
61 | }
62 |
63 | // EvalProgram evaluates all expressions in the program and returns the values.
64 | func (a *Apl) EvalProgram(p Program) ([]Value, error) {
65 | res := make([]Value, len(p))
66 | for i, e := range p {
67 | if v, err := e.Eval(a); err != nil {
68 | return nil, err
69 | } else {
70 | res[i] = v
71 | }
72 | }
73 | return res, nil
74 | }
75 |
76 | func (p Program) String(f Format) string {
77 | v := make([]string, len(p))
78 | for i := range p {
79 | v[i] = p[i].String(f)
80 | }
81 | return strings.Join(v, "⋄")
82 | }
83 |
84 | // EvalFile parses and evalutes from a reader.
85 | // It handles multiline statements.
86 | // The file argument is used only in the error message.
87 | func (a *Apl) EvalFile(r io.Reader, file string) (err error) {
88 | line := 0
89 | defer func() {
90 | if err != nil {
91 | err = fileError{file: file, line: line, err: err}
92 | }
93 | }()
94 |
95 | ok := true
96 | var p Program
97 | b := NewLineBuffer(a)
98 | scanner := bufio.NewScanner(r)
99 | for scanner.Scan() {
100 | line++
101 | ok, err = b.Add(scanner.Text())
102 | if err != nil {
103 | return
104 | }
105 |
106 | if ok {
107 | p, err = b.Parse()
108 | if err != nil {
109 | return
110 | }
111 |
112 | err = a.Eval(p)
113 | if err != nil {
114 | return
115 | }
116 | }
117 | }
118 | if ok == false && b.Len() > 0 {
119 | return fmt.Errorf("multiline statement is not terminated")
120 | }
121 | return nil
122 | }
123 |
124 | type fileError struct {
125 | file string
126 | line int
127 | err error
128 | }
129 |
130 | func (f fileError) Error() string {
131 | return fmt.Sprintf("%s:%d: %s", f.file, f.line, f.err.Error())
132 | }
133 |
134 | type expr interface {
135 | Eval(*Apl) (Value, error)
136 | String(f Format) string
137 | }
138 |
139 | func isAssignment(e expr) bool {
140 | // Assignment is implemented as an operator.
141 | if fn, ok := e.(*function); ok && fn != nil {
142 | if d, ok := fn.Function.(*derived); ok && d.op == "←" {
143 | return true
144 | }
145 | }
146 | return false
147 | }
148 |
--------------------------------------------------------------------------------
/apl/fmt_test.go:
--------------------------------------------------------------------------------
1 | package apl
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "strings"
7 | "testing"
8 | )
9 |
10 | func TestScanRankArray(t *testing.T) {
11 |
12 | testCases := []struct {
13 | in string
14 | rank int
15 | out []string
16 | }{
17 | {"", 0, []string{}},
18 | {"", 1, []string{}},
19 | {"", 2, []string{}},
20 | {"1 2 3\n4 5 6\n\n7 8 9\n1 2 3", 0, []string{
21 | "1", "2", "3", "4", "5", "6", "7", "8", "9", "1", "2", "3",
22 | }},
23 | {"1 2 3\n4 5 6\n\n7 8 9\n1 2 3", 1, []string{
24 | "1 2 3 [3]",
25 | "4 5 6 [3]",
26 | "7 8 9 [3]",
27 | "1 2 3 [3]",
28 | }},
29 | {"1 2 3\n4 5 6\n\n7 8 9\n1 2 3", 2, []string{
30 | " 1 2 3\n 4 5 6 [2 3]",
31 | " 7 8 9\n 1 2 3 [2 3]",
32 | }},
33 | {"1 2 3\n4 5 7\n", -1, []string{
34 | " 1 2 3\n 4 5 7 [2 3]",
35 | }},
36 | {"1 2 3\n4 5 6", -1, []string{
37 | " 1 2 3\n 4 5 6 [2 3]",
38 | }},
39 | {"1 2 3\n4 5 6\n\n7 8 9\n1 2 3\n\n", -1, []string{
40 | " 1 2 3\n 4 5 6\n\n 7 8 9\n 1 2 3 [2 2 3]",
41 | }},
42 | {"1 2 3\n4 5 6\n\n7 8 9\n1 2 3", -1, []string{
43 | " 1 2 3\n 4 5 6\n\n 7 8 9\n 1 2 3 [2 2 3]",
44 | }},
45 | {"1 2 3\n4 5 6\n\n7 8 9\n1 2 3", 3, []string{
46 | " 1 2 3\n 4 5 6\n\n 7 8 9\n 1 2 3 [2 2 3]",
47 | }},
48 | {"1 2 3\n\n4 5 6\n\n7 8 9\n\n1 2 3", 1, []string{
49 | "1 2 3 [3]",
50 | "4 5 6 [3]",
51 | "7 8 9 [3]",
52 | "1 2 3 [3]",
53 | }},
54 | {"1 2 3\n\n4 5 6\n\n7 8 9\n\n1 2 3", 2, []string{
55 | " 1 2 3 [1 3]",
56 | " 4 5 6 [1 3]",
57 | " 7 8 9 [1 3]",
58 | " 1 2 3 [1 3]",
59 | }},
60 | {"1 2 3\n\n4 5 6\n\n7 8 9\n\n1 2 3", 3, []string{
61 | " 1 2 3\n\n 4 5 6\n\n 7 8 9\n\n 1 2 3 [4 1 3]",
62 | }},
63 | {"[[[1 2 3],[4 5 6]],[[7 8 9],[1 2 3]]]", 2, []string{
64 | " 1 2 3\n 4 5 6 [2 3]",
65 | " 7 8 9\n 1 2 3 [2 3]",
66 | }},
67 | {"[[1,2,3],[4,5,6]]", -1, []string{
68 | " 1 2 3\n 4 5 6 [2 3]",
69 | }},
70 | {"[1,2,3]", -1, []string{
71 | "1 2 3 [3]",
72 | }},
73 | {"[1,2,3;4,5,6]", -1, []string{
74 | " 1 2 3\n 4 5 6 [2 3]",
75 | }},
76 | }
77 |
78 | for k, tc := range testCases {
79 | a := New(nil)
80 | in := strings.NewReader(tc.in)
81 | for i := 0; ; i++ {
82 | v, err := a.ScanRankArray(in, tc.rank)
83 | if err == io.EOF && i == len(tc.out) {
84 | goto next
85 | } else if err == io.EOF {
86 | t.Fatalf("#%d: expected %d results, got %d", k, len(tc.out), i-1)
87 | } else if err != nil {
88 | t.Fatalf("#%d: %s", k, err)
89 | } else if i == len(tc.out) {
90 | t.Fatalf("#%d: too many output values: %v", k, v)
91 | } else {
92 | s := ""
93 | if _, ok := v.(Array); !ok {
94 | s = v.String(a.Format)
95 | } else {
96 | s = fmt.Sprintf("%v %v", v.String(a.Format), v.(Array).Shape())
97 | }
98 | if s != tc.out[i] {
99 | t.Fatalf("#%d:%d: expected:\n%s\ngot:\n%s", k, i, tc.out[i], s)
100 | }
101 | }
102 | }
103 | next:
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/apl/http/register.go:
--------------------------------------------------------------------------------
1 | package http
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 |
7 | "github.com/ktye/iv/apl"
8 | )
9 |
10 | func Register(a *apl.Apl, name string) {
11 | pkg := map[string]apl.Value{
12 | "get": apl.ToFunction(get),
13 | }
14 | if name == "" {
15 | name = "http"
16 | }
17 | a.RegisterPackage(name, pkg)
18 | }
19 |
20 | // http→get returns a channel to read strings from a http connection.
21 | func get(a *apl.Apl, _, R apl.Value) (apl.Value, error) {
22 | addr, ok := R.(apl.String)
23 | if ok == false {
24 | return nil, fmt.Errorf("http get: right argument must be a string")
25 | }
26 | res, err := http.Get(string(addr))
27 | if err != nil {
28 | return nil, err
29 | }
30 | return apl.LineReader(res.Body), nil
31 | }
32 |
--------------------------------------------------------------------------------
/apl/image.go:
--------------------------------------------------------------------------------
1 | package apl
2 |
3 | import (
4 | "fmt"
5 | "image"
6 | "image/color"
7 | "image/draw"
8 | )
9 |
10 | // An ImageWriter is anything that can handle image output.
11 | // Apl's stdimg device uses it.
12 | // A single image can be written directly with WriteImage.
13 | // An animation needs Start- and StopLoop before and after.
14 | type ImageWriter interface {
15 | WriteImage(Image) error
16 | StartLoop()
17 | StopLoop()
18 | }
19 |
20 | // An Image is a raster image.
21 | //
22 | // It can by converting from a numeric array of shape HEIGHT WIDTH.
23 | // An Image is never empty, it always has rank 2.
24 | // It cannot be reshaped, instead reshape it's int array before creating it.
25 | //
26 | // Formats:
27 | // Img ← `img ⌶B B BoolArray (0 White, 1 Black) // TODO or user def, or alpha?
28 | // Img ← `img ⌶(I;P;) I numeric array with values in the range ⎕IO+(0..0xFF) as indexes into P
29 | // P (palette) vector of shape 256 with values as below:
30 | // Img ← `img ⌶N N numeric array, values between 0 and 0xFFFFFFFF (aarrggbb)
31 | // Transparency has the value 0xFF000000, which is inverted compared to the go image library,
32 | // to be able to specify opaque colors in the short form 0xRRGGBB.
33 | // After creation, an image can be indexed and assigned to.
34 | type Image struct {
35 | image.Image
36 | Dims []int
37 | }
38 |
39 | func (i Image) String(f Format) string {
40 | return i.toIntArray().String(f)
41 | }
42 | func (i Image) Copy() Value { return i } // Image is copied by reference.
43 |
44 | func (i Image) At(k int) Value {
45 | ic, idx := NewIdxConverter(i.Dims)
46 | ic.Indexes(k, idx)
47 | return colorValue(i.Image.At(idx[0], idx[1]))
48 | }
49 | func (i Image) Shape() []int {
50 | return i.Dims
51 | }
52 | func (i Image) Size() int {
53 | return Prod(i.Dims)
54 | }
55 | func (i Image) Set(k int, v Value) error {
56 | num, ok := v.(Number)
57 | if ok == false {
58 | return fmt.Errorf("img set: value must be a number: %T", v)
59 | }
60 | c, ok := num.ToIndex()
61 | if ok == false {
62 | return fmt.Errorf("img set: value must be an integer: %T", v)
63 | }
64 | y := k / i.Dims[1]
65 | x := k - y*i.Dims[1]
66 | r := i.Bounds()
67 | if d, ok := i.Image.(draw.Image); ok {
68 | d.Set(x+r.Min.X, y+r.Min.Y, toColor(c))
69 | return nil
70 | }
71 | return fmt.Errorf("img: image is not settable: %T", i.Image)
72 | }
73 |
74 | func (i Image) toIntArray() IntArray {
75 | ints := make([]int, Prod(i.Dims))
76 | shape := make([]int, len(i.Dims))
77 | copy(shape, i.Dims)
78 | idx := 0
79 | r := i.Image.Bounds()
80 | for y := r.Min.Y; y < r.Max.Y; y++ {
81 | for x := r.Min.X; x < r.Max.X; x++ {
82 | ints[idx] = int(colorValue(i.Image.At(x, y)))
83 | idx++
84 | }
85 | }
86 | return IntArray{Dims: shape, Ints: ints}
87 | }
88 |
89 | // ColorValue converts a Color to an Int.
90 | func colorValue(c color.Color) Int {
91 | r, g, b, a := c.RGBA() // uint32 premultiplied with alpha.
92 | u := (255-(a>>8))<<16 | r&0xFF00<<8 | g&0xFF00 | b>>8
93 | return Int(u)
94 | }
95 | func toColor(i int) color.Color {
96 | u := uint32(i)
97 | return color.RGBA{uint8(u & 0xFF0000 >> 16), uint8(u & 0xFF00 >> 8), uint8(u & 0xFF), ^uint8(u >> 24)}
98 | }
99 |
--------------------------------------------------------------------------------
/apl/index.go:
--------------------------------------------------------------------------------
1 | package apl
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | )
7 |
8 | // IdxSpec is the value of an index specification.
9 | // For an axis specification this must be a length 1 slice
10 | // and the value is an apl.Float.
11 | // For an index specification this is an array and each value
12 | // is either an EmptyArray or an IntArray.
13 | // IdxSpec is origin dependend.
14 | type IdxSpec []Value
15 |
16 | func (x IdxSpec) String(f Format) string {
17 | if x == nil {
18 | return "[nil]"
19 | }
20 | v := make([]string, len(x))
21 | for i, e := range x {
22 | v[i] = e.String(f)
23 | }
24 | return "[" + strings.Join(v, ";") + "]"
25 | }
26 | func (x IdxSpec) Copy() Value {
27 | r := make(IdxSpec, len(x))
28 | for i := range r {
29 | r[i] = x[i].Copy()
30 | }
31 | return r
32 | }
33 |
34 | func (x IdxSpec) Eval(a *Apl) (Value, error) {
35 | return x, nil
36 | }
37 |
38 | // Shape returns the result shape of the index specification applied to an array with shape src.
39 | func (x IdxSpec) Shape(src []int) ([]int, error) {
40 | return nil, fmt.Errorf("TODO apl.IdxSpec.Shape")
41 | }
42 |
43 | // IdxSpec represents an expression of an index specification.
44 | // That is what is in square brackets following an array.
45 | type idxSpec []expr
46 |
47 | func (x idxSpec) String(f Format) string {
48 | if x == nil {
49 | return "[nil]"
50 | }
51 | v := make([]string, len(x))
52 | for i, e := range x {
53 | v[i] = e.String(f)
54 | }
55 | return "[" + strings.Join(v, ";") + "]"
56 | }
57 |
58 | func (x idxSpec) Eval(a *Apl) (Value, error) {
59 | idx := make(IdxSpec, len(x))
60 | for i, e := range x {
61 | if v, err := e.Eval(a); err != nil {
62 | return nil, err
63 | } else {
64 | idx[i] = v
65 | }
66 | }
67 | return idx, nil
68 | }
69 |
70 | // Axis combines the right argument with an axis specification.
71 | type Axis struct {
72 | R Value
73 | A Value
74 | }
75 |
76 | func (ax Axis) String(f Format) string {
77 | return fmt.Sprintf("[%s]%s", ax.A.String(f), ax.R.String(f))
78 | }
79 | func (ax Axis) Copy() Value {
80 | r := Axis{}
81 | if ax.R != nil {
82 | r.R = ax.R.Copy()
83 | }
84 | if ax.A != nil {
85 | r.A = ax.A.Copy()
86 | }
87 | return r
88 | }
89 |
--------------------------------------------------------------------------------
/apl/io/README.md:
--------------------------------------------------------------------------------
1 | # Package io provides input and output streams
2 |
3 | Linking it into APL leads to an unsafe system.
4 |
5 | Io overloads several *primitive functions*:
6 | ```
7 | < filename returns a Channel reading from a file
8 | < 0 returns a Channel reading from stdin
9 | !`ls execute program return a channel
10 | !(`ls`-l) same with arguments
11 | `cat!A same reading input from A (String method) or channel (pipe)
12 | `file 0 {
42 | s = s[:idx]
43 | }
44 | res[i] = apl.String(s)
45 | }
46 | return res
47 | }
48 |
49 | func (e Env) At(a *apl.Apl, v apl.Value) apl.Value {
50 | s, ok := v.(apl.String)
51 | if ok == false {
52 | return apl.String("")
53 | }
54 | val, ok := os.LookupEnv(string(s))
55 | if ok {
56 | return apl.String(val)
57 | }
58 | return nil
59 | }
60 |
61 | func (e Env) Set(a *apl.Apl, key, val apl.Value) error {
62 | k, ok := key.(apl.String)
63 | if ok == false {
64 | return fmt.Errorf("setenv: key must be a string: %T", key)
65 | }
66 | v, ok := val.(apl.String)
67 | if ok == false {
68 | return fmt.Errorf("setenv: value must be a string: %T", val)
69 | }
70 | return os.Setenv(string(k), string(v))
71 | }
72 |
73 | type envfs struct{}
74 |
75 | func (e envfs) FileSystem(root string) (FileSystem, error) {
76 | if root != "/" {
77 | return nil, fmt.Errorf("envfs can only be registered with root file env:///, not %s", root)
78 | }
79 | return envfs{}, nil
80 | }
81 |
82 | func (e envfs) String() string { return "env:///" }
83 |
84 | func (e envfs) Open(name, mpt string) (io.ReadCloser, error) {
85 | if name == "" {
86 | v := os.Environ()
87 | var buf bytes.Buffer
88 | tw := tabwriter.NewWriter(&buf, 1, 0, 1, ' ', 0)
89 | for _, s := range v {
90 | s = strings.Replace(s, "=", "\t", 1)
91 | fmt.Fprintln(tw, mpt+s)
92 | }
93 | tw.Flush()
94 | return ioutil.NopCloser(&buf), nil
95 | }
96 | v, ok := os.LookupEnv(name)
97 | if ok {
98 | return ioutil.NopCloser(strings.NewReader(v)), nil
99 | }
100 | return nil, &os.PathError{
101 | Op: "open",
102 | Path: name,
103 | Err: fmt.Errorf("environment variable does not exist"),
104 | }
105 | }
106 |
107 | func (e envfs) Write(name string) (io.WriteCloser, error) {
108 | var b strings.Builder
109 | return envwriter{name: name, Builder: &b}, nil
110 | }
111 |
112 | type envwriter struct {
113 | name string
114 | *strings.Builder
115 | }
116 |
117 | func (e envwriter) Close() error {
118 | return os.Setenv(e.name, e.String())
119 | }
120 |
--------------------------------------------------------------------------------
/apl/io/read.go:
--------------------------------------------------------------------------------
1 | package io
2 |
3 | import (
4 | "bufio"
5 | "fmt"
6 | "io"
7 | "os"
8 | ex "os/exec"
9 | "strings"
10 |
11 | "github.com/ktye/iv/apl"
12 | "github.com/ktye/iv/apl/domain"
13 | "github.com/ktye/iv/apl/scan"
14 | )
15 |
16 | // Stdin is exported to be overwritable by tests.
17 | var Stdin io.ReadCloser = os.Stdin
18 |
19 | // read reads from a file
20 | func read(a *apl.Apl, _, R apl.Value) (apl.Value, error) {
21 | name, ok := R.(apl.String)
22 | if ok == false {
23 | // If R is 0, it reads from stdin.
24 | if num, ok := R.(apl.Number); ok {
25 | if n, ok := num.ToIndex(); ok && n == 0 {
26 | return apl.LineReader(Stdin), nil
27 | }
28 | }
29 | return nil, fmt.Errorf("io read: expect file name %T", R)
30 | }
31 | f, err := Open(string(name))
32 | if err != nil {
33 | return nil, err
34 | }
35 | return apl.LineReader(f), nil // LineReader closes the file.
36 | }
37 |
38 | // exec executes a program and sends the output through a channel.
39 | // If called dyadically it uses R as an input, that can be a channel or a Value.
40 | // If the program starts with a slash, it's location is looked up in the file system.
41 | // TODO: should all arguments starting with a slash be replaced?
42 | func exec(a *apl.Apl, L, R apl.Value) (apl.Value, error) {
43 | r := R
44 | var in io.Reader
45 | if L != nil {
46 | r = L
47 | c, ok := R.(apl.Channel)
48 | if ok {
49 | in = bufio.NewReader(apl.NewChannelReader(a, c))
50 | } else {
51 | in = strings.NewReader(R.String(a.Format))
52 | }
53 | }
54 |
55 | v, ok := domain.ToStringArray(nil).To(a, r)
56 | if ok == false {
57 | return nil, fmt.Errorf("io exec: argv must be strings: %T", r)
58 | }
59 | argv := v.(apl.StringArray).Strings
60 | if len(argv) == 0 {
61 | return nil, fmt.Errorf("io exec: argv empty")
62 | }
63 |
64 | // If the command starts with a slash, we may relocate it.
65 | if strings.HasPrefix(argv[0], "/") {
66 | fsys, mpt, err := lookup(argv[0])
67 | if err != nil {
68 | return nil, err
69 | }
70 | if f, ok := fsys.(fs); ok == false {
71 | return nil, fmt.Errorf("exec: %s: file system is not an os fs: %s", argv[0], fsys.String())
72 | } else {
73 | relpath := strings.TrimPrefix(argv[0], mpt)
74 | argv[0] = f.path(relpath)
75 | }
76 | }
77 |
78 | cmd := ex.Command(argv[0], argv[1:]...)
79 | cmd.Stdin = in
80 | out, err := cmd.StdoutPipe()
81 | if err != nil {
82 | return nil, err
83 | }
84 | c := apl.LineReader(out)
85 | if err := cmd.Start(); err != nil {
86 | return nil, err
87 | }
88 | return c, nil
89 | }
90 |
91 | // Load reads the file R and executes it.
92 | // It returns an error, if R is not a file.
93 | // If L is given, the file is executed in a new environment and the resulting variables
94 | // are copied to a package with the name of L.
95 | func load(a *apl.Apl, L, R apl.Value) (apl.Value, error) {
96 | s, ok := R.(apl.String)
97 | if ok == false {
98 | return nil, fmt.Errorf("io l: argument must be a file name: %T", R)
99 | }
100 |
101 | f, err := Open(string(s))
102 | if err != nil {
103 | return nil, err
104 | }
105 | defer f.Close()
106 |
107 | if L == nil {
108 | if err := a.EvalFile(f, string(s)); err != nil {
109 | return nil, err
110 | }
111 | return apl.EmptyArray{}, nil
112 | }
113 |
114 | l, ok := L.(apl.String)
115 | if ok == false {
116 | return nil, fmt.Errorf("io l: left argument must be a package name: %T", L)
117 | }
118 |
119 | if err := a.LoadPkg(f, string(s), string(l)); err != nil {
120 | return nil, err
121 | }
122 | return apl.EmptyArray{}, nil
123 | }
124 |
125 | func lCmd(t []scan.Token) []scan.Token {
126 | // /l `/file.apl `pkg
127 | if len(t) == 2 && t[0].T == scan.String && t[1].T == scan.String {
128 | return []scan.Token{t[1], scan.Token{T: scan.Identifier, S: "io→l"}, t[0]}
129 | }
130 | l := scan.Token{T: scan.Identifier, S: "io→l"}
131 | return append([]scan.Token{l}, t...)
132 | }
133 |
--------------------------------------------------------------------------------
/apl/io/register.go:
--------------------------------------------------------------------------------
1 | // Package io provides input and output streams.
2 | //
3 | // Linking it into APL leads to an unsafe system.
4 | // See README.md
5 | package io
6 |
7 | import (
8 | "github.com/ktye/iv/apl"
9 | "github.com/ktye/iv/apl/domain"
10 | "github.com/ktye/iv/apl/scan"
11 | )
12 |
13 | // Register adds the io package to the interpreter.
14 | // This will provide access to the file system and allows to start external processes.
15 | func Register(a *apl.Apl, name string) {
16 | if name == "" {
17 | name = "io"
18 | }
19 | pkg := map[string]apl.Value{
20 | "cd": apl.ToFunction(cd),
21 | "e": apl.ToFunction(env),
22 | "l": apl.ToFunction(load),
23 | "r": apl.ToFunction(read),
24 | "x": apl.ToFunction(exec),
25 | "mount": apl.ToFunction(mount),
26 | "umount": apl.ToFunction(umount),
27 | }
28 | cmd := map[string]scan.Command{
29 | "cd": toCommand(cdCmd),
30 | "l": toCommand(lCmd),
31 | "m": toCommand(mCmd),
32 | }
33 | a.AddCommands(cmd)
34 | a.RegisterPackage(name, pkg)
35 |
36 | a.RegisterPrimitive("<", apl.ToHandler(
37 | read,
38 | domain.Monadic(domain.IsString(nil)),
39 | "read file",
40 | ))
41 | a.RegisterPrimitive("<", apl.ToHandler(
42 | read,
43 | domain.Monadic(domain.ToIndex(nil)),
44 | "read fd",
45 | ))
46 | a.RegisterPrimitive("!", apl.ToHandler(
47 | exec,
48 | domain.Monadic(domain.ToStringArray(nil)),
49 | "exec",
50 | ))
51 | a.RegisterPrimitive("!", apl.ToHandler(
52 | exec,
53 | domain.Dyadic(domain.Split(domain.ToStringArray(nil), nil)),
54 | "exec",
55 | ))
56 | RegisterProtocol("var", varfs{Apl: a})
57 | RegisterProtocol("env", envfs{})
58 | mount(a, apl.String("/"), apl.String("."))
59 | mount(a, apl.String("/v/"), apl.String("var:///"))
60 | mount(a, apl.String("/e/"), apl.String("env:///"))
61 | }
62 |
--------------------------------------------------------------------------------
/apl/io/var.go:
--------------------------------------------------------------------------------
1 | package io
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "io"
7 | "io/ioutil"
8 | "os"
9 | "reflect"
10 | "strings"
11 | "text/tabwriter"
12 |
13 | "github.com/ktye/iv/apl"
14 | )
15 |
16 | // Varfs exports variables as a file system.
17 | type varfs struct {
18 | *apl.Apl
19 | }
20 |
21 | func (v varfs) FileSystem(root string) (FileSystem, error) {
22 | if v.Apl == nil {
23 | return nil, fmt.Errorf("varfs: apl is not connected")
24 | }
25 | if root != "/" {
26 | return nil, fmt.Errorf("varfs can only be registerd with root file: var:///")
27 | }
28 | return v, nil
29 | }
30 |
31 | func (v varfs) String() string {
32 | return "var:///"
33 | }
34 |
35 | func (v varfs) Write(name string) (rc io.WriteCloser, err error) {
36 | defer func() {
37 | if err != nil {
38 | err = &os.PathError{
39 | Op: "write",
40 | Path: name,
41 | Err: err,
42 | }
43 | }
44 | }()
45 |
46 | if strings.HasSuffix(name, "/") {
47 | return nil, fmt.Errorf("varfs: cannot write to directory")
48 | }
49 | // Package variables are immutable.
50 | if strings.ContainsRune(name, '→') {
51 | return nil, fmt.Errorf("varfs: cannot update package variable")
52 | }
53 | x := v.Apl.Lookup(name)
54 | if x == nil {
55 | return nil, fmt.Errorf("varfs: variable does not exist")
56 | }
57 | if vr, ok := x.(apl.VarReader); ok {
58 | var b bytes.Buffer
59 | return varWriter{Buffer: &b, a: v.Apl, v: vr, name: name}, nil
60 | }
61 | return nil, fmt.Errorf("varfs: type is not assignable: %T", x)
62 | }
63 |
64 | type varWriter struct {
65 | *bytes.Buffer
66 | a *apl.Apl
67 | v apl.VarReader
68 | name string
69 | }
70 |
71 | func (vw varWriter) Close() error {
72 | t := reflect.TypeOf(vw.v)
73 | v, err := vw.v.ReadFrom(vw.a, vw.Buffer)
74 | if err != nil {
75 | return err
76 | }
77 | if nt := reflect.TypeOf(v); nt != t {
78 | return fmt.Errorf("%T ReadFrom returns a wrong type: %T", t, nt)
79 | }
80 | return vw.a.Assign(vw.name, v)
81 | }
82 |
83 | func (v varfs) Open(name, mpt string) (io.ReadCloser, error) {
84 | list := func() (io.ReadCloser, error) {
85 | pkg := strings.TrimSuffix(name, "/")
86 | l, err := v.Apl.Vars(pkg)
87 | if err != nil {
88 | return nil, err
89 | }
90 | if pkg != "" {
91 | pkg += "→"
92 | }
93 | var buf bytes.Buffer
94 | tw := tabwriter.NewWriter(&buf, 1, 0, 1, ' ', 0)
95 | for i := range l {
96 | varname := pkg + l[i]
97 | info := ""
98 | if strings.HasSuffix(varname, "/") == false {
99 | x := v.Apl.Lookup(varname)
100 | if x == nil {
101 | info = "?"
102 | } else {
103 | info = reflect.TypeOf(x).String()
104 | if ar, ok := x.(apl.Array); ok {
105 | info += fmt.Sprintf(" %v", ar.Shape())
106 | }
107 | if s, ok := x.(apl.String); ok {
108 | info += fmt.Sprintf(" %d", len(s))
109 | }
110 | }
111 | }
112 | fmt.Fprintf(tw, "%s\t%s\n", mpt+varname, info)
113 | }
114 | tw.Flush()
115 | return ioutil.NopCloser(&buf), nil
116 | }
117 | if name == "" || strings.HasSuffix(name, "/") {
118 | return list()
119 | }
120 |
121 | // Print a variable.
122 | x := v.Apl.Lookup(name)
123 | if x != nil {
124 | return ioutil.NopCloser(strings.NewReader(x.String(v.Apl.Format))), nil
125 | }
126 |
127 | return nil, &os.PathError{
128 | Op: "open",
129 | Path: name,
130 | Err: fmt.Errorf("variable does not exist"),
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/apl/list.go:
--------------------------------------------------------------------------------
1 | package apl
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | )
7 |
8 | // List is a collection of items, possibly nested.
9 | // It also acts as a vector (a rank 1 array) but cannot be reshaped.
10 | type List []Value
11 |
12 | func (l List) String(f Format) string {
13 | if f.PP == -2 {
14 | return l.jsonString(f)
15 | }
16 | var buf strings.Builder
17 | buf.WriteRune('(')
18 | for i := range l {
19 | buf.WriteString(l[i].String(f))
20 | buf.WriteRune(';')
21 | }
22 | buf.WriteRune(')')
23 | return buf.String()
24 | }
25 | func (l List) Copy() Value {
26 | r := make(List, len(l))
27 | for i := range l {
28 | r[i] = l[i].Copy()
29 | }
30 | return r
31 | }
32 |
33 | func (l List) At(i int) Value {
34 | return l[i]
35 | }
36 |
37 | func (l List) Shape() []int {
38 | return []int{len(l)}
39 | }
40 |
41 | func (l List) Size() int {
42 | return len(l)
43 | }
44 |
45 | func (l List) GetDeep(idx []int) (Value, error) {
46 | return l.getset(idx, nil)
47 | }
48 |
49 | func (l List) SetDeep(idx []int, v Value) error {
50 | _, err := l.getset(idx, v.Copy())
51 | return err
52 | }
53 |
54 | func (l List) Depth() int {
55 | max := 1
56 | for _, e := range l {
57 | if el, ok := e.(List); ok {
58 | if d := el.Depth(); 1+d > max {
59 | max = 1 + d
60 | }
61 | }
62 | }
63 | return max
64 | }
65 |
66 | func (l List) getset(idx []int, v Value) (Value, error) {
67 | if len(idx) == 0 {
68 | return nil, fmt.Errorf("empty index")
69 | }
70 | for i, k := range idx {
71 | if k < 0 || k >= len(l) {
72 | return nil, fmt.Errorf("index out of range")
73 | }
74 | if i == len(idx)-1 {
75 | if v != nil {
76 | l[k] = v
77 | return nil, nil
78 | } else {
79 | return l[k], nil
80 | }
81 | }
82 | if lst, ok := l[k].(List); ok == false {
83 | return nil, fmt.Errorf("index is too deep")
84 | } else {
85 | l = lst
86 | }
87 | }
88 | return nil, fmt.Errorf("not reached")
89 | }
90 |
91 | // jsonString formats the list as a json object.
92 | func (l List) jsonString(f Format) string {
93 | var b strings.Builder
94 | b.WriteRune('[')
95 | for i, v := range l {
96 | if i > 0 {
97 | b.WriteRune(',')
98 | }
99 | b.WriteString(v.String(f))
100 | }
101 | b.WriteRune(']')
102 | return b.String()
103 | }
104 |
105 | type list []expr
106 |
107 | func (l list) Eval(a *Apl) (Value, error) {
108 | lst := make(List, len(l))
109 | var err error
110 | for i := range lst {
111 | if l[i] == nil {
112 | lst[i] = EmptyArray{}
113 | continue
114 | }
115 | lst[i], err = l[i].Eval(a)
116 | if err != nil {
117 | return nil, err
118 | }
119 | }
120 | return lst, nil
121 | }
122 |
123 | func (l list) String(f Format) string {
124 | var buf strings.Builder
125 | buf.WriteRune('(')
126 | for i := range l {
127 | buf.WriteString(l[i].String(f))
128 | buf.WriteRune(';')
129 | }
130 | buf.WriteRune(')')
131 | return buf.String()
132 | }
133 |
--------------------------------------------------------------------------------
/apl/multiline.go:
--------------------------------------------------------------------------------
1 | package apl
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 |
7 | "github.com/ktye/iv/apl/scan"
8 | )
9 |
10 | // ParseLines parses multiple lines separated by newline, that may contain continuation lines.
11 | // Continuation lines are only allowed for lambda functions.
12 | func (a *Apl) ParseLines(lines string) (Program, error) {
13 | b := NewLineBuffer(a)
14 | v := strings.Split(lines, "\n")
15 | for i, s := range v {
16 | if ok, err := b.Add(s); err != nil {
17 | return nil, err
18 | } else if i == len(v)-1 {
19 | if ok == false {
20 | return nil, fmt.Errorf("unbalanced {")
21 | }
22 | return b.Parse()
23 | }
24 | }
25 | return nil, nil
26 | }
27 |
28 | // LineBuffer buffers multiline statements for lambda functions.
29 | type LineBuffer struct {
30 | a *Apl
31 | tokens []scan.Token
32 | level int
33 | }
34 |
35 | func NewLineBuffer(a *Apl) *LineBuffer {
36 | return &LineBuffer{a: a}
37 | }
38 |
39 | // Add a line to the buffer.
40 | // The function returns ok, if the line is complete and can be parsed.
41 | func (b *LineBuffer) Add(line string) (bool, error) {
42 | if b.a == nil {
43 | return false, fmt.Errorf("linebuffer is not initialized (no APL)")
44 | }
45 | tokens, err := b.a.Scan(line)
46 | if err != nil {
47 | b.reset()
48 | return false, err
49 | }
50 | if len(tokens) == 0 {
51 | return false, nil
52 | }
53 |
54 | // Join with diamonds. Ommit the diamond if the last token is LeftBrace
55 | // or the next token is a RightBrace.
56 | diamond := true
57 | if len(b.tokens) == 0 {
58 | diamond = false
59 | } else if b.tokens[len(b.tokens)-1].T == scan.LeftBrace {
60 | diamond = false
61 | }
62 | if len(tokens) > 0 && diamond == true {
63 | b.tokens = append(b.tokens, scan.Token{T: scan.Diamond, S: "⋄"})
64 | }
65 | b.tokens = append(b.tokens, tokens...)
66 |
67 | for _, t := range tokens {
68 | if t.T == scan.LeftBrace {
69 | b.level++
70 | } else if t.T == scan.RightBrace {
71 | b.level--
72 | if b.level < 0 {
73 | b.reset()
74 | return false, fmt.Errorf("too many }")
75 | }
76 | }
77 | }
78 | if b.level == 0 {
79 | return true, nil
80 | }
81 | return false, nil
82 | }
83 |
84 | // Parse parses the tokens in the buffer.
85 | // Lines must be pushed to the buffer with Add and Parse should only be called if Add returned true.
86 | func (b *LineBuffer) Parse() (Program, error) {
87 | defer b.reset()
88 | return b.a.parse(b.tokens)
89 | }
90 |
91 | func (b *LineBuffer) Len() int {
92 | return len(b.tokens)
93 | }
94 |
95 | func (b *LineBuffer) reset() {
96 | b.level = 0
97 | if len(b.tokens) > 0 {
98 | b.tokens = b.tokens[:0]
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/apl/numbers/complexs.go:
--------------------------------------------------------------------------------
1 | package numbers
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/ktye/iv/apl"
7 | )
8 |
9 | // ComplexArray is a uniform array of complex128
10 | type ComplexArray struct {
11 | Dims []int
12 | Cmplx []complex128
13 | }
14 |
15 | func (f ComplexArray) String(af apl.Format) string {
16 | return apl.ArrayString(af, f)
17 | }
18 |
19 | func (f ComplexArray) Copy() apl.Value {
20 | r := ComplexArray{Dims: apl.CopyShape(f), Cmplx: make([]complex128, len(f.Cmplx))}
21 | copy(r.Cmplx, f.Cmplx)
22 | return f
23 | }
24 |
25 | func (f ComplexArray) At(i int) apl.Value {
26 | return Complex(f.Cmplx[i])
27 | }
28 |
29 | func (f ComplexArray) Shape() []int {
30 | return f.Dims
31 | }
32 |
33 | func (f ComplexArray) Size() int {
34 | return len(f.Cmplx)
35 | }
36 |
37 | func (f ComplexArray) Zero() apl.Value {
38 | return Complex(0.0)
39 | }
40 |
41 | func (f ComplexArray) Set(i int, v apl.Value) error {
42 | if i < 0 || i > len(f.Cmplx) {
43 | return fmt.Errorf("index out of range")
44 | }
45 | if c, ok := v.(Complex); ok {
46 | f.Cmplx[i] = complex128(c)
47 | return nil
48 | }
49 | return fmt.Errorf("cannot assign %T to ComplexArray", v)
50 | }
51 |
52 | func (f ComplexArray) Make(shape []int) apl.Uniform {
53 | return ComplexArray{
54 | Dims: shape,
55 | Cmplx: make([]complex128, prod(shape)),
56 | }
57 | }
58 |
59 | func makeComplexArray(v []apl.Value) ComplexArray {
60 | f := make([]complex128, len(v))
61 | for i, e := range v {
62 | f[i] = complex128(e.(Complex))
63 | }
64 | return ComplexArray{
65 | Dims: []int{len(v)},
66 | Cmplx: f,
67 | }
68 | }
69 |
70 | func (f ComplexArray) Reshape(shape []int) apl.Value {
71 | res := ComplexArray{
72 | Dims: shape,
73 | Cmplx: make([]complex128, prod(shape)),
74 | }
75 | k := 0
76 | for i := range res.Cmplx {
77 | res.Cmplx[i] = f.Cmplx[k]
78 | k++
79 | if k == len(f.Cmplx) {
80 | k = 0
81 | }
82 | }
83 | return res
84 | }
85 |
--------------------------------------------------------------------------------
/apl/numbers/exception.go:
--------------------------------------------------------------------------------
1 | package numbers
2 |
3 | import (
4 | "math"
5 | "math/cmplx"
6 |
7 | "github.com/ktye/iv/apl"
8 | )
9 |
10 | const (
11 | NaN exception = "NaN"
12 | Inf exception = "∞"
13 | NegInf exception = "¯∞"
14 | )
15 |
16 | type exception string
17 |
18 | func (e exception) String(f apl.Format) string {
19 | return string(e)
20 | }
21 | func (e exception) Copy() apl.Value { return e }
22 |
23 | func isException(n apl.Number) (exception, bool) {
24 | if f, ok := n.(Float); ok {
25 | if math.IsNaN(float64(f)) {
26 | return NaN, true
27 | }
28 | if math.IsInf(float64(f), 1) {
29 | return Inf, true
30 | }
31 | if math.IsInf(float64(f), -1) {
32 | return NegInf, true
33 | }
34 | }
35 | if c, ok := n.(Complex); ok {
36 | if cmplx.IsNaN(complex128(c)) {
37 | return NaN, true
38 | }
39 | if cmplx.IsInf(complex128(c)) {
40 | return Inf, true
41 | }
42 | }
43 | return "", false
44 | }
45 |
--------------------------------------------------------------------------------
/apl/numbers/floats.go:
--------------------------------------------------------------------------------
1 | package numbers
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/ktye/iv/apl"
7 | )
8 |
9 | // FloatArray is a uniform array of float64.
10 | type FloatArray struct {
11 | Dims []int
12 | Floats []float64
13 | }
14 |
15 | func (f FloatArray) String(af apl.Format) string {
16 | return apl.ArrayString(af, f)
17 | }
18 | func (f FloatArray) Copy() apl.Value {
19 | r := FloatArray{Dims: apl.CopyShape(f), Floats: make([]float64, len(f.Floats))}
20 | copy(r.Floats, f.Floats)
21 | return r
22 | }
23 |
24 | func (f FloatArray) At(i int) apl.Value {
25 | return Float(f.Floats[i])
26 | }
27 |
28 | func (f FloatArray) Shape() []int {
29 | return f.Dims
30 | }
31 |
32 | func (f FloatArray) Size() int {
33 | return len(f.Floats)
34 | }
35 |
36 | func (f FloatArray) Zero() apl.Value {
37 | return Float(0.0)
38 | }
39 |
40 | func (f FloatArray) Set(i int, v apl.Value) error {
41 | if i < 0 || i > len(f.Floats) {
42 | return fmt.Errorf("index out of range")
43 | }
44 | if c, ok := v.(Float); ok {
45 | f.Floats[i] = float64(c)
46 | return nil
47 | }
48 | return fmt.Errorf("cannot assign %T to FloatArray", v)
49 | }
50 |
51 | func (f FloatArray) Make(shape []int) apl.Uniform {
52 | return FloatArray{
53 | Dims: shape,
54 | Floats: make([]float64, prod(shape)),
55 | }
56 | }
57 |
58 | func makeFloatArray(v []apl.Value) FloatArray {
59 | f := make([]float64, len(v))
60 | for i, e := range v {
61 | f[i] = float64(e.(Float))
62 | }
63 | return FloatArray{
64 | Dims: []int{len(v)},
65 | Floats: f,
66 | }
67 | }
68 |
69 | func (f FloatArray) Reshape(shape []int) apl.Value {
70 | res := FloatArray{
71 | Dims: shape,
72 | Floats: make([]float64, prod(shape)),
73 | }
74 | k := 0
75 | for i := range res.Floats {
76 | res.Floats[i] = f.Floats[k]
77 | k++
78 | if k == len(f.Floats) {
79 | k = 0
80 | }
81 | }
82 | return res
83 | }
84 |
85 | func prod(shape []int) int {
86 | if len(shape) == 0 {
87 | return 0
88 | }
89 | n := shape[0]
90 | for i := 1; i < len(shape); i++ {
91 | n *= shape[i]
92 | }
93 | return n
94 | }
95 |
--------------------------------------------------------------------------------
/apl/numbers/numbers_test.go:
--------------------------------------------------------------------------------
1 | package numbers
2 |
3 | import (
4 | "reflect"
5 | "testing"
6 | "time"
7 |
8 | "github.com/ktye/iv/apl"
9 | )
10 |
11 | func TestParse(t *testing.T) {
12 | testCases := []struct {
13 | s string
14 | n apl.Number
15 | }{
16 | {"1", apl.Int(1)},
17 | {"1b", apl.Bool(true)},
18 | {"¯2", apl.Int(-2)},
19 | {"¯2.0", Float(-2)},
20 | {"2.", Float(2)},
21 | {"3J¯2.0", Complex(complex(3, -2))},
22 | {"5a90", Complex(complex(0, 5))},
23 | {"3.12E¯2", Float(0.0312)},
24 | {".5", Float(0.5)},
25 | {"¯.3", Float(-0.3)},
26 | {"2014.04.02", Time(time.Date(2014, 4, 2, 0, 0, 0, 0, time.UTC))},
27 | {"2014.04.02T09.37.22", Time(time.Date(2014, 4, 2, 9, 37, 22, 0, time.UTC))},
28 | {"10s", Time(y0.Add(10 * time.Second))},
29 | }
30 |
31 | a := apl.New(nil)
32 | Register(a)
33 |
34 | for k, tc := range testCases {
35 | ne, err := a.Tower.Parse(tc.s)
36 | if err != nil {
37 | t.Fatal(err)
38 | }
39 | n, err := ne.Eval(a)
40 | if err != nil {
41 | t.Fatal(err)
42 | }
43 | if reflect.TypeOf(n) != reflect.TypeOf(tc.n) {
44 | t.Fatalf("#%d: %s: expected %T got %T", k, tc.s, tc.n, n)
45 | }
46 | if n != tc.n {
47 | t.Fatalf("#%d: %s: numbers are not equal: %v, %v", k, tc.s, tc.n, n)
48 | }
49 | }
50 | }
51 |
52 | func TestSameType(t *testing.T) {
53 | a := apl.New(nil)
54 | Register(a)
55 |
56 | testCases := []struct {
57 | a, b apl.Number
58 | c, d apl.Number
59 | }{
60 | {apl.Bool(false), apl.Bool(true), apl.Bool(false), apl.Bool(true)},
61 | {apl.Bool(false), apl.Int(1), apl.Int(0), apl.Int(1)},
62 | {apl.Int(1), apl.Bool(false), apl.Int(1), apl.Int(0)},
63 | {apl.Int(1), apl.Int(2), apl.Int(1), apl.Int(2)},
64 | {apl.Bool(true), Float(3), Float(1), Float(3)},
65 | {apl.Int(0), Float(3), Float(0), Float(3)},
66 | {Float(3), apl.Int(4), Float(3), Float(4)},
67 | {apl.Int(2), Complex(3 + 1i), Complex(2), Complex(3 + 1i)},
68 | {Complex(1 + 2i), Float(3), Complex(1 + 2i), Complex(3)},
69 | }
70 |
71 | for n, tc := range testCases {
72 | c, d, err := a.Tower.SameType(tc.a, tc.b)
73 | if err != nil {
74 | t.Fatalf("#%d: %s", n, err)
75 | }
76 | if reflect.TypeOf(c) != reflect.TypeOf(d) {
77 | t.Fatalf("not the same type: %T %T", c, d)
78 | }
79 | if c != tc.c {
80 | t.Fatalf("expected %v got %v", tc.c, c)
81 | }
82 | if d != tc.d {
83 | t.Fatalf("expected %v got %v", tc.d, d)
84 | }
85 | }
86 | }
87 |
88 | func TestImport(t *testing.T) {
89 | a := apl.New(nil)
90 | Register(a)
91 |
92 | testCases := []struct {
93 | i apl.Number
94 | n apl.Number
95 | }{
96 | {apl.Bool(false), Float(0)},
97 | {apl.Bool(true), Float(1)},
98 | {apl.Int(0), Float(0)},
99 | {apl.Int(1), Float(1)},
100 | {apl.Int(2), Float(2)},
101 | {apl.Int(-1), Float(-1)},
102 | }
103 | for _, tc := range testCases {
104 | n := a.Tower.Import(tc.i)
105 | if reflect.TypeOf(n) != reflect.TypeOf(tc.n) {
106 | t.Fatal("wrong type")
107 | }
108 | if n != tc.n {
109 | t.Fatal("wrong value")
110 | }
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/apl/numbers/register.go:
--------------------------------------------------------------------------------
1 | // Package number defines the basic numeric types for APL.
2 | // These are Integer, Float and Complex.
3 | package numbers
4 |
5 | import (
6 | "reflect"
7 | "time"
8 |
9 | "github.com/ktye/iv/apl"
10 | )
11 |
12 | // Register sets the default numeric tower Integer->Float->Complex.
13 | func Register(a *apl.Apl) {
14 | if err := a.SetTower(newTower()); err != nil {
15 | panic(err)
16 | }
17 | }
18 |
19 | func newTower() apl.Tower {
20 | m := make(map[reflect.Type]*apl.Numeric)
21 | m[reflect.TypeOf(Float(0))] = &apl.Numeric{
22 | Class: 0,
23 | Parse: ParseFloat,
24 | Uptype: floatToComplex,
25 | }
26 | m[reflect.TypeOf(Complex(0))] = &apl.Numeric{
27 | Class: 1,
28 | Parse: ParseComplex,
29 | Uptype: func(n apl.Number) (apl.Number, bool) {
30 | // Uptype converts a number to seconds, if the imag part is 0
31 | if imag(complex128(n.(Complex))) != 0 {
32 | return nil, false
33 | }
34 | d := time.Duration(int64(1e9 * real(complex128(n.(Complex)))))
35 | return Time(y0.Add(d)), true
36 | },
37 | }
38 | m[reflect.TypeOf(Time{})] = &apl.Numeric{
39 | Class: 2,
40 | Parse: ParseTime,
41 | Uptype: func(n apl.Number) (apl.Number, bool) { return n, false },
42 | }
43 | t := apl.Tower{
44 | Numbers: m,
45 | Import: func(n apl.Number) apl.Number {
46 | if b, ok := n.(apl.Bool); ok {
47 | if b {
48 | return Float(1)
49 | }
50 | return Float(0)
51 | } else if n, ok := n.(apl.Int); ok {
52 | return Float(n)
53 | }
54 | return n
55 | },
56 | Uniform: makeUniform,
57 | }
58 | return t
59 | }
60 |
61 | func makeUniform(v []apl.Value) (apl.Value, bool) {
62 | if len(v) == 0 {
63 | return nil, false
64 | }
65 | if t := reflect.TypeOf(v[0]); t == reflect.TypeOf(apl.Bool(false)) {
66 | return makeBoolArray(v), true
67 | } else if t := reflect.TypeOf(v[0]); t == reflect.TypeOf(apl.Int(0)) {
68 | return makeIndexArray(v), true
69 | } else if t == reflect.TypeOf(Float(0.0)) {
70 | return makeFloatArray(v), true
71 | } else if t == reflect.TypeOf(Complex(0)) {
72 | return makeComplexArray(v), true
73 | } else if t == reflect.TypeOf(y0) {
74 | return makeTimeArray(v), true
75 | }
76 | return nil, false
77 | }
78 |
79 | func makeBoolArray(v []apl.Value) apl.BoolArray {
80 | f := make([]bool, len(v))
81 | for i, e := range v {
82 | f[i] = bool(e.(apl.Bool))
83 | }
84 | return apl.BoolArray{
85 | Dims: []int{len(v)},
86 | Bools: f,
87 | }
88 | }
89 | func makeIndexArray(v []apl.Value) apl.IntArray {
90 | f := make([]int, len(v))
91 | for i, e := range v {
92 | f[i] = int(e.(apl.Int))
93 | }
94 | return apl.IntArray{
95 | Dims: []int{len(v)},
96 | Ints: f,
97 | }
98 | }
99 |
100 | func getformat(f apl.Format, num apl.Value) (string, bool) {
101 | if f.Fmt == nil {
102 | return "", false
103 | }
104 | s := f.Fmt[reflect.TypeOf(num)]
105 | if len(s) > 0 && s[0] == '-' {
106 | return s[1:], true
107 | }
108 | if f.PP < -1 {
109 | return "", true // intended for external interchange (full prec, with -).
110 | }
111 | return s, false
112 | }
113 |
--------------------------------------------------------------------------------
/apl/numbers/times.go:
--------------------------------------------------------------------------------
1 | package numbers
2 |
3 | import (
4 | "fmt"
5 | "time"
6 |
7 | "github.com/ktye/iv/apl"
8 | )
9 |
10 | // TimeArray is a uniform array of time.Time.
11 | type TimeArray struct {
12 | Dims []int
13 | Times []time.Time
14 | }
15 |
16 | func (t TimeArray) String(f apl.Format) string {
17 | return apl.ArrayString(f, t)
18 | }
19 |
20 | func (t TimeArray) Copy() apl.Value {
21 | r := TimeArray{Dims: apl.CopyShape(t), Times: make([]time.Time, len(t.Times))}
22 | copy(r.Times, t.Times)
23 | return r
24 | }
25 |
26 | func (t TimeArray) At(i int) apl.Value {
27 | return Time(t.Times[i])
28 | }
29 |
30 | func (t TimeArray) Shape() []int {
31 | return t.Dims
32 | }
33 |
34 | func (t TimeArray) Size() int {
35 | return len(t.Times)
36 | }
37 |
38 | func (t TimeArray) Zero() apl.Value {
39 | return Time(y0)
40 | }
41 |
42 | func (t TimeArray) Set(i int, v apl.Value) error {
43 | if i < 0 || i > len(t.Times) {
44 | return fmt.Errorf("index out of range")
45 | }
46 | if c, ok := v.(Time); ok {
47 | t.Times[i] = time.Time(c)
48 | return nil
49 | }
50 | return fmt.Errorf("cannot assign %T to TimeArray", v)
51 | }
52 |
53 | func (t TimeArray) Reshape(shape []int) apl.Value {
54 | res := TimeArray{
55 | Dims: shape,
56 | Times: make([]time.Time, prod(shape)),
57 | }
58 | k := 0
59 | for i := range res.Times {
60 | res.Times[i] = t.Times[k]
61 | k++
62 | if k == len(t.Times) {
63 | k = 0
64 | }
65 | }
66 | return res
67 | }
68 |
69 | func (t TimeArray) Make(shape []int) apl.Uniform {
70 | return TimeArray{
71 | Dims: shape,
72 | Times: make([]time.Time, prod(shape)),
73 | }
74 | }
75 |
76 | func makeTimeArray(v []apl.Value) TimeArray {
77 | t := make([]time.Time, len(v))
78 | for i, e := range v {
79 | t[i] = time.Time(e.(Time))
80 | }
81 | return TimeArray{
82 | Dims: []int{len(v)},
83 | Times: t,
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/apl/object.go:
--------------------------------------------------------------------------------
1 | package apl
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | "text/tabwriter"
7 | )
8 |
9 | // Object is a compound type that has keys and values.
10 | //
11 | // Values are accessed by indexing with keys.
12 | // Object[Key]
13 | // Keys are usually strings, but dont have to be.
14 | // To set a key, use indexed assignment:
15 | // Object[Name]←X
16 | // This also works for vectors
17 | // Object[`k1`k2`k3] ← 5 6 7
18 | //
19 | // Keys are returned by #Object.
20 | // Number of keys can also be obtained by ⍴Object.
21 | //
22 | // Indexing by vector returns a Dict with the specified keys.
23 | // Object["key1" "key2"].
24 | //
25 | // Method calls (calling a function stored in a key) or a go method
26 | // for an xgo object cannot be applied directly:
27 | // Object[`f] R ⍝ cannot be parsed
28 | // Instead, assign it to a function variable, or commute:
29 | // f←Object[`f] ⋄ f R
30 | // Object[`f]⍨R
31 | type Object interface {
32 | Value
33 | Keys() []Value
34 | At(Value) Value
35 | Set(Value, Value) error
36 | }
37 |
38 | // Dict is a dictionary object.
39 | // A Dict is created with the L#R, where
40 | // L is a key or a vector of keys and R conforming values.
41 | // Dicts can be indexed with their keys.
42 | // Example:
43 | // D←`alpha#1 2 3 ⍝ Single key
44 | // D←`a`b`c#1 2 3 ⍝ 3 Keys
45 | // D[`a] ⍝ returns value 1
46 | // D[`a`c] ⍝ returns a dict with 2 keys
47 | type Dict struct {
48 | K []Value
49 | M map[Value]Value
50 | }
51 |
52 | func (d *Dict) Keys() []Value {
53 | return d.K
54 | }
55 |
56 | func (d *Dict) At(key Value) Value {
57 | if d.M == nil {
58 | return nil
59 | }
60 | return d.M[key]
61 | }
62 |
63 | // Set updates the value for the given key, or creates a new one,
64 | // if the key does not exist.
65 | // Keys must be valid variable names.
66 | func (d *Dict) Set(key Value, v Value) error {
67 | if d.M == nil {
68 | d.M = make(map[Value]Value)
69 | }
70 | if _, ok := d.M[key]; ok == false {
71 | d.K = append(d.K, key.Copy())
72 | }
73 | d.M[key.Copy()] = v.Copy()
74 | return nil
75 | }
76 |
77 | func (d *Dict) String(f Format) string {
78 | if f.PP == -2 {
79 | return d.jsonString(f)
80 | } else if f.PP == -3 {
81 | return d.matString(f)
82 | }
83 | var buf strings.Builder
84 | tw := tabwriter.NewWriter(&buf, 1, 0, 1, ' ', 0)
85 | for _, k := range d.K {
86 | fmt.Fprintf(tw, "%s:\t%s\n", k.String(f), d.M[k].String(f))
87 | }
88 | tw.Flush()
89 | s := buf.String()
90 | if len(s) > 0 && s[len(s)-1] == '\n' {
91 | return s[:len(s)-1]
92 | }
93 | return s
94 | }
95 |
96 | func (d *Dict) Copy() Value {
97 | r := Dict{}
98 | if d.K != nil {
99 | r.K = make([]Value, len(d.K))
100 | for i := range d.K {
101 | r.K[i] = d.K[i].Copy()
102 | }
103 | }
104 | if d.M != nil {
105 | r.M = make(map[Value]Value)
106 | for k, v := range d.M {
107 | r.M[k.Copy()] = v.Copy()
108 | }
109 | }
110 | return &r
111 | }
112 |
113 | func (d *Dict) jsonString(f Format) string {
114 | var b strings.Builder
115 | b.WriteRune('{')
116 | keys := d.Keys()
117 | for i, key := range keys {
118 | if i > 0 {
119 | b.WriteRune(',')
120 | }
121 | k := key.String(f)
122 | val := d.At(key)
123 | v := val.String(f)
124 | b.WriteString(k)
125 | b.WriteRune(':')
126 | b.WriteString(v)
127 | }
128 | b.WriteRune('}')
129 | return b.String()
130 | }
131 |
132 | func (d *Dict) matString(f Format) string {
133 | var b strings.Builder
134 | b.WriteString("struct(")
135 | keys := d.Keys()
136 | for i, key := range keys {
137 | if i > 0 {
138 | b.WriteRune(',')
139 | }
140 | k := key.String(f)
141 | val := d.At(key)
142 | v := val.String(f)
143 | b.WriteString(k)
144 | b.WriteRune(',')
145 | b.WriteString(v)
146 | }
147 | b.WriteRune(')')
148 | return b.String()
149 | }
150 |
151 | func (a *Apl) ParseDict(prototype Value, s string) (*Dict, error) {
152 | if prototype != nil {
153 | _, ok := prototype.(*Dict)
154 | if ok == false {
155 | return nil, fmt.Errorf("ParseDict: prototype is not a dict: %T", prototype)
156 | }
157 | }
158 | return nil, fmt.Errorf("TODO ParseDict")
159 | }
160 |
--------------------------------------------------------------------------------
/apl/operators/axis.go:
--------------------------------------------------------------------------------
1 | package operators
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/ktye/iv/apl"
7 | . "github.com/ktye/iv/apl/domain"
8 | )
9 |
10 | func init() {
11 | register(operator{
12 | symbol: "⍂",
13 | Domain: DyadicOp(nil),
14 | doc: "axis specification",
15 | derived: axis,
16 | selection: selection(axis),
17 | })
18 | }
19 |
20 | func axis(a *apl.Apl, f, g apl.Value) apl.Function {
21 | derived := func(a *apl.Apl, L, R apl.Value) (apl.Value, error) {
22 | if p, ok := f.(apl.Function); ok {
23 | return p.Call(a, L, apl.Axis{R: R, A: g})
24 | } else {
25 | return nil, fmt.Errorf("axis: expected primitive on the left: %T", f)
26 | }
27 | }
28 | return function(derived)
29 | }
30 |
31 | // splitAxis returns ax.R and converts ax.A to []int taking account of index origin.
32 | // It R is not an axis it returns R and nil.
33 | func splitAxis(a *apl.Apl, R apl.Value) (apl.Value, []int, error) {
34 | ax, ok := R.(apl.Axis)
35 | if ok == false {
36 | return R, nil, nil
37 | }
38 | if _, ok := ax.A.(apl.EmptyArray); ok {
39 | return ax.R, nil, nil
40 | }
41 | to := ToIndexArray(nil)
42 | X, ok := to.To(a, ax.A)
43 | if ok == false {
44 | return nil, nil, fmt.Errorf("axis is not an index array")
45 | }
46 | ar := X.(apl.IntArray)
47 | shape := ar.Shape()
48 | if len(shape) != 1 {
49 | return nil, nil, fmt.Errorf("axis has wrong shape: %d", len(shape))
50 | }
51 | x := make([]int, len(ar.Ints))
52 | for i, n := range ar.Ints {
53 | x[i] = n - a.Origin
54 | }
55 | return ax.R, x, nil
56 | }
57 |
--------------------------------------------------------------------------------
/apl/operators/commute.go:
--------------------------------------------------------------------------------
1 | package operators
2 |
3 | import (
4 | "github.com/ktye/iv/apl"
5 | . "github.com/ktye/iv/apl/domain"
6 | )
7 |
8 | func init() {
9 | register(operator{
10 | symbol: "⍨",
11 | Domain: MonadicOp(Function(nil)),
12 | doc: "commute, duplicate",
13 | derived: commute,
14 | })
15 | }
16 |
17 | func commute(a *apl.Apl, f, _ apl.Value) apl.Function {
18 | derived := func(a *apl.Apl, L, R apl.Value) (apl.Value, error) {
19 | f := f.(apl.Function)
20 | if L == nil {
21 | L = R.Copy()
22 | }
23 | return f.Call(a, R, L)
24 | }
25 | return function(derived)
26 | }
27 |
--------------------------------------------------------------------------------
/apl/operators/identity.go:
--------------------------------------------------------------------------------
1 | package operators
2 |
3 | import (
4 | "math"
5 |
6 | "github.com/ktye/iv/apl"
7 | "github.com/ktye/iv/apl/numbers"
8 | )
9 |
10 | // identityItem returns the identity item for the given function f, when
11 | // the function is applied as f/⍳0.
12 | func identityItem(f apl.Value) apl.Value {
13 | // Table from APL2: p 211, DyaRef p 170
14 | if p, ok := f.(apl.Primitive); ok {
15 | switch p {
16 | case "+", "-", "|", "∨", "<", ">", "≠", "⊤", "∪", "⌽", "⊖":
17 | return apl.Int(0)
18 | case "×", "÷", "*", "!", "^", "∧", "≤", "=", "≥", "/", "⌿", `\`, `⍀`:
19 | return apl.Int(1)
20 | case "⌊":
21 | return numbers.Float(-math.MaxFloat64)
22 | case "⌈":
23 | return numbers.Float(math.MaxFloat64)
24 | }
25 | }
26 | return nil
27 | }
28 |
--------------------------------------------------------------------------------
/apl/operators/jot.go:
--------------------------------------------------------------------------------
1 | package operators
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/ktye/iv/apl"
7 | . "github.com/ktye/iv/apl/domain"
8 | )
9 |
10 | func init() {
11 | register(operator{
12 | symbol: "∘",
13 | Domain: DyadicOp(Split(nil, nil)),
14 | doc: "compose",
15 | derived: compose,
16 | })
17 | }
18 |
19 | func compose(a *apl.Apl, f, g apl.Value) apl.Function {
20 | derived := func(a *apl.Apl, L, R apl.Value) (apl.Value, error) {
21 | fn, isfunc := f.(apl.Function)
22 | gn, isgunc := g.(apl.Function)
23 | if L == nil {
24 | if isfunc && isgunc {
25 | // Form 1: f∘g R
26 | v, err := gn.Call(a, nil, R)
27 | if err != nil {
28 | return nil, err
29 | }
30 | return fn.Call(a, nil, v.Copy())
31 | } else if isgunc {
32 | // Form II: A∘g R
33 | return gn.Call(a, f, R)
34 | } else if isfunc {
35 | // Form III: (f∘X) R
36 | return fn.Call(a, R, g)
37 | }
38 | } else {
39 | if isfunc && isgunc {
40 | // Form IV: L f∘g R
41 | v, err := gn.Call(a, nil, R)
42 | if err != nil {
43 | return nil, err
44 | }
45 | return fn.Call(a, L, v.Copy())
46 | }
47 | }
48 | return nil, fmt.Errorf("compose: cannot handle %T %T ∘ %T %T", L, f, g, R)
49 | }
50 | return function(derived)
51 | }
52 |
--------------------------------------------------------------------------------
/apl/operators/power.go:
--------------------------------------------------------------------------------
1 | package operators
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/ktye/iv/apl"
7 | . "github.com/ktye/iv/apl/domain"
8 | )
9 |
10 | func init() {
11 | register(operator{
12 | symbol: "⍣",
13 | Domain: DyadicOp(Split(Function(nil), nil)),
14 | doc: "power",
15 | derived: power,
16 | })
17 | }
18 |
19 | // TODO: should there be a limit? How to set/change it?
20 | const powerlimit = 1000
21 |
22 | func power(a *apl.Apl, f, g apl.Value) apl.Function {
23 | derived := func(a *apl.Apl, L, R apl.Value) (apl.Value, error) {
24 | f := f.(apl.Function)
25 | gn, isgf := g.(apl.Function)
26 | to := ToIndex(nil)
27 | if isgf == false {
28 | // RO g is not a function but an integer.
29 | nv, ok := to.To(a, g)
30 | if ok == false {
31 | return nil, fmt.Errorf("power: non-function RO must be an integer: %T", g)
32 | }
33 | n := int(nv.(apl.Int))
34 | if n < 0 {
35 | return nil, fmt.Errorf("power: function inverse is not implemented")
36 | } else if n == 0 {
37 | return R, nil
38 | }
39 | var err error
40 | v := R
41 | for i := 0; i < n; i++ {
42 | v, err = f.Call(a, L, v)
43 | if err != nil {
44 | return nil, err
45 | }
46 | }
47 | return v.Copy(), nil
48 | } else {
49 | // RO g is a function.
50 | var err error
51 | var fR, v apl.Value
52 | r := R
53 | m := 0
54 | for {
55 | if m > powerlimit {
56 | return nil, fmt.Errorf("power: recusion limit exceeded")
57 | }
58 | m++
59 | fR, err = f.Call(a, L, r)
60 | if err != nil {
61 | return nil, err
62 | }
63 | v, err = gn.Call(a, fR.Copy(), r)
64 | if err != nil {
65 | return nil, err
66 | }
67 | nv, ok := to.To(a, v.Copy())
68 | if ok == false {
69 | return nil, fmt.Errorf("power: gY must be an integer: %T", v)
70 | }
71 | n := int(nv.(apl.Int))
72 |
73 | r = fR.Copy()
74 | if n == 1 {
75 | return r, nil
76 | }
77 |
78 | }
79 | }
80 | }
81 | return function(derived)
82 | }
83 |
--------------------------------------------------------------------------------
/apl/operators/register.go:
--------------------------------------------------------------------------------
1 | package operators
2 |
3 | import (
4 | "fmt"
5 | "runtime"
6 | "strings"
7 |
8 | "github.com/ktye/iv/apl"
9 | )
10 |
11 | // Register adds the operators in this package to a.
12 | func Register(a *apl.Apl) {
13 | for _, op := range operators {
14 | a.RegisterOperator(op.symbol, op)
15 | }
16 | }
17 |
18 | type operator struct {
19 | apl.Domain
20 | symbol string
21 | doc string
22 | derived func(*apl.Apl, apl.Value, apl.Value) apl.Function
23 | selection func(*apl.Apl, apl.Value, apl.Value, apl.Value, apl.Value) (apl.IntArray, error)
24 | }
25 |
26 | func (op operator) Doc() string { return op.doc }
27 | func (op operator) Derived(a *apl.Apl, LO, RO apl.Value) apl.Function {
28 | return op.derived(a, LO, RO)
29 | }
30 | func (op operator) Select(a *apl.Apl, L, LO, RO, R apl.Value) (apl.IntArray, error) {
31 | if op.selection == nil {
32 | return apl.IntArray{}, fmt.Errorf("operator %s cannot be used in selective assignment", op.symbol)
33 | } else {
34 | return op.selection(a, L, LO, RO, R)
35 | }
36 | }
37 | func (op operator) DyadicOp() bool {
38 | if ar, ok := op.Domain.(arity); ok {
39 | return ar.DyadicOp()
40 | }
41 | // Domain must start with MonadicOp or DyadicOp
42 | panic(op.symbol + ": operator Domain must start with MonadicOp or DyadicOp")
43 | }
44 |
45 | type arity interface {
46 | DyadicOp() bool
47 | }
48 |
49 | var operators []operator
50 |
51 | func register(op operator) {
52 | // Add source path to documentation.
53 | _, fn, line, _ := runtime.Caller(1)
54 | if idx := strings.Index(fn, "apl/operators"); idx != -1 {
55 | fn = fn[idx:]
56 | }
57 | op.doc += fmt.Sprintf("\t%s:%d", fn, line)
58 |
59 | operators = append(operators, op)
60 | }
61 |
62 | // primitive implements a SingleDomain, which tests against the prmitive symbol.
63 | type primitive string
64 |
65 | func (p primitive) To(a *apl.Apl, V apl.Value) (apl.Value, bool) {
66 | if pf, ok := V.(apl.Primitive); ok && pf == apl.Primitive(p) {
67 | return V, true
68 | }
69 | return V, false
70 | }
71 | func (p primitive) String(f apl.Format) string { return string(p) }
72 |
73 | // function is both a func and implements the apl.Function interface,
74 | // by calling itself.
75 | // It is used to wrap derived functions to satisfy apl.Function.
76 | type function func(*apl.Apl, apl.Value, apl.Value) (apl.Value, error)
77 |
78 | func (f function) Call(a *apl.Apl, l, r apl.Value) (apl.Value, error) {
79 | return f(a, l, r)
80 | }
81 |
--------------------------------------------------------------------------------
/apl/operators/select.go:
--------------------------------------------------------------------------------
1 | package operators
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/ktye/iv/apl"
7 | . "github.com/ktye/iv/apl/domain"
8 | )
9 |
10 | // selection returns a derived selection function, given an operator function that creates a derived function.
11 | func selection(op func(*apl.Apl, apl.Value, apl.Value) apl.Function) func(*apl.Apl, apl.Value, apl.Value, apl.Value, apl.Value) (apl.IntArray, error) {
12 | derived := func(a *apl.Apl, L, LO, RO, R apl.Value) (apl.IntArray, error) {
13 |
14 | // Create the derived function.
15 | df := op(a, LO, RO)
16 |
17 | // Create an index array with the shape of R.
18 | var ai apl.IntArray
19 | ar, ok := R.(apl.Array)
20 | if ok == false {
21 | return ai, fmt.Errorf("cannot select from %T", R)
22 | }
23 | ai.Dims = apl.CopyShape(ar)
24 | ai.Ints = make([]int, apl.Prod(ai.Dims))
25 | for i := range ai.Ints {
26 | ai.Ints[i] = i + 1
27 | }
28 |
29 | // Apply the selection function to the index array.
30 | v, err := df.Call(a, L, ai)
31 | if err != nil {
32 | return ai, err
33 | }
34 |
35 | to := ToIndexArray(nil)
36 | if av, ok := to.To(a, v); ok == false {
37 | return ai, fmt.Errorf("could not convert selection to index array: %T", v)
38 | } else {
39 | ai = av.(apl.IntArray)
40 | for i := range ai.Ints {
41 | ai.Ints[i]--
42 | }
43 | return ai, nil
44 | }
45 | }
46 | return derived
47 | }
48 |
49 | // selectSimple returns a general selection function for selective assignment.
50 | // It creates an index array of the same shape of R and applies f to it.
51 | // It is used by replicate and expand which behave like primitive functions instead of operators.
52 | // They take only 2 arguments.
53 | func selectSimple(f func(*apl.Apl, apl.Value, apl.Value) (apl.Value, error)) func(*apl.Apl, apl.Value, apl.Value, apl.Value, apl.Value) (apl.IntArray, error) {
54 | return func(a *apl.Apl, dummyL, L apl.Value, dummyRO, R apl.Value) (apl.IntArray, error) {
55 |
56 | // Create an index array with the shape of R.
57 | var ai apl.IntArray
58 | ar, ok := R.(apl.Array)
59 | if ok == false {
60 | return ai, fmt.Errorf("cannot select from %T", R)
61 | }
62 | ai.Dims = apl.CopyShape(ar)
63 | ai.Ints = make([]int, apl.Prod(ai.Dims))
64 | for i := range ai.Ints {
65 | ai.Ints[i] = i + 1
66 | }
67 |
68 | // Apply the selection function to it.
69 | v, err := f(a, L, ai)
70 | if err != nil {
71 | return ai, err
72 | }
73 |
74 | to := ToIndexArray(nil)
75 | if av, ok := to.To(a, v); ok == false {
76 | return ai, fmt.Errorf("could not convert selection to index array: %T", v)
77 | } else {
78 | // Fill elements will be reported as ¯1, which the assignment should ignore.
79 | ai = av.(apl.IntArray)
80 | for i := range ai.Ints {
81 | ai.Ints[i]--
82 | }
83 | return ai, nil
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/apl/primitives/axis.go:
--------------------------------------------------------------------------------
1 | package primitives
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/ktye/iv/apl"
7 | "github.com/ktye/iv/apl/domain"
8 | )
9 |
10 | // splitAxis returns ax.R and converts ax.A to []int taking account of index origin.
11 | // It R is not an axis it returns R and nil.
12 | func splitAxis(a *apl.Apl, R apl.Value) (apl.Value, []int, error) {
13 | ax, ok := R.(apl.Axis)
14 | if ok == false {
15 | return R, nil, nil
16 | }
17 | if _, ok := ax.A.(apl.EmptyArray); ok {
18 | return ax.R, nil, nil
19 | }
20 | to := domain.ToIndexArray(nil)
21 | X, ok := to.To(a, ax.A)
22 | if ok == false {
23 | return nil, nil, fmt.Errorf("axis is not an index array")
24 | }
25 | ar := X.(apl.IntArray)
26 | shape := ar.Shape()
27 | if len(shape) != 1 {
28 | return nil, nil, fmt.Errorf("axis has wrong shape: %d", len(shape))
29 | }
30 | x := make([]int, len(ar.Ints))
31 | for i, n := range ar.Ints {
32 | x[i] = int(n - a.Origin)
33 | }
34 | return ax.R, x, nil
35 | }
36 |
37 | // SplitCatAxis splits the right argument, if it contains an axis.
38 | // The axis must be a numeric scalar value or a single element array.
39 | // It may contain a fractional part.
40 | // If it does not exist, it is set to the last axis.
41 | // It returns the integer part of the axis and indicates if it is fractional.
42 | // The index origin is substracted.
43 | func splitCatAxis(a *apl.Apl, L, R apl.Value) (apl.Value, int, bool, error) {
44 | ax, ok := R.(apl.Axis)
45 | if ok == false {
46 | if ar, ok := R.(apl.Array); ok == false {
47 | return R, 0, false, nil
48 | } else {
49 | return R, len(ar.Shape()) - 1, false, nil
50 | }
51 | }
52 | R = ax.R
53 |
54 | // X∊⍳(⍴⍴L)⌈⍴⍴R
55 | rkL := 0
56 | al, ok := L.(apl.Array)
57 | if ok {
58 | rkL = len(al.Shape())
59 | }
60 |
61 | rkR := 0
62 | ar, ok := R.(apl.Array)
63 | if ok {
64 | rkR = len(ar.Shape())
65 | }
66 |
67 | max := rkL
68 | if rkR > max {
69 | max = rkR
70 | }
71 |
72 | var x apl.Value
73 | if xr, ok := ax.A.(apl.Array); ok {
74 | if xr.Size() != 1 {
75 | return nil, 0, false, fmt.Errorf(",: axis must be a scalar or single element array")
76 | } else {
77 | x = xr.At(0)
78 | }
79 | } else {
80 | x = ax.A
81 | }
82 | num, ok := x.(apl.Number)
83 | if ok == false {
84 | return nil, 0, false, fmt.Errorf("axis is not numeric")
85 | }
86 | if n, ok := num.ToIndex(); ok {
87 | n -= a.Origin
88 | if n < 0 || n >= max {
89 | return nil, 0, false, fmt.Errorf("axis is out of range")
90 | }
91 | return R, n, false, nil
92 | }
93 |
94 | // The axis is fractional, depending on the numerical tower.
95 | // Substract index origin from the axis.
96 | n := 0
97 | if fl, ok := num.(floorer); ok == false {
98 | return nil, 0, false, fmt.Errorf("cannot floor axis: %T", num)
99 | } else {
100 | if fnum, ok := fl.Floor(); ok == false {
101 | return nil, 0, false, fmt.Errorf("could not floor axis: %T", num)
102 | } else if i, ok := fnum.(apl.Number).ToIndex(); ok == false {
103 | return nil, 0, false, fmt.Errorf("|axis is not an index")
104 | } else {
105 | n = int(i)
106 | }
107 | }
108 |
109 | // Substract index origin.
110 | n -= int(a.Origin)
111 |
112 | // x must be between -1 and max.
113 | if n < -1 || n > max {
114 | return nil, 0, false, fmt.Errorf("axis must be: X∊⍳(⍴⍴L)⌈⍴⍴R")
115 | }
116 | return R, n, true, nil
117 | }
118 |
--------------------------------------------------------------------------------
/apl/primitives/boolean.go:
--------------------------------------------------------------------------------
1 | package primitives
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/ktye/iv/apl"
7 | . "github.com/ktye/iv/apl/domain"
8 | "github.com/ktye/iv/apl/operators"
9 | )
10 |
11 | func init() {
12 | tab := []struct {
13 | symbol string
14 | doc string
15 | logical string
16 | }{
17 | {"^", "logical and", "and"},
18 | {"∧", "logical and", "and"},
19 | {"∨", "logical or", "or"},
20 | {"⍲", "logical nand", "nand"},
21 | {"⍱", "logical nor", "nor"},
22 | }
23 |
24 | for _, e := range tab {
25 | register(primitive{
26 | symbol: e.symbol,
27 | doc: e.doc,
28 | Domain: Dyadic(Split(IsScalar(nil), IsScalar(nil))),
29 | fn: arith2(e.symbol, logical(e.logical)),
30 | })
31 | register(primitive{
32 | symbol: e.symbol,
33 | doc: e.doc,
34 | Domain: arrays{},
35 | fn: array2(e.symbol, logical(e.logical)),
36 | })
37 | }
38 | register(primitive{
39 | symbol: "~",
40 | doc: "logical not",
41 | Domain: Monadic(IsScalar(nil)),
42 | fn: arith1("~", logicalNot),
43 | })
44 | register(primitive{
45 | symbol: "~",
46 | doc: "logical not",
47 | Domain: Monadic(IsArray(nil)),
48 | fn: array1("~", logicalNot),
49 | })
50 | register(primitive{
51 | symbol: "~",
52 | doc: "without, excluding",
53 | Domain: Dyadic(Split(ToVector(nil), ToVector(nil))),
54 | fn: without,
55 | })
56 | }
57 |
58 | // logical not, R is a Number.
59 | func logicalNot(a *apl.Apl, R apl.Value) (apl.Value, bool) {
60 | b, ok := a.Tower.ToBool(R.(apl.Number))
61 | if ok == false {
62 | return nil, false
63 | }
64 | return apl.Bool(!b), true
65 | }
66 |
67 | func logical(logical string) func(*apl.Apl, apl.Value, apl.Value) (apl.Value, bool) {
68 | return func(a *apl.Apl, L, R apl.Value) (apl.Value, bool) {
69 | l, lok := a.Tower.ToBool(L.(apl.Number))
70 | r, rok := a.Tower.ToBool(R.(apl.Number))
71 | if lok == false || rok == false {
72 | if logical == "and" {
73 | return lcm(a, L, R)
74 | } else if logical == "or" {
75 | return gcd(a, L, R)
76 | }
77 | return nil, false
78 | }
79 | var t apl.Bool
80 | switch logical {
81 | case "and":
82 | t = l && r
83 | case "or":
84 | t = l || r
85 | case "nand":
86 | t = !(l && r)
87 | case "nor":
88 | t = !(l || r)
89 | default:
90 | panic(fmt.Sprintf("unknown logical: %s", logical))
91 | }
92 | return t, true
93 | }
94 | }
95 |
96 | // without: L and R are vectors.
97 | // L~R is equivalent to (~L∊R)/L.
98 | func without(a *apl.Apl, L, R apl.Value) (apl.Value, error) {
99 | if _, ok := R.(apl.EmptyArray); ok {
100 | return L, nil
101 | }
102 | if _, ok := L.(apl.EmptyArray); ok {
103 | return apl.EmptyArray{}, nil
104 | }
105 |
106 | lr, err := membership(a, L, R)
107 | if err != nil {
108 | return nil, err
109 | }
110 |
111 | not := arith1("~", logicalNot)
112 | if _, ok := L.(apl.Array); ok {
113 | not = array1("~", logicalNot)
114 | }
115 | nlr, err := not(a, nil, lr)
116 | if err != nil {
117 | return nil, err
118 | }
119 |
120 | to := ToIndexArray(nil)
121 | ia, ok := to.To(a, nlr)
122 | if ok == false {
123 | return nil, fmt.Errorf("without: cannot convert (~L∊R) to index array")
124 | }
125 |
126 | return operators.Replicate(a, ia, L, 0)
127 | }
128 |
--------------------------------------------------------------------------------
/apl/primitives/channel.go:
--------------------------------------------------------------------------------
1 | package primitives
2 |
3 | import (
4 | "fmt"
5 | "time"
6 |
7 | "github.com/ktye/iv/apl"
8 | "github.com/ktye/iv/apl/numbers"
9 | )
10 |
11 | // primitive < is defined in compare.go
12 |
13 | // channelSource sends any value R over a channel.
14 | // <[axis] R
15 | func channelSource(a *apl.Apl, _, R apl.Value) (apl.Value, error) {
16 | r, ax, err := splitAxis(a, R)
17 | if err != nil {
18 | return nil, fmt.Errorf("channel send: %s", err)
19 | }
20 | if len(ax) > 1 {
21 | return nil, fmt.Errorf("channel send: axis must be scalar")
22 | }
23 |
24 | if _, ok := r.(apl.Channel); ok {
25 | return nil, fmt.Errorf("channel send: right argument is a channel")
26 | }
27 |
28 | n := 1
29 | if ax != nil {
30 | n = ax[0]
31 | }
32 | n += int(a.Origin) // splitAxis substracts the origin.
33 |
34 | c := apl.NewChannel()
35 | if n == 0 {
36 | // Send only once, but do not close any channels.
37 | go func(v apl.Value) {
38 | c[0] <- v
39 | }(r)
40 | return c, nil
41 | }
42 |
43 | // Send v n times. If n is negative send until c[1] is closed.
44 | go func(v apl.Value, n int) {
45 | defer close(c[0])
46 | i := 0
47 | for {
48 | select {
49 | case _, ok := <-c[1]:
50 | if ok == false {
51 | return
52 | }
53 | case c[0] <- v:
54 | i++
55 | if n > 0 && i >= n {
56 | return
57 | }
58 | }
59 | }
60 | }(r, n)
61 | return c, nil
62 | }
63 |
64 | // channelCopy connects two channels. It writes to L what it reads from R.
65 | // The function returns the number of values copied.
66 | func channelCopy(a *apl.Apl, L, R apl.Value) (apl.Value, error) {
67 | l := L.(apl.Channel)
68 | r := R.(apl.Channel)
69 | // Should this run in a go-routine and return directly?
70 | defer close(l[0])
71 | ret := apl.EmptyArray{}
72 | for {
73 | select {
74 | case _, ok := <-l[1]:
75 | if ok == false {
76 | close(r[1])
77 | return ret, nil
78 | }
79 | case v, ok := <-r[0]:
80 | if ok == false {
81 | return ret, nil
82 | }
83 | select {
84 | case _, ok := <-l[1]:
85 | if ok == false {
86 | close(r[1])
87 | return ret, nil
88 | }
89 | case l[0] <- v:
90 | }
91 | }
92 | }
93 | }
94 |
95 | // channelDelay returns a channel that sends at fixed intervals what it receives.
96 | func channelDelay(a *apl.Apl, L, R apl.Value) (apl.Value, error) {
97 | d, ok := L.(numbers.Time).Duration()
98 | if ok == false {
99 | return nil, fmt.Errorf("channel delay: left argument is not a duration: %T", L)
100 | }
101 | in := R.(apl.Channel)
102 | out := in.Apply(a, Delay(d), nil, false)
103 | return out, nil
104 | }
105 |
106 | // Delay is a function that pauses execution for a given duration.
107 | // It is currently not bound to a primitive and only used by channel-delay.
108 | type Delay time.Duration
109 |
110 | func (d Delay) Call(a *apl.Apl, _, R apl.Value) (apl.Value, error) {
111 | time.Sleep(time.Duration(d))
112 | return R, nil
113 | }
114 |
115 | // channel1 applies the monadic elementary function to each value in a channel.
116 | func channel1(symbol string, fn func(*apl.Apl, apl.Value) (apl.Value, bool)) func(*apl.Apl, apl.Value, apl.Value) (apl.Value, error) {
117 | return func(a *apl.Apl, _, R apl.Value) (apl.Value, error) {
118 | c := R.(apl.Channel)
119 | return c.Apply(a, apl.Primitive(symbol), nil, false), nil
120 | }
121 | }
122 |
123 | // channel2 applies the dyadic elementary function to each value in a channel.
124 | func channel2(symbol string, fn func(*apl.Apl, apl.Value, apl.Value) (apl.Value, bool)) func(*apl.Apl, apl.Value, apl.Value) (apl.Value, error) {
125 | return func(a *apl.Apl, L, R apl.Value) (apl.Value, error) {
126 | c := R.(apl.Channel)
127 | return c.Apply(a, apl.Primitive(symbol), L, false), nil
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/apl/primitives/compare.go:
--------------------------------------------------------------------------------
1 | package primitives
2 |
3 | import (
4 | "reflect"
5 | "time"
6 |
7 | "github.com/ktye/iv/apl"
8 | . "github.com/ktye/iv/apl/domain"
9 | "github.com/ktye/iv/apl/numbers"
10 | )
11 |
12 | func init() {
13 | tab := []struct {
14 | symbol, doc string
15 | }{
16 | {"=", "equality"},
17 | {"<", "less that"},
18 | {">", "greater than"},
19 | {"≠", "not equal"},
20 | {"≤", "less or equal"},
21 | {"≥", "greater or equal"},
22 | }
23 | for _, e := range tab {
24 | register(primitive{
25 | symbol: e.symbol,
26 | doc: e.doc,
27 | Domain: Dyadic(Split(IsScalar(nil), IsScalar(nil))),
28 | fn: arith2(e.symbol, compare(e.symbol)),
29 | })
30 | register(primitive{
31 | symbol: e.symbol,
32 | doc: e.doc,
33 | Domain: arrays{},
34 | fn: array2(e.symbol, compare(e.symbol)),
35 | })
36 | }
37 |
38 | register(primitive{
39 | symbol: "<",
40 | doc: "channel send, source",
41 | Domain: Monadic(nil),
42 | fn: channelSource, // channel.go
43 | })
44 | register(primitive{
45 | symbol: "<",
46 | doc: "channel copy, connect",
47 | Domain: Dyadic(Split(IsChannel(nil), IsChannel(nil))),
48 | fn: channelCopy, // channel.go
49 | })
50 | register(primitive{
51 | symbol: "<",
52 | doc: "channel delay",
53 | Domain: Dyadic(Split(IsType(reflect.TypeOf(numbers.Time(time.Time{})), nil), IsChannel(nil))),
54 | fn: channelDelay, // channel.go
55 | })
56 | }
57 |
58 | func compare(symbol string) func(*apl.Apl, apl.Value, apl.Value) (apl.Value, bool) {
59 | return func(a *apl.Apl, L apl.Value, R apl.Value) (apl.Value, bool) {
60 | switch symbol {
61 | case "=":
62 | return equals(L, R)
63 | case "<":
64 | return less(L, R)
65 | case ">":
66 | eq, ls, ok := equalless(L, R)
67 | if ok == false {
68 | return nil, false
69 | }
70 | return apl.Bool(!eq && !ls), true
71 | case "≠":
72 | eq, ok := equals(L, R)
73 | if ok == false {
74 | return nil, false
75 | }
76 | return apl.Bool(!eq), true
77 | case "≤":
78 | eq, ls, ok := equalless(L, R)
79 | if ok == false {
80 | return nil, false
81 | }
82 | return apl.Bool(eq || ls), true
83 | case "≥":
84 | eq, ls, ok := equalless(L, R)
85 | if ok == false {
86 | return nil, false
87 | }
88 | return apl.Bool(eq || !ls), true
89 | }
90 | return nil, false
91 | }
92 | }
93 |
94 | func equalless(L, R apl.Value) (apl.Bool, apl.Bool, bool) {
95 | eq, ok := equals(L, R)
96 | if ok == false {
97 | return false, false, false
98 | }
99 | ls, ok := less(L, R)
100 | if ok == false {
101 | return false, false, false
102 | }
103 | return eq, ls, true
104 | }
105 | func equals(L, R apl.Value) (apl.Bool, bool) {
106 | if eq, ok := L.(equaler); ok {
107 | return eq.Equals(R)
108 | }
109 | return apl.Bool(L == R), true
110 | }
111 |
112 | type equaler interface {
113 | Equals(apl.Value) (apl.Bool, bool)
114 | }
115 |
116 | func less(L, R apl.Value) (apl.Bool, bool) {
117 | if ls, ok := L.(lesser); ok {
118 | return ls.Less(R)
119 | }
120 | return false, false
121 | }
122 |
123 | type lesser interface {
124 | Less(apl.Value) (apl.Bool, bool)
125 | }
126 |
--------------------------------------------------------------------------------
/apl/primitives/dict.go:
--------------------------------------------------------------------------------
1 | package primitives
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/ktye/iv/apl"
7 | . "github.com/ktye/iv/apl/domain"
8 | "github.com/ktye/iv/apl/xgo"
9 | )
10 |
11 | func init() {
12 | register(primitive{
13 | symbol: "#",
14 | doc: "keys, methods",
15 | Domain: Monadic(nil),
16 | fn: keys,
17 | })
18 | register(primitive{
19 | symbol: "#",
20 | doc: "dict",
21 | Domain: Dyadic(nil),
22 | fn: dict,
23 | })
24 | }
25 |
26 | // keys: R: object
27 | func keys(a *apl.Apl, _, R apl.Value) (apl.Value, error) {
28 | methods := false
29 | if _, ok := R.(apl.Axis); ok {
30 | methods = true
31 | if r, _, err := splitAxis(a, R); err != nil {
32 | return nil, err
33 | } else {
34 | R = r
35 | }
36 | }
37 | obj, ok := R.(apl.Object)
38 | if ok == false {
39 | return nil, fmt.Errorf("keys: expected object: %T", R)
40 | }
41 | if methods {
42 | o, ok := obj.(xgo.Value)
43 | if ok == false {
44 | return nil, fmt.Errorf("methods: expected xgo.Value: %T", obj)
45 | }
46 | s := o.Methods()
47 | if s == nil {
48 | return apl.EmptyArray{}, nil
49 | }
50 | return apl.StringArray{Dims: []int{len(s)}, Strings: s}, nil
51 | } else {
52 | keyval := obj.Keys()
53 | values := make([]apl.Value, len(keyval))
54 | for i, v := range keyval {
55 | values[i] = v.Copy()
56 | }
57 | return a.UnifyArray(apl.MixedArray{
58 | Dims: []int{len(values)},
59 | Values: values,
60 | }), nil
61 | }
62 | }
63 |
64 | func dict(a *apl.Apl, L, R apl.Value) (apl.Value, error) {
65 | al, ok := L.(apl.Array)
66 |
67 | if ok == false {
68 | return &apl.Dict{
69 | K: []apl.Value{L.Copy()},
70 | M: map[apl.Value]apl.Value{
71 | L: R.Copy(),
72 | },
73 | }, nil
74 | }
75 |
76 | ls := al.Shape()
77 | if len(ls) != 1 {
78 | return nil, fmt.Errorf("dict: left argument must be a vector")
79 | }
80 |
81 | ar, ok := R.(apl.Array)
82 | if ok == false {
83 | mr := apl.NewMixed([]int{ls[0]})
84 | for i := range mr.Values {
85 | mr.Values[i] = R.Copy()
86 | }
87 | ar = mr
88 | }
89 | rs := ar.Shape()
90 | if len(rs) != 1 || rs[0] != ls[0] {
91 | return nil, fmt.Errorf("dict: left and right arguments do not conform")
92 | }
93 |
94 | k := make([]apl.Value, al.Size())
95 | m := make(map[apl.Value]apl.Value)
96 | for i := 0; i < al.Size(); i++ {
97 | l := al.At(i).Copy()
98 | m[l] = ar.At(i).Copy()
99 | k[i] = l
100 | }
101 | return &apl.Dict{
102 | K: k,
103 | M: m,
104 | }, nil
105 | }
106 |
--------------------------------------------------------------------------------
/apl/primitives/doc_test.go:
--------------------------------------------------------------------------------
1 | package primitives
2 |
3 | import (
4 | "io"
5 | "os"
6 | "testing"
7 | "text/tabwriter"
8 |
9 | "github.com/ktye/iv/apl"
10 | "github.com/ktye/iv/apl/numbers"
11 | "github.com/ktye/iv/apl/operators"
12 | aplstrings "github.com/ktye/iv/apl/strings"
13 | )
14 |
15 | func TestDoc(t *testing.T) {
16 | if testing.Short() {
17 | a := apl.New(os.Stdout)
18 | numbers.Register(a)
19 | Register(a)
20 | operators.Register(a)
21 | aplstrings.Register(a, "s")
22 |
23 | var w io.Writer = os.Stdout
24 | tw := tabwriter.NewWriter(w, 2, 0, 2, ' ', 0)
25 | a.Doc(tw)
26 | tw.Flush()
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/apl/primitives/enclose.go:
--------------------------------------------------------------------------------
1 | package primitives
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/ktye/iv/apl"
7 | . "github.com/ktye/iv/apl/domain"
8 | )
9 |
10 | func init() {
11 | register(primitive{
12 | symbol: "⊂",
13 | doc: "enclose, string catenation",
14 | Domain: Monadic(strvec{}),
15 | fn: strcat,
16 | })
17 | register(primitive{
18 | symbol: "⊂",
19 | doc: "join strings",
20 | Domain: Dyadic(Split(IsString(nil), strvec{})),
21 | fn: strjoin,
22 | })
23 | register(primitive{
24 | symbol: "⊃",
25 | doc: "split runes",
26 | Domain: Monadic(IsString(nil)),
27 | fn: runesplit,
28 | })
29 | register(primitive{
30 | symbol: "⊃",
31 | doc: "first",
32 | Domain: Monadic(IsList(nil)),
33 | fn: first,
34 | })
35 | register(primitive{
36 | symbol: "⊃",
37 | doc: "split string",
38 | Domain: Dyadic(Split(IsString(nil), IsString(nil))),
39 | fn: strsplit,
40 | })
41 | }
42 |
43 | // strvec accepts an array if all elements are strings.
44 | // The result is a string vector.
45 | type strvec struct{}
46 |
47 | func (s strvec) To(a *apl.Apl, v apl.Value) (apl.Value, bool) {
48 | ar, ok := v.(apl.Array)
49 | if ok == false {
50 | return v, false
51 | }
52 | vec := apl.StringArray{Dims: []int{ar.Size()}, Strings: make([]string, ar.Size())}
53 | for i := 0; i < vec.Size(); i++ {
54 | if s, ok := ar.At(i).(apl.String); ok {
55 | vec.Strings[i] = string(s)
56 | } else {
57 | return v, false
58 | }
59 | }
60 | return vec, true
61 | }
62 | func (s strvec) String(f apl.Format) string {
63 | return "array of strings"
64 | }
65 |
66 | func strcat(a *apl.Apl, L, R apl.Value) (apl.Value, error) {
67 | ar := R.(apl.StringArray)
68 | return apl.String(strings.Join(ar.Strings, "")), nil
69 | }
70 |
71 | func strjoin(a *apl.Apl, L, R apl.Value) (apl.Value, error) {
72 | v := R.(apl.StringArray)
73 | return apl.String(strings.Join(v.Strings, string(L.(apl.String)))), nil
74 | }
75 |
76 | func runesplit(a *apl.Apl, _, R apl.Value) (apl.Value, error) {
77 | r := []rune(string(R.(apl.String)))
78 | v := make([]string, len(r))
79 | for i, s := range r {
80 | v[i] = string(s)
81 | }
82 | return apl.StringArray{Dims: []int{len(v)}, Strings: v}, nil
83 | }
84 |
85 | // split a string to an string vector
86 | // If L is a string, use it as the separator for strings.Split
87 | // If L is "", use strings.Field
88 | func strsplit(a *apl.Apl, L, R apl.Value) (apl.Value, error) {
89 | l := L.(apl.String)
90 | r := R.(apl.String)
91 | var v []string
92 | if l == "" {
93 | v = strings.Fields(string(r))
94 | } else {
95 | v = strings.Split(string(r), string(l))
96 | }
97 | return apl.StringArray{Dims: []int{len(v)}, Strings: v}, nil
98 | }
99 |
100 | func first(a *apl.Apl, _, R apl.Value) (apl.Value, error) {
101 | r := R.(apl.List)
102 | if len(r) == 0 {
103 | return apl.EmptyArray{}, nil
104 | }
105 | return r[0], nil
106 | }
107 |
--------------------------------------------------------------------------------
/apl/primitives/find.go:
--------------------------------------------------------------------------------
1 | package primitives
2 |
3 | import (
4 | "github.com/ktye/iv/apl"
5 | . "github.com/ktye/iv/apl/domain"
6 | )
7 |
8 | func init() {
9 | register(primitive{
10 | symbol: "⍷",
11 | doc: "find",
12 | Domain: Dyadic(Split(ToArray(nil), ToArray(nil))),
13 | fn: find,
14 | })
15 | }
16 |
17 | func find(a *apl.Apl, L, R apl.Value) (apl.Value, error) {
18 | if _, ok := R.(apl.EmptyArray); ok {
19 | return apl.EmptyArray{}, nil
20 | }
21 |
22 | var al apl.Array
23 | el, Lempty := L.(apl.EmptyArray)
24 | if Lempty == false {
25 | al = L.(apl.Array)
26 | } else {
27 | al = el
28 | }
29 | ls := al.Shape()
30 |
31 | ar := R.(apl.Array)
32 | rs := ar.Shape()
33 |
34 | res := apl.BoolArray{Dims: apl.CopyShape(ar)}
35 | res.Bools = make([]bool, apl.Prod(res.Dims))
36 |
37 | // If the rank of L is arger than the rank of R, nothing is found.
38 | if len(ls) > len(rs) {
39 | return res, nil
40 | }
41 |
42 | // If the rank of L is smaller than the rank of R, fill it with ones
43 | // at the beginning.
44 | if d := len(rs) - len(ls); d > 0 {
45 | shape := apl.CopyShape(ar)
46 | for i := range shape {
47 | if i < d {
48 | shape[i] = 1
49 | } else {
50 | shape[i] = ls[i-d]
51 | }
52 | }
53 | l := apl.MakeArray(al, shape)
54 | for i := 0; i < l.Size(); i++ {
55 | l.Set(i, al.At(i).Copy())
56 | }
57 | al = l
58 | ls = shape
59 | }
60 | nl := al.Size()
61 |
62 | feq := arith2("=", compare("="))
63 | ic, idx := apl.NewIdxConverter(rs)
64 | for i := range res.Bools {
65 | if nl > len(res.Bools)-i {
66 | res.Bools[i] = false
67 | } else {
68 | iseq := true
69 | for k := 0; k < len(idx); k++ {
70 | idx[k] = 0
71 | }
72 | for k := 0; k < nl; k++ {
73 | eq, err := feq(a, al.At(k), ar.At(i+ic.Index(idx)))
74 | if err != nil {
75 | return nil, err
76 | }
77 | if eq.(apl.Bool) == false {
78 | iseq = false
79 | break
80 | }
81 | apl.IncArrayIndex(idx, ls)
82 | }
83 | if iseq {
84 | res.Bools[i] = true
85 | }
86 | }
87 | }
88 | return res, nil
89 | }
90 |
--------------------------------------------------------------------------------
/apl/primitives/gen.go:
--------------------------------------------------------------------------------
1 | // +build ignore
2 |
3 | package main
4 |
5 | import (
6 | "bufio"
7 | "fmt"
8 | "log"
9 | "os"
10 | "os/exec"
11 | "regexp"
12 | "strings"
13 | "time"
14 | )
15 |
16 | var prefix = regexp.MustCompile(` *apl_test.go:[0-9]*:`)
17 | var spaces = regexp.MustCompile(`^ *`)
18 |
19 | // This program is run by go generate.
20 | // It runs go test -v in short mode, which only includes
21 | // tests with the normal numeric tower.
22 | // The output of go test is filtered and written to Tests.md.
23 | func main() {
24 | mktest()
25 | mkref()
26 | }
27 |
28 | func mktest() {
29 |
30 | cmd := exec.Command("go", "test", "-v", "-short", "-run", "Normal")
31 | testout, err := cmd.StdoutPipe()
32 | if err != nil {
33 | log.Fatal(err)
34 | }
35 | if err := cmd.Start(); err != nil {
36 | log.Fatal(err)
37 | }
38 |
39 | w, err := os.Create("../../TESTS.md")
40 | if err != nil {
41 | log.Fatal(err)
42 | }
43 | defer w.Close()
44 |
45 | scn := bufio.NewScanner(testout)
46 | for scn.Scan() {
47 | s := scn.Text()
48 | if strings.HasPrefix(s, "===") {
49 | continue
50 | }
51 | if strings.HasPrefix(s, "---") {
52 | continue
53 | }
54 | s = prefix.ReplaceAllString(s, "")
55 | s = spaces.ReplaceAllString(s, "")
56 | fmt.Fprintln(w, s)
57 | }
58 |
59 | if err := cmd.Wait(); err != nil {
60 | log.Fatal(err)
61 | }
62 | fmt.Fprintf(w, "```\n")
63 | }
64 |
65 | func mkref() {
66 | cmd := exec.Command("go", "test", "-v", "-short", "-run", "Doc")
67 | testout, err := cmd.StdoutPipe()
68 | if err != nil {
69 | log.Fatal(err)
70 | }
71 | if err := cmd.Start(); err != nil {
72 | log.Fatal(err)
73 | }
74 |
75 | w, err := os.Create("../../REF.md")
76 | if err != nil {
77 | log.Fatal(err)
78 | }
79 | defer w.Close()
80 |
81 | fmt.Fprintln(w, `# Reference
82 | - [Primitive Functions](#primitive-functions)
83 | - [Operators](#operators)
84 | `)
85 |
86 | scn := bufio.NewScanner(testout)
87 | for scn.Scan() {
88 | s := scn.Text()
89 | if strings.HasPrefix(s, "===") {
90 | continue
91 | }
92 | if strings.HasPrefix(s, "---") {
93 | continue
94 | }
95 | fmt.Fprintln(w, s)
96 | }
97 |
98 | if err := cmd.Wait(); err != nil {
99 | log.Fatal(err)
100 | }
101 | fmt.Fprintf(w, "\ngenerated by `go generate (apl/primitives/gen.go)` %s\n", time.Now().Format("2006-01-02 15:04:05"))
102 | }
103 |
--------------------------------------------------------------------------------
/apl/primitives/match.go:
--------------------------------------------------------------------------------
1 | package primitives
2 |
3 | import (
4 | "reflect"
5 |
6 | "github.com/ktye/iv/apl"
7 | . "github.com/ktye/iv/apl/domain"
8 | )
9 |
10 | func init() {
11 | register(primitive{
12 | symbol: "≡",
13 | doc: "depth, level of nesting",
14 | Domain: Monadic(nil),
15 | fn: depth,
16 | })
17 | register(primitive{
18 | symbol: "≢",
19 | doc: "tally, number of major cells",
20 | Domain: Monadic(nil),
21 | fn: tally,
22 | })
23 |
24 | register(primitive{
25 | symbol: "≡",
26 | doc: "match",
27 | Domain: Dyadic(nil),
28 | fn: match,
29 | })
30 | register(primitive{
31 | symbol: "≢",
32 | doc: "not match",
33 | Domain: Dyadic(nil),
34 | fn: notmatch,
35 | })
36 | }
37 |
38 | // depth reports the level of nesting.
39 | // Nested arrays are not supported, so depth is always 1 for arrays and 0 for scalars.
40 | func depth(a *apl.Apl, _, R apl.Value) (apl.Value, error) {
41 | if l, ok := R.(apl.List); ok {
42 | return apl.Int(l.Depth()), nil
43 | }
44 | if _, ok := R.(apl.Array); ok {
45 | return apl.Int(1), nil
46 | }
47 | return apl.Int(0), nil
48 | }
49 |
50 | // tally returns the number of major cells of R.
51 | // It is equlivalent to {⍬⍴(⍴⍵),1}.
52 | func tally(a *apl.Apl, _, R apl.Value) (apl.Value, error) {
53 | if t, ok := R.(apl.Table); ok {
54 | return apl.Int(t.Rows), nil
55 | }
56 | if o, ok := R.(apl.Object); ok {
57 | return apl.Int(len(o.Keys())), nil
58 | }
59 | ar, ok := R.(apl.Array)
60 | if ok == false {
61 | return apl.Int(1), nil
62 | }
63 | shape := ar.Shape()
64 | if len(shape) == 0 {
65 | return apl.Int(0), nil
66 | }
67 | return apl.Int(shape[0]), nil
68 | }
69 |
70 | func match(a *apl.Apl, L, R apl.Value) (apl.Value, error) {
71 | al, isal := L.(apl.Array)
72 | ar, isar := R.(apl.Array)
73 | if isal != isar {
74 | return apl.Bool(false), nil
75 | }
76 | if isal == false {
77 | // Compare scalars, convert numbers to the same type.
78 | return apl.Bool(isEqual(a, L, R)), nil
79 | } else {
80 | sl := al.Shape()
81 | sr := ar.Shape()
82 | if len(sr) != len(sl) {
83 | return apl.Bool(false), nil
84 | } else if len(sr) == 0 {
85 | // Empty arrays must have the same type.
86 | if reflect.TypeOf(ar) == reflect.TypeOf(al) {
87 | return apl.Bool(true), nil
88 | } else {
89 | return apl.Bool(false), nil
90 | }
91 | }
92 | for i := range sl {
93 | if sl[i] != sr[i] {
94 | return apl.Bool(false), nil
95 | }
96 | }
97 | feq := arith2("=", compare("="))
98 | for i := 0; i < ar.Size(); i++ {
99 | if iseq, err := feq(a, ar.At(i), al.At(i)); err != nil {
100 | return nil, err
101 | } else if iseq.(apl.Bool) == false {
102 | return apl.Bool(false), nil
103 | }
104 | }
105 | return apl.Bool(true), nil
106 | }
107 | }
108 |
109 | func notmatch(a *apl.Apl, L, R apl.Value) (apl.Value, error) {
110 | if eq, err := match(a, L, R); err != nil {
111 | return nil, err
112 | } else {
113 | return !(eq.(apl.Bool)), nil
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/apl/primitives/query.go:
--------------------------------------------------------------------------------
1 | package primitives
2 |
3 | import (
4 | "fmt"
5 | "math/rand"
6 |
7 | "github.com/ktye/iv/apl"
8 | . "github.com/ktye/iv/apl/domain"
9 | "github.com/ktye/iv/apl/numbers"
10 | )
11 |
12 | func init() {
13 | register(primitive{
14 | symbol: "?",
15 | doc: "roll, rand, randn, bi-randn",
16 | Domain: Monadic(nil),
17 | fn: roll,
18 | })
19 | register(primitive{
20 | symbol: "?",
21 | doc: "deal",
22 | Domain: Dyadic(Split(ToScalar(ToIndex(nil)), ToScalar(ToIndex(nil)))),
23 | fn: deal,
24 | })
25 | }
26 |
27 | // roll returns a number or an array of the same shape as R.
28 | // Values of R must be numbers
29 | func roll(a *apl.Apl, _, R apl.Value) (apl.Value, error) {
30 | ar, ok := R.(apl.Array)
31 | if ok == false {
32 | if z, ok := R.(apl.Number); ok == false {
33 | return nil, fmt.Errorf("roll expectes a numeric array as the right argument")
34 | } else {
35 | if n, err := rollNumber(a, z); err != nil {
36 | return nil, err
37 | } else {
38 | return n, nil
39 | }
40 | }
41 | }
42 | if ar.Size() == 0 {
43 | return apl.EmptyArray{}, nil
44 | }
45 | res := apl.NewMixed(apl.CopyShape(ar))
46 | for i := range res.Values {
47 | if z, ok := ar.At(i).(apl.Number); ok == false {
48 | return nil, fmt.Errorf("roll: array value is not numeric")
49 | } else {
50 | if n, err := rollNumber(a, z); err != nil {
51 | return nil, err
52 | } else {
53 | res.Values[i] = n
54 | }
55 | }
56 | }
57 | return a.UnifyArray(res), nil
58 | }
59 |
60 | // rollNumber returns a random integer upto n, which must be integer.
61 | // If n is 0, it returns a random float between 0 and 1.
62 | // If n is negative it returns a random number from a normal distribution with std←|n.
63 | // If n is complex, it returns a random number from a bivariate normal distribution
64 | // with normal parameters given by the real an imag part.
65 | //
66 | // TODO: seed. Currently random numbers are not seeded (equivalent to Seed(1))
67 | // and always return the same values.
68 | func rollNumber(a *apl.Apl, n apl.Number) (apl.Number, error) {
69 | if f, ok := n.(numbers.Float); ok && float64(f) < 0 {
70 | return numbers.Float(float64(f) * rand.NormFloat64()), nil
71 | }
72 | if z, ok := n.(numbers.Complex); ok {
73 | return numbers.Complex(complex(real(z)*rand.NormFloat64(), imag(z)*rand.NormFloat64())), nil
74 | }
75 | m, ok := n.ToIndex()
76 | if ok == false || m < 0 {
77 | return numbers.Float(float64(m) * rand.NormFloat64()), nil
78 | }
79 | if m == 0 {
80 | // TODO: should we exclude 0?
81 | f := rand.Float64()
82 | return numbers.Float(f), nil // This only works with the default tower.
83 | } else {
84 | return a.Tower.Import(apl.Int(rand.Intn(m) + a.Origin)), nil
85 | }
86 | }
87 |
88 | // deal selects L random numbers from ⍳R without repetition.
89 | func deal(a *apl.Apl, L, R apl.Value) (apl.Value, error) {
90 | // TODO []RL (random link)
91 | n := int(L.(apl.Int))
92 | m := int(R.(apl.Int))
93 | if n <= 0 || m < n {
94 | return nil, fmt.Errorf("deal: L must be > 0 and R >= L")
95 | }
96 | p := rand.Perm(m)
97 | p = p[:n]
98 | for i := range p {
99 | p[i] += a.Origin
100 | }
101 | return apl.IntArray{
102 | Ints: p,
103 | Dims: []int{n},
104 | }, nil
105 | }
106 |
--------------------------------------------------------------------------------
/apl/primitives/register.go:
--------------------------------------------------------------------------------
1 | package primitives
2 |
3 | import (
4 | "fmt"
5 | "runtime"
6 | "strings"
7 |
8 | "github.com/ktye/iv/apl"
9 | )
10 |
11 | func Register(a *apl.Apl) {
12 | for _, p := range primitives {
13 | a.RegisterPrimitive(apl.Primitive(p.symbol), p)
14 | }
15 | }
16 |
17 | var primitives []primitive
18 |
19 | func register(p primitive) {
20 | // Add source path to documentation.
21 | _, fn, line, _ := runtime.Caller(1)
22 | if idx := strings.Index(fn, "apl/primitives"); idx != -1 {
23 | fn = fn[idx:]
24 | }
25 | p.doc += fmt.Sprintf("\t%s:%d", fn, line)
26 |
27 | primitives = append(primitives, p)
28 | }
29 |
30 | type primitive struct {
31 | apl.Domain
32 | symbol string
33 | doc string
34 | fn func(*apl.Apl, apl.Value, apl.Value) (apl.Value, error)
35 | sel func(*apl.Apl, apl.Value, apl.Value) (apl.IntArray, error)
36 | }
37 |
38 | func (p primitive) Call(a *apl.Apl, L, R apl.Value) (apl.Value, error) { return p.fn(a, L, R) }
39 | func (p primitive) Select(a *apl.Apl, L, R apl.Value) (apl.IntArray, error) {
40 | if p.sel == nil {
41 | return apl.IntArray{}, fmt.Errorf("primitive %s cannot be used in selective assignment", p.symbol)
42 | }
43 | return p.sel(a, L, R)
44 | }
45 | func (p primitive) Doc() string { return p.doc }
46 |
--------------------------------------------------------------------------------
/apl/primitives/rho.go:
--------------------------------------------------------------------------------
1 | package primitives
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/ktye/iv/apl"
7 | . "github.com/ktye/iv/apl/domain"
8 | )
9 |
10 | func init() {
11 | register(primitive{
12 | symbol: "⍴",
13 | doc: "shape",
14 | Domain: Monadic(nil),
15 | fn: rho1,
16 | })
17 | register(primitive{
18 | symbol: "⍴",
19 | doc: "reshape",
20 | Domain: Dyadic(Split(ToVector(ToIndexArray(nil)), ToArray(nil))),
21 | fn: rho2,
22 | sel: selection(rho2),
23 | })
24 | register(primitive{
25 | symbol: "⍴",
26 | doc: "reshape channel",
27 | Domain: Dyadic(Split(ToVector(ToIndexArray(nil)), IsChannel(nil))),
28 | fn: rhoChannel,
29 | })
30 | }
31 |
32 | // Rho1 returns the shape of R.
33 | func rho1(a *apl.Apl, _, R apl.Value) (apl.Value, error) {
34 | // Report a table as a two dimensional array.
35 | if t, ok := R.(apl.Table); ok == true {
36 | return apl.IntArray{
37 | Dims: []int{2},
38 | Ints: []int{t.Rows, len(t.K)},
39 | }, nil
40 | }
41 | // An object returns the number of keys.
42 | if o, ok := R.(apl.Object); ok == true {
43 | n := len(o.Keys())
44 | return apl.IntArray{Dims: []int{1}, Ints: []int{n}}, nil
45 | }
46 |
47 | if _, ok := R.(apl.Array); ok == false {
48 | return apl.EmptyArray{}, nil
49 | }
50 | // Shape of an empty array is 0, rank is 1
51 | if _, ok := R.(apl.EmptyArray); ok {
52 | return apl.IntArray{Ints: []int{0}, Dims: []int{1}}, nil
53 | }
54 | ar := R.(apl.Array)
55 | shape := ar.Shape()
56 | ret := apl.IntArray{
57 | Ints: make([]int, len(shape)),
58 | Dims: []int{len(shape)},
59 | }
60 | copy(ret.Ints, shape)
61 | return ret, nil
62 | }
63 |
64 | // Rho2 is dyadic reshape, L is empty or index array, R is array.
65 | func rho2(a *apl.Apl, L, R apl.Value) (apl.Value, error) {
66 | // L is empty, returns empty.
67 | if L.(apl.Array).Size() == 0 {
68 | return apl.EmptyArray{}, nil
69 | }
70 |
71 | if _, ok := R.(apl.Object); ok {
72 | return nil, fmt.Errorf("cannot reshape %T", R)
73 | }
74 |
75 | l := L.(apl.IntArray)
76 | shape := make([]int, len(l.Ints))
77 | copy(shape, l.Ints)
78 | if rs, ok := R.(apl.Reshaper); ok {
79 | return rs.Reshape(shape), nil
80 | }
81 | return nil, fmt.Errorf("cannot reshape %T", R)
82 | }
83 |
84 | // rhoChannel returns a channel and sends arrays with the shape of L.
85 | // Values are read from a channel R.
86 | func rhoChannel(a *apl.Apl, L, R apl.Value) (apl.Value, error) {
87 | if L.(apl.Array).Size() == 0 {
88 | return R, nil
89 | }
90 | al := L.(apl.IntArray)
91 | size := apl.Prod(al.Ints)
92 | newarray := func() apl.MixedArray {
93 | s := make([]int, len(al.Ints))
94 | copy(s, al.Ints)
95 | return apl.NewMixed(s)
96 | }
97 | res := newarray()
98 |
99 | in := R.(apl.Channel)
100 | out := apl.NewChannel()
101 | go func() {
102 | p := 0
103 | defer close(out[0])
104 | push := func(v apl.Value) {
105 | res.Values[p] = v
106 | p++
107 | if p == size {
108 | select {
109 | case _, ok := <-out[1]:
110 | if ok == false {
111 | close(in[1])
112 | return
113 | }
114 | case out[0] <- res:
115 | res = newarray()
116 | p = 0
117 | }
118 | }
119 | }
120 | for {
121 | select {
122 | case _, ok := <-out[1]:
123 | if ok == false {
124 | close(in[1])
125 | return
126 | }
127 | case v, ok := <-in[0]:
128 | if ok == false {
129 | return
130 | }
131 | if ar, ok := v.(apl.Array); ok {
132 | for i := 0; i < ar.Size(); i++ {
133 | push(ar.At(i))
134 | }
135 | } else {
136 | push(v)
137 | }
138 | }
139 | }
140 | }()
141 | return out, nil
142 | }
143 |
--------------------------------------------------------------------------------
/apl/primitives/selection.go:
--------------------------------------------------------------------------------
1 | package primitives
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/ktye/iv/apl"
7 | "github.com/ktye/iv/apl/domain"
8 | )
9 |
10 | // Selection returns a general selection function for selective assignment.
11 | // It creates an index array of the same shape of R and applies f to it.
12 | func selection(f func(*apl.Apl, apl.Value, apl.Value) (apl.Value, error)) func(*apl.Apl, apl.Value, apl.Value) (apl.IntArray, error) {
13 | return func(a *apl.Apl, L apl.Value, R apl.Value) (apl.IntArray, error) {
14 | // Create an index array with the shape of R.
15 | var ai apl.IntArray
16 | ar, ok := R.(apl.Array)
17 | if ok == false {
18 | return ai, fmt.Errorf("cannot select from %T", R)
19 | }
20 | ai.Dims = apl.CopyShape(ar)
21 | ai.Ints = make([]int, ar.Size())
22 | for i := range ai.Ints {
23 | ai.Ints[i] = i
24 | }
25 |
26 | // Apply the selection function to it.
27 | v, err := f(a, L, ai)
28 | if err != nil {
29 | return ai, err
30 | }
31 |
32 | to := domain.ToIndexArray(nil)
33 | if av, ok := to.To(a, v); ok == false {
34 | return ai, fmt.Errorf("could not convert selection to index array: %T", v)
35 | } else {
36 | return av.(apl.IntArray), nil
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/apl/primitives/tack.go:
--------------------------------------------------------------------------------
1 | package primitives
2 |
3 | import (
4 | "github.com/ktye/iv/apl"
5 | . "github.com/ktye/iv/apl/domain"
6 | )
7 |
8 | func init() {
9 | register(primitive{
10 | symbol: "⊣",
11 | doc: "left tack, same",
12 | Domain: Monadic(nil),
13 | fn: same,
14 | })
15 | register(primitive{
16 | symbol: "⊢",
17 | doc: "right tack, same",
18 | Domain: Monadic(nil),
19 | fn: same,
20 | })
21 | register(primitive{
22 | symbol: "⊣",
23 | doc: "left tack, left argument",
24 | Domain: Dyadic(nil),
25 | fn: left,
26 | })
27 | register(primitive{
28 | symbol: "⊢",
29 | doc: "right tack, right argument",
30 | Domain: Dyadic(nil),
31 | fn: right,
32 | })
33 | }
34 |
35 | func same(a *apl.Apl, _, R apl.Value) (apl.Value, error) {
36 | return R, nil
37 | }
38 | func left(a *apl.Apl, L, _ apl.Value) (apl.Value, error) {
39 | return L, nil
40 | }
41 | func right(a *apl.Apl, _, R apl.Value) (apl.Value, error) {
42 | return R, nil
43 | }
44 |
--------------------------------------------------------------------------------
/apl/primitives/type.go:
--------------------------------------------------------------------------------
1 | package primitives
2 |
3 | import (
4 | "fmt"
5 | "reflect"
6 |
7 | "github.com/ktye/iv/apl"
8 | . "github.com/ktye/iv/apl/domain"
9 | )
10 |
11 | func init() {
12 | register(primitive{
13 | symbol: "⌶",
14 | doc: "type",
15 | Domain: Monadic(nil),
16 | fn: typeof,
17 | })
18 | register(primitive{
19 | symbol: "⌶",
20 | doc: "convert to type",
21 | Domain: Dyadic(nil),
22 | fn: convert,
23 | })
24 | register(primitive{
25 | symbol: "⌶",
26 | doc: "convert to named type",
27 | Domain: Dyadic(Split(IsString(nil), nil)),
28 | fn: convert2,
29 | })
30 | }
31 |
32 | func typeof(a *apl.Apl, _, R apl.Value) (apl.Value, error) {
33 | return apl.String(reflect.TypeOf(R).String()), nil
34 | }
35 |
36 | // I-beam is a nice symbol for conversion, as it represents an encode-decode pair.
37 |
38 | // convert is called with a values of the destination type on the left.
39 | func convert(a *apl.Apl, L, R apl.Value) (apl.Value, error) {
40 | // L can be a prototype, similar to the left argument of format.
41 | // TODO: use domain.ToType.
42 | return nil, fmt.Errorf("TODO: convert to destination type")
43 | }
44 |
45 | // convert is called with the name of the target type given as a string on the left argument.
46 | func convert2(a *apl.Apl, L, R apl.Value) (apl.Value, error) {
47 | // TODO: L could be the string that monadic ibeam prints or
48 | // a prototype name similar to the left argument of format.
49 | s := L.(apl.String)
50 | switch s {
51 | case "img":
52 | to := ToImage(nil)
53 | if m, ok := to.To(a, R); ok {
54 | return m, nil
55 | }
56 | return nil, fmt.Errorf("cannot convert to image: %T", R)
57 | default:
58 | return nil, fmt.Errorf("convert: %T to %s is not supported", R, s)
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/apl/primitives/unique.go:
--------------------------------------------------------------------------------
1 | package primitives
2 |
3 | import (
4 | "github.com/ktye/iv/apl"
5 | . "github.com/ktye/iv/apl/domain"
6 | )
7 |
8 | func init() {
9 | register(primitive{
10 | symbol: "∪",
11 | doc: "unique",
12 | Domain: Monadic(ToVector(nil)),
13 | fn: unique,
14 | })
15 | register(primitive{
16 | symbol: "∪",
17 | doc: "union",
18 | Domain: Dyadic(Split(ToVector(nil), ToVector(nil))),
19 | fn: union,
20 | })
21 | }
22 |
23 | // unique: R is a vector.
24 | // DyRef gives an example of an array: ∪3 4 5⍴⍳20
25 | // which fails in tryapl.
26 | // ISO also allows only vectors.
27 | func unique(a *apl.Apl, _, R apl.Value) (apl.Value, error) {
28 | ar := R.(apl.Array)
29 |
30 | var values []apl.Value
31 | for i := 0; i < ar.Size(); i++ {
32 | v := ar.At(i)
33 | u := true
34 | for k := range values {
35 | if isEqual(a, v, values[k]) {
36 | u = false
37 | break
38 | }
39 | }
40 | if u {
41 | values = append(values, v.Copy())
42 | }
43 | }
44 | return a.UnifyArray(apl.MixedArray{Values: values, Dims: []int{len(values)}}), nil
45 | }
46 |
47 | // union of L and R, both are vectors.
48 | func union(a *apl.Apl, L, R apl.Value) (apl.Value, error) {
49 | if _, ok := L.(apl.EmptyArray); ok {
50 | return R, nil
51 | }
52 | if _, ok := R.(apl.EmptyArray); ok {
53 | return L, nil
54 | }
55 | al := L.(apl.Array)
56 | ar := R.(apl.Array)
57 |
58 | var values []apl.Value
59 | appendvec := func(vec apl.Array) error {
60 | for i := 0; i < vec.Size(); i++ {
61 | v := vec.At(i)
62 | u := true
63 | for k := range values {
64 | if isEqual(a, v, values[k]) == true {
65 | u = false
66 | break
67 | }
68 | }
69 | if u {
70 | values = append(values, v.Copy())
71 | }
72 | }
73 | return nil
74 | }
75 | if err := appendvec(al); err != nil {
76 | return nil, err
77 | }
78 | if err := appendvec(ar); err != nil {
79 | return nil, err
80 | }
81 | return a.UnifyArray(apl.MixedArray{Dims: []int{len(values)}, Values: values}), nil
82 | }
83 |
--------------------------------------------------------------------------------
/apl/rpc/README.md:
--------------------------------------------------------------------------------
1 | # rpc package
2 |
3 | The package provides client and server interfaces for communication between distributed
4 | APL instances.
5 |
6 | ## Server
7 | An server could be implemented as:
8 |
9 | ```go
10 | package main
11 |
12 | import (
13 | "github.com/ktye/iv/apl"
14 | "github.com/ktye/iv/apl/numbers"
15 | "github.com/ktye/iv/apl/operators"
16 | "github.com/ktye/iv/apl/primitives"
17 | "github.com/ktye/iv/apl/rpc"
18 | )
19 |
20 | func main() {
21 | a := apl.New(nil)
22 | numbers.Register(a)
23 | primitives.Register(a)
24 | operators.Register(a)
25 | rpc.Register(a, "")
26 |
27 | rpc.ListenAndServe(a, ":1966")
28 | }
29 |
30 | ```
31 | When running, it listens on port 1966 for connections.
32 |
33 | ## Client
34 | On a different process, run a normal APL session:
35 |
36 | ```
37 | C←rpc→dial ":1966"
38 | rpc→call (C; "+/"; 5; ⍳10;)
39 | 15 20 25 30 35 40
40 | ```
41 |
42 | The rpc call evaluates the function string in the remote environment
43 | and calls it with the local values on the remote process.
44 |
45 | APL value that should be transfered over the wire need to be
46 | registerd to the gob package.
47 | See `init.go` for values that are already registerd.
48 |
--------------------------------------------------------------------------------
/apl/rpc/init.go:
--------------------------------------------------------------------------------
1 | package rpc
2 |
3 | import (
4 | "encoding/gob"
5 |
6 | "github.com/ktye/iv/apl"
7 | "github.com/ktye/iv/apl/numbers"
8 | )
9 |
10 | func init() {
11 | // Register types for communication.
12 | gob.Register(apl.Bool(false))
13 | gob.Register(apl.Int(0))
14 | gob.Register(numbers.Float(0.0))
15 | gob.Register(numbers.Complex(0))
16 | gob.Register(apl.String(""))
17 | gob.Register(apl.List(nil))
18 | gob.Register(apl.MixedArray{})
19 | gob.Register(apl.IntArray{})
20 | gob.Register(apl.Bool(false))
21 | }
22 |
--------------------------------------------------------------------------------
/apl/rpc/register.go:
--------------------------------------------------------------------------------
1 | package rpc
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/ktye/iv/apl"
7 | )
8 |
9 | // Register adds the rpc package to the interpreter.
10 | // See README.md
11 | func Register(a *apl.Apl, name string) {
12 | pkg := map[string]apl.Value{
13 | "dial": apl.ToFunction(dial),
14 | "call": apl.ToFunction(call),
15 | "close": apl.ToFunction(closeconn),
16 | }
17 | if name == "" {
18 | name = "rpc"
19 | }
20 | a.RegisterPackage(name, pkg)
21 | }
22 |
23 | func dial(a *apl.Apl, L, R apl.Value) (apl.Value, error) {
24 | if L != nil {
25 | return nil, fmt.Errorf("rpc dial must be called monadically")
26 | }
27 | if s, ok := R.(apl.String); ok == false {
28 | return nil, fmt.Errorf("rpc dial: argument must be a string")
29 | } else {
30 | return Dial(string(s))
31 | }
32 | }
33 |
34 | func call(a *apl.Apl, L, R apl.Value) (apl.Value, error) {
35 | if L != nil {
36 | return nil, fmt.Errorf("rpc call must be called monadically")
37 | }
38 | lst, ok := R.(apl.List)
39 | if ok == false {
40 | return nil, fmt.Errorf("rpc call: argument must be a list: %T", R)
41 | }
42 | if len(lst) < 3 {
43 | return nil, fmt.Errorf("rpc call: argument list is too short")
44 | }
45 | if len(lst) > 4 {
46 | return nil, fmt.Errorf("rpc call: argument list is too long")
47 | }
48 | c, ok := lst[0].(Conn)
49 | if ok == false {
50 | return nil, fmt.Errorf("rpc call: first list argument must be a connection")
51 | }
52 | f, ok := lst[1].(apl.String)
53 | if ok == false {
54 | return nil, fmt.Errorf("rpc call: second list argument must be a string")
55 | }
56 |
57 | var Larg, Rarg apl.Value
58 | if len(lst) == 3 {
59 | Rarg = lst[2]
60 | } else {
61 | Larg = lst[2]
62 | Rarg = lst[3]
63 | }
64 | return c.Call(string(f), Larg, Rarg)
65 | }
66 |
67 | func closeconn(a *apl.Apl, _, R apl.Value) (apl.Value, error) {
68 | c, ok := R.(Conn)
69 | if ok == false {
70 | return nil, fmt.Errorf("right argument must be a connection")
71 | }
72 | return c.Close()
73 | }
74 |
--------------------------------------------------------------------------------
/apl/rpc/rpc.go:
--------------------------------------------------------------------------------
1 | // Package rpc adds remove procedure calls to APL.
2 | package rpc
3 |
4 | import (
5 | "encoding/gob"
6 | "fmt"
7 | "net"
8 |
9 | "github.com/ktye/iv/apl"
10 | )
11 |
12 | func Dial(address string) (Conn, error) {
13 | c, err := net.Dial("tcp", address)
14 | if err != nil {
15 | return Conn{}, err
16 | }
17 | return Conn{Conn: c}, nil
18 | }
19 |
20 | type Conn struct {
21 | net.Conn
22 | }
23 |
24 | func (c Conn) String(f apl.Format) string {
25 | remote := c.RemoteAddr()
26 | if remote == nil {
27 | return fmt.Sprintf("rpc→conn not connected")
28 | }
29 | return fmt.Sprintf("rpc→conn to %s", remote.String())
30 | }
31 | func (c Conn) Copy() apl.Value { return c }
32 |
33 | func (c Conn) Close() (apl.Value, error) {
34 | if c.Conn == nil {
35 | return nil, fmt.Errorf("not connected")
36 | }
37 | if err := c.Conn.Close(); err != nil {
38 | return nil, err
39 | }
40 | return apl.Int(1), nil
41 | }
42 |
43 | func (c Conn) Call(f string, L, R apl.Value) (apl.Value, error) {
44 | req := Request{Fn: f, L: L, R: R}
45 | if err := gob.NewEncoder(c.Conn).Encode(req); err != nil {
46 | c.Conn.Close()
47 | return nil, err
48 | }
49 | var res Response
50 | if err := gob.NewDecoder(c.Conn).Decode(&res); err != nil {
51 | c.Conn.Close()
52 | return nil, err
53 | }
54 | if res.Err != "" {
55 | return nil, fmt.Errorf("%s", res.Err)
56 | } else if res.V == nil {
57 | return nil, fmt.Errorf("empty result")
58 | }
59 | return res.V, nil
60 | }
61 |
--------------------------------------------------------------------------------
/apl/rpc/server.go:
--------------------------------------------------------------------------------
1 | package rpc
2 |
3 | import (
4 | "encoding/gob"
5 | "fmt"
6 | "log"
7 | "net"
8 |
9 | "github.com/ktye/iv/apl"
10 | )
11 |
12 | // ListenAndServe puts APL into server mode.
13 | // It accepts a single connection at a time from anyone.
14 | func ListenAndServe(a *apl.Apl, addr string) {
15 | ln, err := net.Listen("tcp", addr)
16 | if err != nil {
17 | log.Fatal(err)
18 | }
19 | defer ln.Close()
20 |
21 | log.Print("listen on ", addr)
22 | for {
23 | conn, err := ln.Accept()
24 | if err != nil {
25 | log.Print(err)
26 | } else {
27 | handle(a, conn)
28 | }
29 | }
30 | }
31 |
32 | type Request struct {
33 | Fn string
34 | L, R apl.Value
35 | }
36 |
37 | type Response struct {
38 | Err string
39 | V apl.Value
40 | }
41 |
42 | func handle(a *apl.Apl, cn net.Conn) {
43 | log.Print("conn ", cn.RemoteAddr())
44 | var req Request
45 | for {
46 | var res Response
47 | if err := gob.NewDecoder(cn).Decode(&req); err != nil {
48 | res.Err = err.Error()
49 | } else {
50 | if v, err := exec(a, req); err != nil {
51 | res.Err = err.Error()
52 | } else {
53 | res.V = v
54 | }
55 | }
56 | if err := gob.NewEncoder(cn).Encode(res); err != nil {
57 | log.Print(err)
58 | cn.Close()
59 | return
60 | }
61 | }
62 | }
63 |
64 | func exec(a *apl.Apl, req Request) (apl.Value, error) {
65 | if req.R == nil {
66 | return nil, fmt.Errorf("right argument is nil")
67 | }
68 | if p, err := a.Parse(req.Fn); err != nil {
69 | return nil, err
70 | } else if len(p) != 1 {
71 | return nil, fmt.Errorf("expected a single function expression: got %d", len(p))
72 | } else if v, err := p[0].Eval(a); err != nil {
73 | return nil, err
74 | } else if f, ok := v.(apl.Function); ok == false {
75 | return nil, fmt.Errorf("expr is not a function")
76 | } else {
77 | return f.Call(a, req.L, req.R)
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/apl/string.go:
--------------------------------------------------------------------------------
1 | package apl
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "io/ioutil"
7 | "reflect"
8 | )
9 |
10 | type String string
11 |
12 | // String prints a string.
13 | func (s String) String(f Format) string {
14 | fs := f.Fmt[reflect.TypeOf(s)]
15 | if fs == "" {
16 | if f.PP < 0 {
17 | fs = "%q"
18 | }
19 | }
20 | if fs == "" {
21 | return string(s)
22 | }
23 | return fmt.Sprintf(fs, string(s))
24 | }
25 |
26 | func (s String) Copy() Value {
27 | return s
28 | }
29 |
30 | func (s String) Eval(a *Apl) (Value, error) {
31 | return s, nil
32 | }
33 |
34 | func (s String) ReadFrom(a *Apl, r io.Reader) (Value, error) {
35 | b, err := ioutil.ReadAll(r)
36 | if err != nil {
37 | return nil, err
38 | }
39 | return String(b), nil
40 | }
41 |
42 | // Less implements primitives.lesser to be used for comparison and sorting.
43 | func (s String) Less(r Value) (Bool, bool) {
44 | b, ok := r.(String)
45 | if ok == false {
46 | return false, false
47 | }
48 | return s < b, true
49 | }
50 |
51 | func (s String) Export() reflect.Value {
52 | return reflect.ValueOf(string(s))
53 | }
54 |
55 | // StringArray is a uniform array of strings.
56 | type StringArray struct {
57 | Dims []int
58 | Strings []string
59 | }
60 |
61 | func (s StringArray) String(f Format) string {
62 | return ArrayString(f, s)
63 | }
64 |
65 | func (s StringArray) Copy() Value {
66 | r := StringArray{Dims: CopyShape(s), Strings: make([]string, len(s.Strings))}
67 | copy(r.Strings, s.Strings)
68 | return r
69 | }
70 |
71 | func (s StringArray) At(i int) Value {
72 | return String(s.Strings[i])
73 | }
74 |
75 | func (s StringArray) Shape() []int {
76 | return s.Dims
77 | }
78 |
79 | func (s StringArray) Size() int {
80 | return len(s.Strings)
81 | }
82 |
83 | func (s StringArray) Zero() Value {
84 | return String("")
85 | }
86 |
87 | func (s StringArray) Set(i int, v Value) error {
88 | if i < 0 || i > len(s.Strings) {
89 | return fmt.Errorf("index out of range")
90 | }
91 | if c, ok := v.(String); ok {
92 | s.Strings[i] = string(c)
93 | return nil
94 | }
95 | return fmt.Errorf("cannot assign %T to StringArray", v)
96 | }
97 |
98 | func (s StringArray) Make(shape []int) Uniform {
99 | return StringArray{
100 | Dims: shape,
101 | Strings: make([]string, Prod(shape)),
102 | }
103 | }
104 |
105 | func makeStringArray(v []Value) StringArray {
106 | str := make([]string, len(v))
107 | for i, e := range v {
108 | str[i] = string(e.(String))
109 | }
110 | return StringArray{
111 | Dims: []int{len(v)},
112 | Strings: str,
113 | }
114 | }
115 |
116 | func (s StringArray) Reshape(shape []int) Value {
117 | res := StringArray{
118 | Dims: shape,
119 | Strings: make([]string, Prod(shape)),
120 | }
121 | k := 0
122 | for i := range res.Strings {
123 | res.Strings[i] = s.Strings[k]
124 | k++
125 | if k == len(s.Strings) {
126 | k = 0
127 | }
128 | }
129 | return res
130 | }
131 |
--------------------------------------------------------------------------------
/apl/strings/register.go:
--------------------------------------------------------------------------------
1 | // Package strings provides go string functions
2 | package strings
3 |
4 | import (
5 | "reflect"
6 | "strings"
7 |
8 | "github.com/ktye/iv/apl"
9 | "github.com/ktye/iv/apl/xgo"
10 | )
11 |
12 | func Register(a *apl.Apl, name string) {
13 | if name == "" {
14 | name = "s"
15 | }
16 | pkg := map[string]apl.Value{
17 | "compare": xgo.Function{Name: "Compare", Fn: reflect.ValueOf(strings.Compare)},
18 | "contains": xgo.Function{Name: "Contains", Fn: reflect.ValueOf(strings.Contains)},
19 | "containsany": xgo.Function{Name: "ContainsAny", Fn: reflect.ValueOf(strings.ContainsAny)},
20 | "containsrune": xgo.Function{Name: "ContainsRune", Fn: reflect.ValueOf(strings.ContainsRune)},
21 | "count": xgo.Function{Name: "Count", Fn: reflect.ValueOf(strings.Count)},
22 | "equalfold": xgo.Function{Name: "EqualFold", Fn: reflect.ValueOf(strings.EqualFold)},
23 | "fields": xgo.Function{Name: "Fields", Fn: reflect.ValueOf(strings.Fields)},
24 | "fieldsfunc": xgo.Function{Name: "FieldsFunc", Fn: reflect.ValueOf(strings.FieldsFunc)},
25 | "hasprefix": xgo.Function{Name: "HasPrefix", Fn: reflect.ValueOf(strings.HasPrefix)},
26 | "hassuffix": xgo.Function{Name: "HasSuffix", Fn: reflect.ValueOf(strings.HasSuffix)},
27 | "index": xgo.Function{Name: "Index", Fn: reflect.ValueOf(strings.Index)},
28 | "indexany": xgo.Function{Name: "IndexAny", Fn: reflect.ValueOf(strings.IndexAny)},
29 | "indexbyte": xgo.Function{Name: "IndexByte", Fn: reflect.ValueOf(strings.IndexByte)},
30 | "indexfunc": xgo.Function{Name: "IndexFunc", Fn: reflect.ValueOf(strings.IndexFunc)},
31 | "indexrune": xgo.Function{Name: "IndexRune", Fn: reflect.ValueOf(strings.IndexRune)},
32 | "join": xgo.Function{Name: "Join", Fn: reflect.ValueOf(strings.Join)},
33 | "lastindex": xgo.Function{Name: "LastIndex", Fn: reflect.ValueOf(strings.LastIndex)},
34 | "lastindexany": xgo.Function{Name: "LastIndexAny", Fn: reflect.ValueOf(strings.LastIndexAny)},
35 | "lastindexbyte": xgo.Function{Name: "LastIndexByte", Fn: reflect.ValueOf(strings.LastIndexByte)},
36 | "repeat": xgo.Function{Name: "Repeat", Fn: reflect.ValueOf(strings.Repeat)},
37 | "replace": xgo.Function{Name: "Replace", Fn: reflect.ValueOf(strings.Replace)},
38 | "split": xgo.Function{Name: "Split", Fn: reflect.ValueOf(strings.Split)},
39 | "splitafter": xgo.Function{Name: "SplitAfter", Fn: reflect.ValueOf(strings.SplitAfter)},
40 | "splitaftern": xgo.Function{Name: "SplitAfterN", Fn: reflect.ValueOf(strings.SplitAfterN)},
41 | "splitn": xgo.Function{Name: "SplitN", Fn: reflect.ValueOf(strings.SplitN)},
42 | "title": xgo.Function{Name: "Title", Fn: reflect.ValueOf(strings.Title)},
43 | "tolower": xgo.Function{Name: "ToLower", Fn: reflect.ValueOf(strings.ToLower)},
44 | "tolowerspecial": xgo.Function{Name: "ToLowerSpecial", Fn: reflect.ValueOf(strings.ToLowerSpecial)},
45 | "totitle": xgo.Function{Name: "ToTitle", Fn: reflect.ValueOf(strings.ToTitle)},
46 | "totitlespecial": xgo.Function{Name: "ToTitleSpecial", Fn: reflect.ValueOf(strings.ToTitleSpecial)},
47 | "toupper": xgo.Function{Name: "ToUpper", Fn: reflect.ValueOf(strings.ToUpper)},
48 | "toupperspecial": xgo.Function{Name: "ToUpperSpecial", Fn: reflect.ValueOf(strings.ToUpperSpecial)},
49 | "trim": xgo.Function{Name: "Trim", Fn: reflect.ValueOf(strings.Trim)},
50 | "trimleft": xgo.Function{Name: "TrimLeft", Fn: reflect.ValueOf(strings.TrimLeft)},
51 | "trimprefix": xgo.Function{Name: "TrimPrefix", Fn: reflect.ValueOf(strings.TrimPrefix)},
52 | "trimright": xgo.Function{Name: "TrimRight", Fn: reflect.ValueOf(strings.TrimRight)},
53 | "trimspace": xgo.Function{Name: "TrimSpace", Fn: reflect.ValueOf(strings.TrimSpace)},
54 | "trimsuffix": xgo.Function{Name: "TrimSuffix", Fn: reflect.ValueOf(strings.TrimSuffix)},
55 | }
56 | a.RegisterPackage(name, pkg)
57 | }
58 |
--------------------------------------------------------------------------------
/apl/train.go:
--------------------------------------------------------------------------------
1 | package apl
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | )
7 |
8 | // Train is a function train expression.
9 | // It does not contain it's arguments like a Primitive or a fnVar.
10 | // See DyaProg p 25.
11 | type train []expr
12 |
13 | func (t train) Eval(a *Apl) (Value, error) {
14 | return t, nil
15 | }
16 |
17 | func (t train) String(f Format) string {
18 | v := make([]string, len(t))
19 | for i := range t {
20 | v[i] = t[i].String(f)
21 | }
22 | return "(" + strings.Join(v, ", ") + ")"
23 | }
24 | func (t train) Copy() Value { return t }
25 |
26 | func (t train) Call(a *Apl, L, R Value) (Value, error) {
27 | if len(t) < 2 {
28 | return nil, fmt.Errorf("cannot call short train, length %d", len(t))
29 | } else if len(t)%2 == 0 {
30 | // even number: f g h i j k → f(g h(i j k)) ⍝ atop(fork(fork))
31 | f := atop{}
32 | end := 1
33 | if len(t) == 2 {
34 | end = 2
35 | }
36 | for i, e := range t[:end] {
37 | if v, err := e.Eval(a); err != nil {
38 | return nil, err
39 | } else {
40 | f[i] = v
41 | }
42 | }
43 | if len(t) > 3 {
44 | f[1] = train(t[1:])
45 | }
46 | return f.Call(a, L, R)
47 | } else {
48 | // odd number: e f g h i j k → e f(g h(i j k)) ⍝ fork(fork(fork))
49 | f := fork{}
50 | end := 2
51 | if len(t) == 3 {
52 | end = 3
53 | }
54 | for i, e := range t[:end] {
55 | if v, err := e.Eval(a); err != nil {
56 | return nil, err
57 | } else {
58 | f[i] = v
59 | }
60 | }
61 | if len(t) > 3 {
62 | f[2] = train(t[2:])
63 | }
64 | return f.Call(a, L, R)
65 | }
66 | }
67 |
68 | type atop [2]Value
69 |
70 | func (t atop) String(f Format) string {
71 | return fmt.Sprintf("(%s %s)", t[0].String(f), t[1].String(f))
72 | }
73 |
74 | func (t atop) Call(a *Apl, L, R Value) (Value, error) {
75 | g, ok := t[0].(Function)
76 | if ok == false {
77 | return nil, fmt.Errorf("atop: expected function g: %T", t[0])
78 | }
79 | h, ok := t[1].(Function)
80 | if ok == false {
81 | return nil, fmt.Errorf("atop: expected function h: %T", t[1])
82 | }
83 |
84 | // L may be nil.
85 | v, err := h.Call(a, L, R)
86 | if err != nil {
87 | return nil, err
88 | }
89 | return g.Call(a, nil, v)
90 | }
91 |
92 | type fork [3]Value
93 |
94 | func (fk fork) String(f Format) string {
95 | return fmt.Sprintf("(%s %s %s)", fk[0].String(f), fk[1].String(f), fk[2].String(f))
96 | }
97 |
98 | func (fk fork) Call(a *Apl, L, R Value) (Value, error) {
99 | f, fok := fk[0].(Function)
100 |
101 | g, ok := fk[1].(Function)
102 | if ok == false {
103 | return nil, fmt.Errorf("fork: expected function g: %T", fk[1])
104 | }
105 | h, ok := fk[2].(Function)
106 | if ok == false {
107 | return nil, fmt.Errorf("fork: expected function h: %T", fk[2])
108 | }
109 |
110 | // Agh fork if f is not a function.
111 | var l Value
112 | l = fk[0]
113 | if fok {
114 | if v, err := f.Call(a, L, R); err != nil { // TODO copy?
115 | return nil, err
116 | } else {
117 | l = v
118 | }
119 | }
120 | r, err := h.Call(a, L, R) // TODO copy?
121 | if err != nil {
122 | return nil, err
123 | }
124 | return g.Call(a, l, r)
125 | }
126 |
--------------------------------------------------------------------------------
/apl/uniform.go:
--------------------------------------------------------------------------------
1 | package apl
2 |
3 | import (
4 | "fmt"
5 | "reflect"
6 | )
7 |
8 | // Unify tries to convert the array to a uniform array, if possible.
9 | // If uptype is true, it uptypes numeric values, if that helps.
10 | func (a *Apl) Unify(A Array, uptype bool) (resultarray Array, resultok bool) {
11 | if _, ok := A.(EmptyArray); ok {
12 | return A, false // An empty array is defined to be not uniform.
13 | }
14 | if _, ok := A.(Uniform); ok {
15 | return A, true
16 | }
17 |
18 | noNumber := -10
19 | boolType := reflect.TypeOf(Bool(false))
20 | indexType := reflect.TypeOf(Int(0))
21 | class := func(t reflect.Type) int {
22 | n, ok := a.Tower.Numbers[t]
23 | if ok == false {
24 | if t == indexType {
25 | return -1
26 | } else if t == boolType {
27 | return -2
28 | }
29 | return noNumber
30 | }
31 | return n.Class
32 | }
33 |
34 | // If all values of the array are the same type, it is uniform.
35 | // This includes a List who's primary values are lists as well.
36 | size := A.Size()
37 | if size < 1 {
38 | return A, true
39 | }
40 | v0 := A.At(0)
41 | t0 := reflect.TypeOf(v0)
42 | max := class(t0)
43 | var maxnumber Number
44 | if max != noNumber {
45 | maxnumber = v0.(Number)
46 | }
47 | sametype := true
48 | for i := 1; i < size; i++ {
49 | v := A.At(i)
50 | t := reflect.TypeOf(v)
51 | if t != t0 {
52 | sametype = false
53 | if uptype == false {
54 | return A, false
55 | }
56 | }
57 | if max != noNumber {
58 | if c := class(t); c == noNumber {
59 | max = noNumber
60 | } else if c > max {
61 | max = c
62 | maxnumber = v.(Number)
63 | }
64 | }
65 | }
66 |
67 | // All values have the same type.
68 | // Try to convert them to a compact uniform type.
69 | if sametype {
70 | // Some uniform types are defined in the numeric implementation.
71 | // E.g. numbers/{FloatArray;ComplexArray;TimeArray}.
72 | if max != noNumber {
73 | var values []Value
74 | switch v := A.(type) {
75 | case MixedArray:
76 | values = v.Values
77 | case List:
78 | values = []Value(v)
79 | default:
80 | return A, true
81 | }
82 | if u, ok := a.Tower.Uniform(values); ok == false {
83 | return A, true
84 | } else if rs, ok := u.(Reshaper); ok {
85 | return rs.Reshape(CopyShape(A)).(Array), true
86 | }
87 | }
88 | // Some uniform types are defined in array.go.
89 | if t0 == reflect.TypeOf(String("")) {
90 | ar := StringArray{}.Make(CopyShape(A))
91 | for i := 0; i < A.Size(); i++ {
92 | v := A.At(i)
93 | ar.(StringArray).Strings[i] = string(v.(String))
94 | }
95 | return ar, true
96 | } else if t0 == reflect.TypeOf(Bool(false)) {
97 | ar := BoolArray{}.Make(CopyShape(A))
98 | for i := 0; i < A.Size(); i++ {
99 | v := A.At(i)
100 | ar.(BoolArray).Bools[i] = bool(v.(Bool))
101 | }
102 | return ar, true
103 | } else if t0 == reflect.TypeOf(Int(0)) {
104 | ar := IntArray{}.Make(CopyShape(A))
105 | for i := 0; i < A.Size(); i++ {
106 | v := A.At(i)
107 | ar.(IntArray).Ints[i] = int(v.(Int))
108 | }
109 | return ar, true
110 | }
111 | // Unknown uniform type is returnd as it is.
112 | return A, true
113 | } else if max == noNumber {
114 | // If values are not of the same type, and not identified by
115 | // the current tower, there is no chance to make them equal.
116 | return A, false
117 | }
118 |
119 | // Try to uptype all values to the same number type.
120 | values := make([]Value, size)
121 | var err error
122 | for i := 0; i < size; i++ {
123 | v := A.At(i)
124 | values[i], _, err = a.Tower.SameType(v.(Number), maxnumber)
125 | if err != nil {
126 | fmt.Println(err)
127 | return A, false
128 | }
129 | }
130 | if u, ok := a.Tower.Uniform(values); ok {
131 | if rs, ok := u.(Reshaper); ok {
132 | return rs.Reshape(CopyShape(A)).(Array), true
133 | }
134 | }
135 | return A, false
136 | }
137 |
138 | // UnifyArray tries to unify the input array without uptyping.
139 | func (a *Apl) UnifyArray(A Array) Array {
140 | u, _ := a.Unify(A, false)
141 | return u
142 | }
143 |
--------------------------------------------------------------------------------
/apl/value.go:
--------------------------------------------------------------------------------
1 | package apl
2 |
3 | import "io"
4 |
5 | // Value is the result of an evaluation.
6 | // Any type that implements the interface is a valid type for apl.
7 | //
8 | // The String method is used to display the value.
9 | // It does not need to be unique or represent the input syntax.
10 | // Mosty types have no input respresentation at all.
11 | // They are the result of a computation.
12 | //
13 | // A Value may implement further interfaces such as Array, Uniform or Function.
14 | type Value interface {
15 | String(Format) string
16 | Copy() Value
17 | }
18 |
19 | // VarReader is implemented by Values that are able to parse from a Reader.
20 | // The ReadFrom method must return a new value of the same type.
21 | // The function should be able to parse the format of it's String method.
22 | // It is used by varfs in package io.
23 | type VarReader interface {
24 | ReadFrom(*Apl, io.Reader) (Value, error)
25 | }
26 |
--------------------------------------------------------------------------------
/apl/xgo/convert.go:
--------------------------------------------------------------------------------
1 | package xgo
2 |
3 | import (
4 | "fmt"
5 | "reflect"
6 |
7 | "github.com/ktye/iv/apl"
8 | "github.com/ktye/iv/apl/numbers"
9 | )
10 |
11 | // Exporter can be implemented by an apl.Value to be able convert it to a go value.
12 | type Exporter interface {
13 | Export() reflect.Value
14 | }
15 |
16 | // export converts an apl value to a go value.
17 | func export(v apl.Value, t reflect.Type) (reflect.Value, error) {
18 |
19 | if e, ok := v.(Exporter); ok {
20 | x := e.Export()
21 | if x.Type().ConvertibleTo(t) {
22 | return x.Convert(t), nil
23 | }
24 | }
25 |
26 | zero := reflect.Value{}
27 | switch t.Kind() {
28 |
29 | case reflect.Int:
30 | return reflect.ValueOf(int(v.(apl.Int))), nil
31 |
32 | case reflect.Float64:
33 | return reflect.ValueOf(float64(v.(numbers.Float))), nil
34 |
35 | case reflect.Complex128:
36 | return reflect.ValueOf(complex128(v.(numbers.Complex))), nil
37 |
38 | case reflect.String:
39 | return reflect.ValueOf(string(v.(apl.String))), nil
40 |
41 | case reflect.Slice:
42 | ar, ok := v.(apl.Array)
43 | if ok == false {
44 | return zero, fmt.Errorf("expected slice: %T", v)
45 | }
46 | et := t.Elem()
47 | n := ar.Size()
48 | s := reflect.MakeSlice(t, n, n)
49 | for i := 0; i < n; i++ {
50 | if e, err := export(ar.At(i), et); err != nil {
51 | return zero, err
52 | } else {
53 | se := s.Index(i)
54 | se.Set(e)
55 | }
56 | }
57 | return s, nil
58 |
59 | case reflect.Struct:
60 | if xv, ok := v.(Value); ok {
61 | st := reflect.Value(xv).Type()
62 | if st == reflect.PtrTo(t) {
63 | return reflect.Indirect(reflect.Value(xv)), nil
64 | } else if st == t {
65 | return reflect.Value(xv), nil
66 | }
67 | }
68 | return zero, fmt.Errorf("xgo: export struct: cannot convert %T to %s", v, t)
69 |
70 | default:
71 | return zero, fmt.Errorf("cannot convert to %v (%s)", t, t.Kind())
72 | }
73 | }
74 |
75 | // convert converts a go value to an apl value.
76 | func Convert(v reflect.Value) (apl.Value, error) {
77 | switch v.Kind() {
78 | case reflect.Int:
79 | return apl.Int(int(v.Int())), nil
80 |
81 | case reflect.Uint:
82 | return apl.Int(int(v.Uint())), nil
83 |
84 | case reflect.Float64:
85 | return numbers.Float(v.Float()), nil
86 |
87 | case reflect.Complex128:
88 | return numbers.Complex(v.Complex()), nil
89 |
90 | case reflect.String:
91 | return apl.String(v.String()), nil
92 |
93 | case reflect.Slice:
94 | n := v.Len()
95 | ar := apl.NewMixed([]int{n})
96 | for i := range ar.Values {
97 | if e, err := Convert(v.Index(i)); err != nil {
98 | return nil, err
99 | } else {
100 | ar.Values[i] = e
101 | }
102 | }
103 | return ar, nil
104 |
105 | case reflect.Struct:
106 | return Value(v.Addr()), nil // TODO: populate
107 |
108 | default:
109 | return nil, fmt.Errorf("cannot convert %s to an apl value", v.Kind())
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/apl/xgo/function.go:
--------------------------------------------------------------------------------
1 | package xgo
2 |
3 | import (
4 | "fmt"
5 | "reflect"
6 |
7 | "github.com/ktye/iv/apl"
8 | )
9 |
10 | type Function struct {
11 | Name string
12 | Fn reflect.Value
13 | }
14 |
15 | func (f Function) String(af apl.Format) string {
16 | return f.Name
17 | }
18 | func (f Function) Copy() apl.Value { return f }
19 |
20 | // Call a go function.
21 | // If it requires 1 argument, that is taken from the right value.
22 | // Two arguments may be the right and left argument or a vector of 2 arguments.
23 | // More than two arguments must be passed in a vector of the right size.
24 | // If the function returns an error as the last value, it is checked and returned.
25 | // Otherwise, or if the error is nil the result is converted and returned.
26 | // More than one result will be returned as a List.
27 | func (f Function) Call(a *apl.Apl, L, R apl.Value) (apl.Value, error) {
28 | errarg := func(i int, err error) error {
29 | return fmt.Errorf("function %s argument %d: %s", f.Name, i+1, err)
30 | }
31 | t := f.Fn.Type()
32 | args := t.NumIn()
33 | in := make([]reflect.Value, args)
34 | var err error
35 | if args == 0 {
36 | } else if args == 1 {
37 | in[0], err = export(R, t.In(0))
38 | if err != nil {
39 | return nil, errarg(0, err)
40 | }
41 | } else if args == 2 && L != nil {
42 | in[0], err = export(R, t.In(0))
43 | if err != nil {
44 | return nil, errarg(0, err)
45 | }
46 | in[1], err = export(L, t.In(1))
47 | if err != nil {
48 | return nil, errarg(1, err)
49 | }
50 | } else if L == nil {
51 | ar, ok := R.(apl.Array)
52 | if ok == false {
53 | return nil, fmt.Errorf("function %s requires %d arguments", f.Name, args)
54 | }
55 | rs := ar.Shape()
56 | if len(rs) > 1 {
57 | return nil, fmt.Errorf("function argument has rank %d", len(rs))
58 | }
59 | if n := ar.Size(); n != args {
60 | return nil, fmt.Errorf("function %s requires %d arguments, R has size %d", f.Name, args, n)
61 | } else {
62 | for i := 0; i < args; i++ {
63 | in[i], err = export(ar.At(i), t.In(i))
64 | if err != nil {
65 | return nil, errarg(i, err)
66 | }
67 | }
68 | }
69 | }
70 | out := f.Fn.Call(in)
71 |
72 | // Test if the last output value is an error, check and remove it.
73 | if len(out) > 0 {
74 | if last := out[len(out)-1]; last.Type().Implements(reflect.TypeOf((*error)(nil)).Elem()) {
75 | if last.IsNil() == false {
76 | return nil, last.Interface().(error)
77 | } else {
78 | out = out[:len(out)-1]
79 | }
80 | }
81 | }
82 |
83 | if len(out) == 0 {
84 | return apl.EmptyArray{}, nil
85 | } else if len(out) == 1 {
86 | return Convert(out[0])
87 | } else {
88 | res := make(apl.List, len(out))
89 | for i := range out {
90 | if v, err := Convert(out[i]); err != nil {
91 | return nil, err
92 | } else {
93 | res[i] = v
94 | }
95 | }
96 | return res, nil
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/apl/xgo/method.go:
--------------------------------------------------------------------------------
1 | package xgo
2 |
3 | /* TODO remove
4 | import (
5 | "fmt"
6 | "reflect"
7 |
8 | "github.com/ktye/iv/apl"
9 | )
10 |
11 | type Method struct {
12 | Value reflect.Value
13 | Method string
14 | }
15 |
16 | func (m Method) String(a *apl.Apl) {
17 | fmt.Srintf("%v→%s", m.Value.Type(), m.Method)
18 | }
19 |
20 | func (m Method) Call(a *apl.Apl, L, R apl.Value) (apl.Value, error) {
21 | var zero reflect.Value
22 | if m.Value == zero {
23 | return nil, fmt.Errorf("method has no value")
24 | }
25 | fn := reflect.ValueOf(m.Value).MethodByName(m.Method)
26 | if fn == zero {
27 | return nil, fmt.Errorf("method %s does not exist", m.Method)
28 | }
29 | fn := Function{Name: m.Method, Fn: fn}
30 | return fn.Call(a, L, R)
31 | }
32 | */
33 |
--------------------------------------------------------------------------------
/apl/xgo/register.go:
--------------------------------------------------------------------------------
1 | package xgo
2 |
3 | import (
4 | "fmt"
5 | "reflect"
6 | "strings"
7 |
8 | "github.com/ktye/iv/apl"
9 | )
10 |
11 | func Register(a *apl.Apl, name string) {
12 | if name == "" {
13 | name = "go"
14 | }
15 | pkg := map[string]apl.Value{
16 | "t": New(reflect.TypeOf(T{})),
17 | "s": New(reflect.TypeOf(S{})),
18 | "i": New(reflect.TypeOf(I(0))),
19 | "source": source{},
20 | "echo": echo{},
21 | }
22 | a.RegisterPackage("go", pkg)
23 | }
24 |
25 | type I int
26 |
27 | // T is an example struct with methods with pointer receivers.
28 | type T struct {
29 | A string
30 | I int
31 | F float64
32 | C complex128
33 | V []string
34 | S S
35 | }
36 |
37 | func (t *T) Inc() {
38 | t.I++
39 | }
40 |
41 | func (t *T) Join(sep string) (int, string) {
42 | s := strings.Join(t.V, sep)
43 | return len(t.V), s
44 | }
45 |
46 | // S is an example struct with a method without pointer receiver.
47 | type S struct {
48 | A int
49 | B int
50 | V []int
51 | }
52 |
53 | func (s S) Sum() int {
54 | return s.A + s.B
55 | }
56 |
57 | // source returns a Channel to pull numbers from.
58 | // It stops if the max value is reached or the channel is closed.
59 | // It is used for demonstrating apl.Channel.
60 | type source struct{}
61 |
62 | func (_ source) String(f apl.Format) string {
63 | return "source"
64 | }
65 | func (s source) Copy() apl.Value { return s }
66 |
67 | func (s source) Call(a *apl.Apl, _, R apl.Value) (apl.Value, error) {
68 | num, ok := R.(apl.Number)
69 | if ok == false {
70 | return nil, fmt.Errorf("source: r must be a number")
71 | }
72 | n, ok := num.ToIndex()
73 | if ok == false || n <= 0 {
74 | return nil, fmt.Errorf("source: R must be a positive integer")
75 | }
76 | c := apl.NewChannel()
77 | go func(c apl.Channel) {
78 | for i := 0; i < n; i++ {
79 | select {
80 | case <-c[1]:
81 | close(c[0])
82 | return
83 | default:
84 | c[0] <- apl.Int(i)
85 | }
86 | }
87 | close(c[0])
88 | }(c)
89 | return c, nil
90 | }
91 |
92 | // echo returns a Channel which echos what was send to it.
93 | type echo struct{}
94 |
95 | func (_ echo) String(f apl.Format) string {
96 | return "echo"
97 | }
98 | func (e echo) Copy() apl.Value { return e }
99 |
100 | func (_ echo) Call(a *apl.Apl, _, R apl.Value) (apl.Value, error) {
101 | p := ""
102 | if s, ok := R.(apl.String); ok == false || len(s) == 0 {
103 | return nil, fmt.Errorf("echo: R must be a non-empty string")
104 | } else {
105 | p = string(s)
106 | }
107 | c := apl.NewChannel()
108 | go func(c apl.Channel) {
109 | var buf []apl.Value
110 | s := "empty stack"
111 | for {
112 | select {
113 | case r, ok := <-c[1]:
114 | if ok == false {
115 | close(c[0])
116 | return
117 | }
118 | buf = append(buf, r)
119 | if len(buf) == 1 {
120 | s = p + r.String(a.Format)
121 | }
122 | case c[0] <- apl.String(s):
123 | if len(buf) > 1 {
124 | copy(buf, buf[1:])
125 | buf = buf[:len(buf)-1]
126 | if len(buf) > 0 {
127 | s = p + buf[0].String(a.Format)
128 | }
129 | } else if len(buf) == 1 {
130 | buf = buf[:0]
131 | s = p + "empty stack"
132 | }
133 | }
134 | }
135 | close(c[0])
136 | }(c)
137 | return c, nil
138 | }
139 |
--------------------------------------------------------------------------------
/apl/xgo/type.go:
--------------------------------------------------------------------------------
1 | package xgo
2 |
3 | import (
4 | "fmt"
5 | "reflect"
6 | "strings"
7 | "text/tabwriter"
8 | "unicode"
9 |
10 | "github.com/ktye/iv/apl"
11 | )
12 |
13 | // New returns an initialization function for the given type.
14 | func New(t reflect.Type) create {
15 | return create{t}
16 | }
17 |
18 | type Value reflect.Value
19 |
20 | // xgo values are copied by reference.
21 | func (v Value) Copy() apl.Value {
22 | return v
23 | }
24 |
25 | func (v Value) String(f apl.Format) string {
26 | keys := v.Keys()
27 | if keys == nil {
28 | return fmt.Sprintf("xgo.Value (not a struct) %T", v)
29 | }
30 | var buf strings.Builder
31 | tw := tabwriter.NewWriter(&buf, 1, 0, 1, ' ', 0)
32 | for _, k := range keys {
33 | val := v.At(k)
34 | s := ""
35 | if val == nil {
36 | s = "?"
37 | } else {
38 | s = val.String(f)
39 | }
40 | fmt.Fprintf(tw, "%s:\t%s\n", k.String(f), s)
41 | }
42 | tw.Flush()
43 | s := buf.String()
44 | if len(s) > 0 && s[len(s)-1] == '\n' {
45 | return s[:len(s)-1]
46 | }
47 | return s
48 | }
49 |
50 | // Keys returns the field names, if the value is a struct.
51 | // It does not return the method names.
52 | // It returns nil, if the Value is not a struct.
53 | func (v Value) Keys() []apl.Value {
54 | val := reflect.Value(v)
55 | if val.Kind() == reflect.Ptr {
56 | val = val.Elem()
57 | }
58 | if val.Kind() != reflect.Struct {
59 | return nil
60 | }
61 | t := val.Type()
62 | n := t.NumField()
63 | res := make([]apl.Value, n)
64 | for i := 0; i < n; i++ {
65 | res[i] = apl.String(t.Field(i).Name)
66 | }
67 | return res
68 | }
69 |
70 | func (v Value) Methods() []string {
71 | val := reflect.Value(v)
72 | t := val.Type()
73 | n := t.NumMethod()
74 | res := make([]string, n)
75 | for i := range res {
76 | res[i] = lower(t.Method(i).Name)
77 | }
78 | return res
79 | }
80 |
81 | // Field returns the value of a field or a method with the given name.
82 | func (v Value) At(key apl.Value) apl.Value {
83 | name, ok := key.(apl.String)
84 | if ok == false {
85 | return nil
86 | }
87 | val := reflect.Value(v)
88 | var zero reflect.Value
89 | Name := upper(string(name))
90 | m := val.MethodByName(Name)
91 | if m != zero {
92 | return Function{Name: Name, Fn: m}
93 | }
94 | if val.Kind() == reflect.Ptr {
95 | val = val.Elem()
96 | }
97 | if val.Kind() != reflect.Struct {
98 | return nil
99 | }
100 | sf := val.FieldByName(Name)
101 | if sf == zero {
102 | return nil
103 | }
104 | rv, err := Convert(sf)
105 | if err != nil {
106 | return nil
107 | }
108 | return rv
109 | }
110 |
111 | func (v Value) Set(key apl.Value, fv apl.Value) error {
112 | field, ok := key.(apl.String)
113 | if ok == false {
114 | return fmt.Errorf("key must be a string")
115 | }
116 | val := reflect.Value(v).Elem()
117 | if val.Kind() != reflect.Struct {
118 | return fmt.Errorf("not a struct: cannot set field")
119 | }
120 | sf := val.FieldByName(string(field))
121 | var zero reflect.Value
122 | if sf == zero {
123 | return fmt.Errorf("%v: field does not exist: %s", val.Type(), field)
124 | }
125 | sv, err := export(fv, sf.Type())
126 | if err != nil {
127 | return err
128 | }
129 | sf.Set(sv)
130 | return nil
131 | }
132 |
133 | type create struct {
134 | reflect.Type
135 | }
136 |
137 | func (t create) String(f apl.Format) string {
138 | return fmt.Sprintf("new %v", t.Type)
139 | }
140 | func (t create) Copy() apl.Value {
141 | return t
142 | }
143 |
144 | func (t create) Call(a *apl.Apl, L, R apl.Value) (apl.Value, error) {
145 | v := reflect.New(t.Type)
146 | return Value(v), nil
147 | }
148 |
149 | func upper(s string) string {
150 | return firstrune(s, unicode.ToUpper)
151 | }
152 |
153 | func lower(s string) string {
154 | return firstrune(s, unicode.ToLower)
155 | }
156 |
157 | func firstrune(s string, f func(r rune) rune) string {
158 | var buf strings.Builder
159 | for i, r := range s {
160 | if i == 0 {
161 | buf.WriteRune(f(r))
162 | } else {
163 | buf.WriteRune(r)
164 | }
165 | }
166 | return buf.String()
167 | }
168 |
--------------------------------------------------------------------------------
/build:
--------------------------------------------------------------------------------
1 | set -e
2 | cd cmd/iv && GOOS=linux GOARCH=amd64 go build -o ../../ivL main.go && cd -
3 | cd cmd/iv && GOOS=darwin GOARCH=amd64 go build -o ../../ivM main.go && cd -
4 | cd cmd/iv && GOOS=windows GOARCH=amd64 go build -o ../../iv.exe main.go && cd -
5 | cd cmd/apl && GOOS=linux GOARCH=amd64 go build -o ../../aplL main.go && cd -
6 | cd cmd/apl && GOOS=darwin GOARCH=amd64 go build -o ../../aplM main.go && cd -
7 | cd cmd/apl && GOOS=windows GOARCH=amd64 go build -o ../../apl.exe main.go && cd -
8 |
--------------------------------------------------------------------------------
/cmd/apl.go:
--------------------------------------------------------------------------------
1 | // Package cmd contains shared code between cmd/apl, cmd/iv and cmd/lui
2 | package cmd
3 |
4 | import (
5 | "bufio"
6 | "fmt"
7 | "io"
8 | "os"
9 |
10 | "github.com/ktye/iv/apl"
11 | )
12 |
13 | // Apl runs the interpreter in file mode if arguments are given, otherwise as a repl.
14 | func Apl(a *apl.Apl, stdin io.Reader, args []string) error {
15 | // Execute files.
16 | if len(args) > 0 {
17 | for _, name := range args {
18 | var r io.Reader
19 | if name == "-" {
20 | r = stdin
21 | name = "stdin"
22 | } else {
23 | f, err := os.Open(name)
24 | if err != nil {
25 | return err
26 | }
27 | defer f.Close()
28 | r = f
29 | }
30 | return a.EvalFile(r, name)
31 | }
32 | return nil
33 | }
34 |
35 | // Run interactively.
36 | scanner := bufio.NewScanner(stdin)
37 | fmt.Printf(" ")
38 | for scanner.Scan() {
39 | s := scanner.Text()
40 | if err := a.ParseAndEval(s); err != nil {
41 | fmt.Println(err)
42 | }
43 | fmt.Printf(" ")
44 | }
45 | return nil
46 | }
47 |
--------------------------------------------------------------------------------
/cmd/apl/README.md:
--------------------------------------------------------------------------------
1 | # cmd/apl
2 |
3 | Apl is a simple command line program that runs APL\iv.
4 | It includes only the basic packages *numbers*, *primitives* and *operators*.
5 |
6 | It is just one example to use the interpreter.
7 | A more advanced program is `cmd/lui`.
8 |
9 | ## Usage
10 | ```
11 | apl
12 | ```
13 | If no input argument is given, the program acts as a simple REPL reading a line at a time.
14 | Multiline statements are errors.
15 | On error it prints a message but continues.
16 |
17 | Apl does not contain a readline library. If you need line editing or history, consider `rlwrap`.
18 |
19 | ```
20 | apl FILE ...
21 | ```
22 | If one or more arguments are given, it reads input from these files.
23 | Multiline statements are allowed.
24 | On error it prints a message to stderr and exits.
25 | If an argument is `-` it reads from stdin, but otherwise behaves like reading from a file.
26 |
27 |
28 | ## Testing
29 | `go test` runs all file in `testdata/*.apl` and compares the results to the corresponding `.out` files.
30 | If a file is known to fail, it's error message is in a `.err` file.
31 |
32 | More unit tests are in `apl/primitives/apl_test.go`.
33 |
34 | ## Example
35 | ```
36 | apl testdata/a.apl
37 | ```
38 |
--------------------------------------------------------------------------------
/cmd/apl/apl_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "os"
5 | "testing"
6 |
7 | "github.com/ktye/iv/cmd"
8 | )
9 |
10 | func init() {
11 | if err := os.Chdir("testdata"); err != nil {
12 | panic(err)
13 | }
14 | }
15 |
16 | func TestApl(t *testing.T) {
17 | if err := cmd.AplTest(newApl); err != nil {
18 | t.Fatal(err)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/cmd/apl/main.go:
--------------------------------------------------------------------------------
1 | // APL interpreter.
2 | //
3 | // Usage
4 | // apl < INPUT
5 | // apl FILES...
6 | package main
7 |
8 | import (
9 | "fmt"
10 | "os"
11 |
12 | "github.com/ktye/iv/apl"
13 | "github.com/ktye/iv/apl/big"
14 | "github.com/ktye/iv/apl/numbers"
15 | "github.com/ktye/iv/apl/operators"
16 | "github.com/ktye/iv/apl/primitives"
17 | "github.com/ktye/iv/cmd"
18 | )
19 |
20 | func main() {
21 | a := newApl()
22 | a.SetOutput(os.Stdout)
23 | if err := cmd.Apl(a, os.Stdin, os.Args[1:]); err != nil {
24 | fmt.Fprintln(os.Stderr, err)
25 | os.Exit(1)
26 | }
27 | }
28 |
29 | func newApl() *apl.Apl {
30 | a := apl.New(nil)
31 | numbers.Register(a)
32 | big.Register(a, "")
33 | primitives.Register(a)
34 | operators.Register(a)
35 | return a
36 | }
37 |
--------------------------------------------------------------------------------
/cmd/apl/testdata/a.apl:
--------------------------------------------------------------------------------
1 | 1+1
2 |
3 | ⍝ Multiline lambda expression
4 | f←{
5 | ⍺<0: -⍵
6 | ⍵
7 | }
8 |
9 | 5 f 3
10 | (1-2)f 3
11 |
--------------------------------------------------------------------------------
/cmd/apl/testdata/a.out:
--------------------------------------------------------------------------------
1 | 2
2 | 3
3 | ¯3
4 |
--------------------------------------------------------------------------------
/cmd/apl/testdata/abc.apl:
--------------------------------------------------------------------------------
1 | big→set 1
2 | A←154476802108746166441951315019919837485664325669565431700026634898253202035277999
3 | B←36875131794129999827197811565225474825492979968971970996283137471637224634055579
4 | C←4373612677928697257861252602371390152816537558161613618621437993378423467772036
5 | 4=(A÷B+C)+(B÷A+C)+C÷A+B
6 |
--------------------------------------------------------------------------------
/cmd/apl/testdata/abc.out:
--------------------------------------------------------------------------------
1 | 1
2 | 1
3 |
--------------------------------------------------------------------------------
/cmd/apl/testdata/e.apl:
--------------------------------------------------------------------------------
1 | 1x
2 |
--------------------------------------------------------------------------------
/cmd/apl/testdata/e.err:
--------------------------------------------------------------------------------
1 | e.apl:1: cannot parse number: 1x
2 |
--------------------------------------------------------------------------------
/cmd/apl/testdata/finn.out:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ktye/iv/5c1b6375ce570a8839f81d3a500be7f503ba1ce3/cmd/apl/testdata/finn.out
--------------------------------------------------------------------------------
/cmd/apl/testdata/fmt.apl:
--------------------------------------------------------------------------------
1 | ⍝ Formatting examples for arrays, dicts and lists to csv, json, mat
2 | ¯1⍕2 3 4⍴⍳24 ⍝ display format
3 | ¯2⍕2 3 4⍴⍳24 ⍝ json
4 | ¯3⍕3⍴⍳3 ⍝ matlab vector
5 | ¯3⍕2 3⍴⍳6 ⍝ matlab vector
6 | ⍝ string arrays
7 | S←"abc" "def" "gh\ni"
8 | ¯1⍕3 2⍴S
9 | `json ⍕3 2⍴S
10 | `mat ⍕3 2⍴S
11 | ⍝ floats and complex
12 | S←1.2 3.4 5.6J¯7.8
13 | ¯1⍕3 2⍴S
14 | `csv ⍕3 2⍴S
15 | `json ⍕3 2⍴S
16 | `mat ⍕3 2⍴S
17 | ⍝ dictionaries
18 | S←`A`b`zeta#(1; "zwei";1 2 3;)
19 | S
20 | ¯1⍕S
21 | ¯2⍕S
22 | ¯3⍕S
23 | ⍝ lists
24 | L←(1 2;(3;(1;2;);"four";);5;)
25 | L
26 | ¯1⍕L
27 | ¯2⍕L
28 | ⍝ Numbers
29 | 123 0123 0x1234
30 | ¯1⍕123 0123 0x123
31 | ¯8⍕123 0123 0x123
32 | ¯16⍕123 0123 0x123
33 | `x ⍕123 0123 0x123
34 | `x ⍕ 1.23
35 |
--------------------------------------------------------------------------------
/cmd/apl/testdata/fmt.out:
--------------------------------------------------------------------------------
1 | 1 2 3 4
2 | 5 6 7 8
3 | 9 10 11 12
4 |
5 | 13 14 15 16
6 | 17 18 19 20
7 | 21 22 23 24
8 | [[[1,2,3,4],[5,6,7,8],[9,10,11,12]],[[13,14,15,16],[17,18,19,20],[21,22,23,24]]]
9 | [1,2,3]
10 | [1,2,3;4,5,6]
11 | "abc" "def"
12 | "gh\ni" "abc"
13 | "def" "gh\ni"
14 | [["abc","def"],["gh\ni","abc"],["def","gh\ni"]]
15 | ["abc","def";"gh\ni","abc";"def","gh\ni"]
16 | 1.2 3.4
17 | 5.6J¯7.8 1.2
18 | 3.4 5.6J¯7.8
19 | 1.2,3.4
20 | 5.6J¯7.8,1.2
21 | 3.4,5.6J¯7.8
22 |
23 | [[1.2,3.4],[[5.6,-7.8],1.2],[3.4,[5.6,-7.8]]]
24 | [1.2,3.4;(5.6-7.8i),1.2;3.4,(5.6-7.8i)]
25 | A: 1
26 | b: zwei
27 | zeta: 1 2 3
28 | "A": 1
29 | "b": "zwei"
30 | "zeta": 1 2 3
31 | {"A":1,"b":"zwei","zeta":[1,2,3]}
32 | struct("A",1,"b","zwei","zeta",[1,2,3])
33 | (1 2;(3;(1;2;);four;);5;)
34 | (1 2;(3;(1;2;);"four";);5;)
35 | [[1,2],[3,[1,2],"four"],5]
36 | 123 83 4660
37 | 123 83 291
38 | 0173 0123 0443
39 | 0x7B 0x53 0x123
40 | 0x7B 0x53 0x123
41 | 5539427541665710p-52
42 |
--------------------------------------------------------------------------------
/cmd/apl/testdata/obj.apl:
--------------------------------------------------------------------------------
1 | ⍝ Create a dictionary and index-assign (change) a field
2 | B←`a`b`c#(2 3⍴⍳6;"alpha";3;)
3 | B[`c]←5
4 | B[`c]=5 ⍝ out: 1
5 |
6 | ⍝ A is a nested dictionary
7 | A←`x`y`z#(1;B;"ZZZ";)
8 |
9 | ⍝ Depth-assignment
10 | A[`y;`c]←"@"
11 | A[`y;`c] ⍝ out: @
12 | A[`y;`a;2;2]←8
13 | A[`y;`a;2;] ⍝ out: 4 8 6
14 | A[`y;`a;2;2] ⍝ out: 8
15 | A[`y;`c]←1 2 3
16 | A[`y;`c] ⍝ out: 1 2 3
17 |
--------------------------------------------------------------------------------
/cmd/apl/testdata/obj.out:
--------------------------------------------------------------------------------
1 | 1
2 | @
3 | 4 8 6
4 | 8
5 | 1 2 3
6 |
--------------------------------------------------------------------------------
/cmd/apl/testdata/parse.apl:
--------------------------------------------------------------------------------
1 | test←{X←`A ⍎⍕⍵ ⋄ X≡⍵}
2 | ⎕PP←0
3 | test 2 3 4⍴⍳24
4 | ⎕PP←¯1
5 | test 2 3 4⍴⍳24
6 | ⎕PP←¯2
7 | test 2 3 4⍴⍳24
8 | ⎕PP←¯3
9 | test 3 4⍴⍳24
--------------------------------------------------------------------------------
/cmd/apl/testdata/parse.out:
--------------------------------------------------------------------------------
1 | 1
2 | 1b
3 | 1b
4 | 1b
5 |
--------------------------------------------------------------------------------
/cmd/apl/testdata/table.apl:
--------------------------------------------------------------------------------
1 | ⍝ Create a table
2 | D ← 2019.02.07T13.25 + ⍳3
3 | N ← `Peter`Jack`Thomas
4 | B ← 1b 0b 1b
5 | I ← 100 × ⍳3
6 | F ← 100÷⍳3
7 | C ← 1+0J1×⍳3
8 | M ← ((1.2 2.1 3.0;);(;);(7.8912345678;);) ⍝ Multiple values; not allowed in K
9 |
10 | ⍝ A table is created by transposing a dictionary.
11 | ⍝ A dictionary is created with the # primitive
12 | D ← `Time`Name`Mark`Count`Number`Comp`Mult#(D;N;B;I;F;C;M;)
13 | T← ⍉D
14 |
15 | ⍝ Print the table in standard form, which is equal to ⍕T
16 | "Default table format:"
17 | T
18 |
19 | "PP set to 2:"
20 | ⎕PP←2
21 | T
22 | ⎕PP←⍳0 ⍝ reset PP
23 |
24 | ⍝ Print the table in parsable form
25 | "Parsable table format:"
26 | ¯1⍕T
27 |
28 | ⍝ Print table in csv format
29 | "csv format:"
30 | `csv ⍕T
31 |
32 | ⍝ Custom formatting is created in a dictionary with the corresponding field names
33 | "custom format:"
34 | F←`Time#`alpha
35 | F[`Time]←`2006-01-02T15:04 ⍝ To format dates, use the desired result for the prototype date Jan 2, 2006-01-02 15:04:05
36 | F[`Count]←`0x%x ⍝ Hexadecimal
37 | F[`Comp]←`%.3f@%.1f ⍝ Amplitude @ angle in degree
38 | F
39 | F⍕T
40 |
41 | ⍝ Custom format in csv output
42 | "custom format with csv:"
43 | F[`CSV]←1 ⍝ Add the special key CSV with value 1
44 | F⍕T
45 |
46 | ⍝ Table aggregations
47 | T←⍉`A`B`C`D#(1.1 1.2 1.3;2.1 2.2 2.3; 3.1 3.2 3.3;1 2 1;)
48 | T
49 | "column sum:"
50 | T[;;+/]
51 | T[;`A`C;+/]
52 |
53 | "named aggregations:"
54 | T[;`A;`min`max #(⌊/;⌈/;)]
55 |
56 | "non-aggregating functions:"
57 | T[;`A;`min`max #(⌊\;⌈\;)]
58 |
59 | "individual functions per column:"
60 | T[;`A`C;`minA`maxC #(⌊/;⌈/;)]
61 |
62 | "group by D:"
63 | T[;`A`C`D;+/;`D]
64 |
65 | "grouping function:"
66 | T[;`D`A`C;+/;"D is even"#⊃({0=2|D};)]
67 |
--------------------------------------------------------------------------------
/cmd/apl/testdata/table.out:
--------------------------------------------------------------------------------
1 | Default table format:
2 | Time Name Mark Count Number Comp Mult
3 | 2019.02.07T13.25.01.000 Peter 1 100 100 1J1 1.2 2.1 3
4 | 2019.02.07T13.25.02.000 Jack 0 200 50 1J2
5 | 2019.02.07T13.25.03.000 Thomas 1 300 33.3333 1J3 7.89123
6 |
7 | PP set to 2:
8 | Time Name Mark Count Number Comp Mult
9 | 2019.02.07T13.25.01.000 Peter 1 100 1E+02 1J1 1.2 2.1 3
10 | 2019.02.07T13.25.02.000 Jack 0 200 50 1J2
11 | 2019.02.07T13.25.03.000 Thomas 1 300 33 1J3 7.9
12 |
13 | Parsable table format:
14 | "Time" "Name" "Mark" "Count" "Number" "Comp" "Mult"
15 | 2019.02.07T13.25.01.000 "Peter" 1b 100 100 1J1 [1.2 2.1 3]
16 | 2019.02.07T13.25.02.000 "Jack" 0b 200 50 1J2 []
17 | 2019.02.07T13.25.03.000 "Thomas" 1b 300 33.333333333333336 1J3 [7.8912345678]
18 |
19 | csv format:
20 | Time,Name,Mark,Count,Number,Comp,Mult
21 | 2019.02.07T13.25.01.000,Peter,1,100,100,1J1,1.2 2.1 3
22 | 2019.02.07T13.25.02.000,Jack,0,200,50,1J2,
23 | 2019.02.07T13.25.03.000,Thomas,1,300,33.3333,1J3,7.89123
24 |
25 | custom format:
26 | Time: 2006-01-02T15:04
27 | Count: 0x%x
28 | Comp: %.3f@%.1f
29 | Time Name Mark Count Number Comp Mult
30 | 2019-02-07T13:25 Peter 1 0x64 100 1.414@45.0 1.2 2.1 3
31 | 2019-02-07T13:25 Jack 0 0xc8 50 2.236@63.4
32 | 2019-02-07T13:25 Thomas 1 0x12c 33.3333 3.162@71.6 7.89123
33 |
34 | custom format with csv:
35 | Time,Name,Mark,Count,Number,Comp,Mult
36 | 2019-02-07T13:25,Peter,1,0x64,100,1.414@45.0,1.2 2.1 3
37 | 2019-02-07T13:25,Jack,0,0xc8,50,2.236@63.4,
38 | 2019-02-07T13:25,Thomas,1,0x12c,33.3333,3.162@71.6,7.89123
39 |
40 | A B C D
41 | 1.1 2.1 3.1 1
42 | 1.2 2.2 3.2 2
43 | 1.3 2.3 3.3 1
44 |
45 | column sum:
46 | A B C D
47 | 3.6 6.6 9.6 4
48 |
49 | A C
50 | 3.6 9.6
51 |
52 | named aggregations:
53 | min max
54 | 1.1 1.3
55 |
56 | non-aggregating functions:
57 | min max
58 | 1.1 1.1
59 | 1.1 1.2
60 | 1.1 1.3
61 |
62 | individual functions per column:
63 | minA maxC
64 | 1.1 3.3
65 |
66 | group by D:
67 | D A C
68 | 1 2.4 6.4
69 | 2 1.2 3.2
70 |
71 | grouping function:
72 | D is even A C
73 | 0 2.4 6.4
74 | 1 1.2 3.2
75 |
76 |
--------------------------------------------------------------------------------
/cmd/iv.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "io"
5 |
6 | "github.com/ktye/iv/apl"
7 | )
8 |
9 | func Iv(a *apl.Apl, p string, w io.Writer) error {
10 | a.SetOutput(w)
11 | if err := a.ParseAndEval(`r←{<⍤⍵ io→r 0}⋄s←{⍵⍴<⍤0 io→r 0}`); err != nil {
12 | return err
13 | }
14 | return a.ParseAndEval(p)
15 | }
16 |
--------------------------------------------------------------------------------
/cmd/iv/README.md:
--------------------------------------------------------------------------------
1 | # iv - APL stream processor
2 |
3 | Program *iv* is similar to `cmd/apl` but reads data from stdin instead of program text.
4 |
5 | The program is given as command line arguments.
6 |
7 | ```
8 | Usage
9 | cat data | iv COMMANDS
10 | ```
11 |
12 | ## streaming data
13 | Iv provides two pre-defined functions that read data from stdin:
14 | ```
15 | r ← {<⍤⍵ io→r 0}
16 | s ← {⍵⍴<⍤0 io→r 0}
17 | ```
18 | io→r 0 returns a channel that provides a line of input on each read.
19 |
20 | The rank operator `⍤` is extended to parse sub-arrays from a channel delivering strings.
21 | See `ScanRankArray` in `apl/fmt.go`.
22 | The number and array syntax is not as strict as usual program text to allow interoperatiliby with other programs.
23 |
24 | Function `r` is called monadically with a rank argument: `r 0` reads a scalar at a time, `r 1` a vector at a time and so on.
25 | Usually a vector is a line and higher order arrays are terminated by multiple newlines.
26 | Bracket notation (json arrays) or matlab style `1 2 3;4 5 6` can also be used.
27 |
28 | Function `s` ignores the structure of incoming data and always reads a scalar at a time, reshaping it according to it's right argument.
29 |
30 | ## examples
31 | To apply a function on each 2d subarray of the input stream, we can call iv with:
32 | ```
33 | cat data | iv f¨r 2
34 | ```
35 |
36 |
37 | Format each 3 2 subarray of the input stream to a json string:
38 | ```
39 | cat data | iv '`json ⍕¨s 2 3'
40 | ```
41 |
42 | More examples are given in `testdata`.
43 |
44 | ## extra libraries
45 | cmd/iv includes only the base packages. Even `io` mentioned above only provides a single function to read from stdin.
46 | To use all packages in this repository, cmd/lui can be called to work like iv by including the argument `-i`.
47 | ```
48 | lui -i COMMANDS
49 | ```
50 |
--------------------------------------------------------------------------------
/cmd/iv/iv_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "os"
5 | "testing"
6 |
7 | "github.com/ktye/iv/cmd"
8 | )
9 |
10 | func init() {
11 | if err := os.Chdir("testdata"); err != nil {
12 | panic(err)
13 | }
14 | }
15 |
16 | func TestIv(t *testing.T) {
17 | if err := cmd.IvTest(newApl); err != nil {
18 | t.Fatal(err)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/cmd/iv/main.go:
--------------------------------------------------------------------------------
1 | // APL stream processor.
2 | //
3 | // Usage
4 | // cat data | iv COMMANDS
5 | package main
6 |
7 | import (
8 | "fmt"
9 | "io"
10 | "os"
11 | "strings"
12 |
13 | "github.com/ktye/iv/apl"
14 | "github.com/ktye/iv/apl/numbers"
15 | "github.com/ktye/iv/apl/operators"
16 | "github.com/ktye/iv/apl/primitives"
17 | "github.com/ktye/iv/cmd"
18 | )
19 |
20 | var stdin io.ReadCloser = os.Stdin
21 |
22 | func main() {
23 | if len(os.Args) < 2 {
24 | fatal(fmt.Errorf("arguments expected"))
25 | }
26 | a := newApl(stdin)
27 | fatal(cmd.Iv(a, strings.Join(os.Args[1:], " "), os.Stdout))
28 | }
29 |
30 | func newApl(r io.ReadCloser) *apl.Apl {
31 | stdin = r
32 | a := apl.New(nil)
33 | numbers.Register(a)
34 | primitives.Register(a)
35 | operators.Register(a)
36 |
37 | // Add a minimal io package that's sole purpose is to allow
38 | // to read from stdin.
39 | pkg := map[string]apl.Value{
40 | "r": apl.ToFunction(readfd),
41 | }
42 | a.RegisterPackage("io", pkg)
43 | return a
44 | }
45 |
46 | func fatal(err error) {
47 | if err != nil {
48 | fmt.Fprintln(os.Stderr, err)
49 | os.Exit(1)
50 | }
51 | }
52 |
53 | func readfd(a *apl.Apl, _, R apl.Value) (apl.Value, error) {
54 | fd, ok := R.(apl.Int)
55 | if ok == false {
56 | return nil, fmt.Errorf("iv/io read: argument must be 0 (stdin)")
57 | }
58 | if fd != 0 {
59 | return nil, fmt.Errorf("monadic <: right argument must be 0 (stdin)")
60 | }
61 | return apl.LineReader(stdin), nil
62 | }
63 |
--------------------------------------------------------------------------------
/cmd/iv/testdata/a.iv:
--------------------------------------------------------------------------------
1 | # 1 + 1
2 |
--------------------------------------------------------------------------------
/cmd/iv/testdata/a.out:
--------------------------------------------------------------------------------
1 | 2
2 |
--------------------------------------------------------------------------------
/cmd/iv/testdata/colsum.iv:
--------------------------------------------------------------------------------
1 | # {⍵,⍪+/⍵}¨r 2
2 | 1 2 3
3 | 4 5 6
4 |
5 | 7 8 9
6 | 1 2 3
--------------------------------------------------------------------------------
/cmd/iv/testdata/colsum.out:
--------------------------------------------------------------------------------
1 | 1 2 3 6
2 | 4 5 6 15
3 | 7 8 9 24
4 | 1 2 3 6
5 |
--------------------------------------------------------------------------------
/cmd/iv/testdata/rank.iv:
--------------------------------------------------------------------------------
1 | # {(⍵;⍴⍵;)}¨r 2
2 | 1 2 3
3 | 4 5 6
4 |
5 | 7 8 9
6 | 1 2 3
7 |
--------------------------------------------------------------------------------
/cmd/iv/testdata/rank.out:
--------------------------------------------------------------------------------
1 | ( 1 2 3
2 | 4 5 6;2 3;)
3 | ( 7 8 9
4 | 1 2 3;2 3;)
5 |
--------------------------------------------------------------------------------
/cmd/iv/testdata/shape.iv:
--------------------------------------------------------------------------------
1 | # `json ⍕¨s 3 2
2 | 1 2 3 4 5
3 | 6 7 8 9 0
4 | 1 2
--------------------------------------------------------------------------------
/cmd/iv/testdata/shape.out:
--------------------------------------------------------------------------------
1 | [[1,2],[3,4],[5,6]]
2 | [[7,8],[9,0],[1,2]]
3 |
--------------------------------------------------------------------------------
/cmd/testing.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "bufio"
5 | "bytes"
6 | "fmt"
7 | "io"
8 | "io/ioutil"
9 | "os"
10 | "strings"
11 |
12 | "github.com/ktye/iv/apl"
13 | )
14 |
15 | // AplTest is run from go test in cmd/apl and cmd/lui.
16 | func AplTest(newapl func() *apl.Apl) error {
17 | d, err := os.Open(".")
18 | if err != nil {
19 | return err
20 | }
21 | defer d.Close()
22 |
23 | files, err := d.Readdirnames(-1)
24 | if err != nil {
25 | return err
26 | }
27 |
28 | for _, file := range files {
29 | if strings.HasSuffix(file, ".apl") == false {
30 | continue
31 | }
32 | if err := testAplFile(newapl, file); err != nil {
33 | return err
34 | }
35 | }
36 | return nil
37 | }
38 |
39 | // IvTest is run from go test in cmd/iv and cmd/lui.
40 | func IvTest(newapl func(io.ReadCloser) *apl.Apl) error {
41 | d, err := os.Open(".")
42 | if err != nil {
43 | return err
44 | }
45 | defer d.Close()
46 |
47 | files, err := d.Readdirnames(-1)
48 | if err != nil {
49 | return err
50 | }
51 |
52 | for _, file := range files {
53 | if strings.HasSuffix(file, ".iv") == false {
54 | continue
55 | }
56 | if err := testIvFile(newapl, file[:len(file)-3]); err != nil {
57 | return fmt.Errorf("%s: %s", file, err)
58 | }
59 | }
60 | return nil
61 | }
62 |
63 | func testAplFile(newapl func() *apl.Apl, file string) error {
64 | var out bytes.Buffer
65 | a := newapl()
66 | a.SetOutput(&out)
67 | if err := Apl(a, nil, []string{file}); err != nil {
68 | return compareError(err, file)
69 | }
70 | return compareOut(out.Bytes(), file[:len(file)-4])
71 | }
72 | func testIvFile(newapl func(io.ReadCloser) *apl.Apl, file string) error {
73 | f, err := os.Open(file + ".iv")
74 | if err != nil {
75 | return err
76 | }
77 | defer f.Close()
78 |
79 | // First line in each iv test file is the program with a comment.
80 | r := bufio.NewReader(f)
81 | prog, err := readline(r)
82 | if err != nil {
83 | return err
84 | }
85 |
86 | var out bytes.Buffer
87 | if err := Iv(newapl(ioutil.NopCloser(r)), prog[1:], &out); err != nil {
88 | return err
89 | }
90 | return compareOut(out.Bytes(), file)
91 | }
92 |
93 | func readline(s io.RuneScanner) (string, error) {
94 | var b strings.Builder
95 | for {
96 | if r, _, err := s.ReadRune(); err != nil {
97 | return "", err
98 | } else {
99 | if r == '\n' {
100 | return b.String(), nil
101 | }
102 | b.WriteRune(r)
103 | }
104 | }
105 | }
106 |
107 | func compareError(got error, file string) error {
108 | name := file[:len(file)-4] + ".err"
109 | want, err := ioutil.ReadFile(name)
110 | if err != nil {
111 | return fmt.Errorf("%s: failed but (%s)\n%s", file, err, got)
112 | }
113 | if got := got.Error() + "\n"; got != string(want) {
114 | return fmt.Errorf("%s: expected:\n%sgot:\n%s", file, want, got)
115 | }
116 | return nil
117 | }
118 |
119 | func compareOut(got []byte, file string) error {
120 | name := file + ".out"
121 | want, err := ioutil.ReadFile(name)
122 | if err != nil {
123 | return fmt.Errorf("%s: should have failed, but: %s", file, err)
124 | }
125 |
126 | at := -1
127 | max := len(want)
128 | if len(got) < max {
129 | max = len(got)
130 | }
131 | if len(want) != len(got) {
132 | at = max
133 | }
134 |
135 | line := 1
136 | for i := 0; i < max; i++ {
137 | if want[i] == '\n' {
138 | line++
139 | }
140 | if got[i] != want[i] {
141 | at = i
142 | break
143 | }
144 | }
145 |
146 | if at < 0 {
147 | return nil
148 | }
149 | return fmt.Errorf("%s:%d differs (byte %d). Got:\n%s\nWant:\n%s", name, line, at+1, string(got), string(want))
150 | }
151 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/ktye/iv
2 |
3 | go 1.12
4 |
--------------------------------------------------------------------------------
/log.svg:
--------------------------------------------------------------------------------
1 |
6 |
7 |
14 |
--------------------------------------------------------------------------------