libjay 0.2.0__tar.gz → 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {libjay-0.2.0 → libjay-0.2.1}/Cargo.lock +5 -5
- {libjay-0.2.0 → libjay-0.2.1}/Cargo.toml +1 -1
- {libjay-0.2.0 → libjay-0.2.1}/PKG-INFO +1 -1
- {libjay-0.2.0 → libjay-0.2.1}/crates/libjay/src/array.rs +18 -3
- {libjay-0.2.0 → libjay-0.2.1}/crates/libjay/src/dtype.rs +9 -0
- {libjay-0.2.0 → libjay-0.2.1}/crates/libjay/src/explain.rs +5 -0
- {libjay-0.2.0 → libjay-0.2.1}/crates/libjay/src/fmt.rs +46 -11
- {libjay-0.2.0 → libjay-0.2.1}/crates/libjay/src/frontend/apl.rs +150 -2
- {libjay-0.2.0 → libjay-0.2.1}/crates/libjay/src/frontend/j.rs +52 -16
- {libjay-0.2.0 → libjay-0.2.1}/crates/libjay/src/fuse.rs +214 -71
- {libjay-0.2.0 → libjay-0.2.1}/crates/libjay/src/ir.rs +3 -0
- {libjay-0.2.0 → libjay-0.2.1}/crates/libjay/src/lib.rs +1 -0
- {libjay-0.2.0 → libjay-0.2.1}/crates/libjay/src/par.rs +32 -2
- libjay-0.2.1/crates/libjay/src/symbol.rs +131 -0
- {libjay-0.2.0 → libjay-0.2.1}/crates/libjay/src/verb.rs +1852 -234
- {libjay-0.2.0 → libjay-0.2.1}/crates/libjay-python/src/data.rs +10 -2
- {libjay-0.2.0 → libjay-0.2.1}/crates/libjay-python/src/lib.rs +6 -0
- {libjay-0.2.0 → libjay-0.2.1}/LICENSE +0 -0
- {libjay-0.2.0 → libjay-0.2.1}/crates/libjay/Cargo.toml +0 -0
- {libjay-0.2.0 → libjay-0.2.1}/crates/libjay/LICENSE +0 -0
- {libjay-0.2.0 → libjay-0.2.1}/crates/libjay/README.md +0 -0
- {libjay-0.2.0 → libjay-0.2.1}/crates/libjay/src/complex.rs +0 -0
- {libjay-0.2.0 → libjay-0.2.1}/crates/libjay/src/device/codegen.rs +0 -0
- {libjay-0.2.0 → libjay-0.2.1}/crates/libjay/src/device/gpu.rs +0 -0
- {libjay-0.2.0 → libjay-0.2.1}/crates/libjay/src/device/mod.rs +0 -0
- {libjay-0.2.0 → libjay-0.2.1}/crates/libjay/src/error.rs +0 -0
- {libjay-0.2.0 → libjay-0.2.1}/crates/libjay/src/exact.rs +0 -0
- {libjay-0.2.0 → libjay-0.2.1}/crates/libjay/src/frontend/mod.rs +0 -0
- {libjay-0.2.0 → libjay-0.2.1}/crates/libjay/src/gerund.rs +0 -0
- {libjay-0.2.0 → libjay-0.2.1}/crates/libjay/src/limits.rs +0 -0
- {libjay-0.2.0 → libjay-0.2.1}/crates/libjay/src/rng.rs +0 -0
- {libjay-0.2.0 → libjay-0.2.1}/crates/libjay/src/simd.rs +0 -0
- {libjay-0.2.0 → libjay-0.2.1}/crates/libjay-python/Cargo.toml +0 -0
- {libjay-0.2.0 → libjay-0.2.1}/crates/libjay-testkit/Cargo.toml +0 -0
- {libjay-0.2.0 → libjay-0.2.1}/crates/libjay-testkit/src/compare.rs +0 -0
- {libjay-0.2.0 → libjay-0.2.1}/crates/libjay-testkit/src/corpus.rs +0 -0
- {libjay-0.2.0 → libjay-0.2.1}/crates/libjay-testkit/src/eval.rs +0 -0
- {libjay-0.2.0 → libjay-0.2.1}/crates/libjay-testkit/src/lib.rs +0 -0
- {libjay-0.2.0 → libjay-0.2.1}/crates/libjay-testkit/src/replay.rs +0 -0
- {libjay-0.2.0 → libjay-0.2.1}/crates/libjay-testkit/src/snapshot.rs +0 -0
- {libjay-0.2.0 → libjay-0.2.1}/pyproject.toml +0 -0
- {libjay-0.2.0 → libjay-0.2.1}/python/README.md +0 -0
- {libjay-0.2.0 → libjay-0.2.1}/python/jay/__init__.py +0 -0
- {libjay-0.2.0 → libjay-0.2.1}/python/jay/_cli.py +0 -0
- {libjay-0.2.0 → libjay-0.2.1}/python/jay/_tstring.py +0 -0
- {libjay-0.2.0 → libjay-0.2.1}/python/jay/lang.py +0 -0
|
@@ -494,7 +494,7 @@ checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
|
|
494
494
|
|
|
495
495
|
[[package]]
|
|
496
496
|
name = "libjay"
|
|
497
|
-
version = "0.2.
|
|
497
|
+
version = "0.2.1"
|
|
498
498
|
dependencies = [
|
|
499
499
|
"libjay-testkit",
|
|
500
500
|
"multiversion",
|
|
@@ -510,14 +510,14 @@ dependencies = [
|
|
|
510
510
|
|
|
511
511
|
[[package]]
|
|
512
512
|
name = "libjay-capi"
|
|
513
|
-
version = "0.2.
|
|
513
|
+
version = "0.2.1"
|
|
514
514
|
dependencies = [
|
|
515
515
|
"libjay",
|
|
516
516
|
]
|
|
517
517
|
|
|
518
518
|
[[package]]
|
|
519
519
|
name = "libjay-devtools"
|
|
520
|
-
version = "0.2.
|
|
520
|
+
version = "0.2.1"
|
|
521
521
|
dependencies = [
|
|
522
522
|
"libjay-testkit",
|
|
523
523
|
"rayon",
|
|
@@ -525,7 +525,7 @@ dependencies = [
|
|
|
525
525
|
|
|
526
526
|
[[package]]
|
|
527
527
|
name = "libjay-python"
|
|
528
|
-
version = "0.2.
|
|
528
|
+
version = "0.2.1"
|
|
529
529
|
dependencies = [
|
|
530
530
|
"arrow-array",
|
|
531
531
|
"arrow-buffer",
|
|
@@ -537,7 +537,7 @@ dependencies = [
|
|
|
537
537
|
|
|
538
538
|
[[package]]
|
|
539
539
|
name = "libjay-testkit"
|
|
540
|
-
version = "0.2.
|
|
540
|
+
version = "0.2.1"
|
|
541
541
|
dependencies = [
|
|
542
542
|
"libjay",
|
|
543
543
|
]
|
|
@@ -436,6 +436,10 @@ pub enum Data {
|
|
|
436
436
|
/// pair of Arrow float columns all share.
|
|
437
437
|
Complex(Buf<Cx>),
|
|
438
438
|
Char(Buf<char>),
|
|
439
|
+
/// Symbols: every element is an index into the process-wide symbol
|
|
440
|
+
/// table (see [`crate::symbol`]), so the buffer is as flat and as
|
|
441
|
+
/// cheap to copy as one of integers and the names live once each.
|
|
442
|
+
Symbol(Buf<crate::symbol::Id>),
|
|
439
443
|
/// Boxes: every element is a whole array. Foreign memory never holds
|
|
440
444
|
/// these, so a boxed buffer is always owned and cloning it is a
|
|
441
445
|
/// refcount bump like any other.
|
|
@@ -452,6 +456,7 @@ impl Data {
|
|
|
452
456
|
Data::F64(_) => DType::F64,
|
|
453
457
|
Data::Complex(_) => DType::Complex,
|
|
454
458
|
Data::Char(_) => DType::Char,
|
|
459
|
+
Data::Symbol(_) => DType::Symbol,
|
|
455
460
|
Data::Box(_) => DType::Box,
|
|
456
461
|
}
|
|
457
462
|
}
|
|
@@ -465,6 +470,7 @@ impl Data {
|
|
|
465
470
|
Data::F64(v) => v.len(),
|
|
466
471
|
Data::Complex(v) => v.len(),
|
|
467
472
|
Data::Char(v) => v.len(),
|
|
473
|
+
Data::Symbol(v) => v.len(),
|
|
468
474
|
Data::Box(v) => v.len(),
|
|
469
475
|
}
|
|
470
476
|
}
|
|
@@ -483,6 +489,7 @@ impl Data {
|
|
|
483
489
|
Data::F64(v) => v.is_foreign(),
|
|
484
490
|
Data::Complex(v) => v.is_foreign(),
|
|
485
491
|
Data::Char(v) => v.is_foreign(),
|
|
492
|
+
Data::Symbol(v) => v.is_foreign(),
|
|
486
493
|
Data::Box(v) => v.is_foreign(),
|
|
487
494
|
}
|
|
488
495
|
}
|
|
@@ -498,6 +505,7 @@ impl Data {
|
|
|
498
505
|
Data::F64(v) => v.owner(),
|
|
499
506
|
Data::Complex(v) => v.owner(),
|
|
500
507
|
Data::Char(v) => v.owner(),
|
|
508
|
+
Data::Symbol(v) => v.owner(),
|
|
501
509
|
Data::Box(v) => v.owner(),
|
|
502
510
|
}
|
|
503
511
|
}
|
|
@@ -511,6 +519,7 @@ impl Data {
|
|
|
511
519
|
Data::F64(v) => Data::F64(v.slice(start, end)),
|
|
512
520
|
Data::Complex(v) => Data::Complex(v.slice(start, end)),
|
|
513
521
|
Data::Char(v) => Data::Char(v.slice(start, end)),
|
|
522
|
+
Data::Symbol(v) => Data::Symbol(v.slice(start, end)),
|
|
514
523
|
Data::Box(v) => Data::Box(v.slice(start, end)),
|
|
515
524
|
}
|
|
516
525
|
}
|
|
@@ -524,6 +533,7 @@ impl Data {
|
|
|
524
533
|
DType::F64 => Data::F64(Buf::new()),
|
|
525
534
|
DType::Complex => Data::Complex(Buf::new()),
|
|
526
535
|
DType::Char => Data::Char(Buf::new()),
|
|
536
|
+
DType::Symbol => Data::Symbol(Buf::new()),
|
|
527
537
|
DType::Box => Data::Box(Buf::new()),
|
|
528
538
|
}
|
|
529
539
|
}
|
|
@@ -539,6 +549,7 @@ impl Data {
|
|
|
539
549
|
Data::F64(v) => v.push(0.0),
|
|
540
550
|
Data::Complex(v) => v.push(crate::complex::ZERO),
|
|
541
551
|
Data::Char(v) => v.push(' '),
|
|
552
|
+
Data::Symbol(v) => v.push(crate::symbol::EMPTY),
|
|
542
553
|
Data::Box(v) => v.push(Array::box_fill()),
|
|
543
554
|
}
|
|
544
555
|
}
|
|
@@ -554,6 +565,7 @@ impl Data {
|
|
|
554
565
|
(Data::F64(a), Data::F64(b)) => a.push(b[i]),
|
|
555
566
|
(Data::Complex(a), Data::Complex(b)) => a.push(b[i]),
|
|
556
567
|
(Data::Char(a), Data::Char(b)) => a.push(b[i]),
|
|
568
|
+
(Data::Symbol(a), Data::Symbol(b)) => a.push(b[i]),
|
|
557
569
|
(Data::Box(a), Data::Box(b)) => a.push(b[i].clone()),
|
|
558
570
|
_ => {}
|
|
559
571
|
}
|
|
@@ -568,6 +580,7 @@ impl Data {
|
|
|
568
580
|
(Data::F64(a), Data::F64(b)) => a.extend_from_slice(b),
|
|
569
581
|
(Data::Complex(a), Data::Complex(b)) => a.extend_from_slice(b),
|
|
570
582
|
(Data::Char(a), Data::Char(b)) => a.extend_from_slice(b),
|
|
583
|
+
(Data::Symbol(a), Data::Symbol(b)) => a.extend_from_slice(b),
|
|
571
584
|
(Data::Box(a), Data::Box(b)) => a.extend_from_slice(b),
|
|
572
585
|
_ => return false,
|
|
573
586
|
}
|
|
@@ -605,6 +618,7 @@ impl Data {
|
|
|
605
618
|
DType::F64 => by!(F64, Buf::join_fast),
|
|
606
619
|
DType::Complex => by!(Complex, Buf::join_fast),
|
|
607
620
|
DType::Char => by!(Char, Buf::join),
|
|
621
|
+
DType::Symbol => by!(Symbol, Buf::join_fast),
|
|
608
622
|
DType::Ext => by!(Ext, Buf::join),
|
|
609
623
|
DType::Rat => by!(Rat, Buf::join),
|
|
610
624
|
DType::Box => by!(Box, Buf::join),
|
|
@@ -703,6 +717,7 @@ impl Data {
|
|
|
703
717
|
DType::F64 => by!(F64, weave),
|
|
704
718
|
DType::Complex => by!(Complex, weave),
|
|
705
719
|
DType::Char => by!(Char, weave),
|
|
720
|
+
DType::Symbol => by!(Symbol, weave),
|
|
706
721
|
DType::Ext => by!(Ext, weave_cloned),
|
|
707
722
|
DType::Rat => by!(Rat, weave_cloned),
|
|
708
723
|
DType::Box => by!(Box, weave_cloned),
|
|
@@ -1035,7 +1050,7 @@ impl Array {
|
|
|
1035
1050
|
Data::Rat(v) => Some(v.iter().map(|x| [x.to_f64(), 0.0]).collect()),
|
|
1036
1051
|
Data::F64(v) => Some(v.iter().map(|&x| [x, 0.0]).collect()),
|
|
1037
1052
|
Data::Complex(v) => Some(v.to_vec()),
|
|
1038
|
-
Data::Char(_) | Data::Box(_) => None,
|
|
1053
|
+
Data::Char(_) | Data::Symbol(_) | Data::Box(_) => None,
|
|
1039
1054
|
}
|
|
1040
1055
|
}
|
|
1041
1056
|
|
|
@@ -1049,7 +1064,7 @@ impl Array {
|
|
|
1049
1064
|
Data::F64(v) => Some(v.to_vec()),
|
|
1050
1065
|
// A complex value is not a real one, even when its imaginary
|
|
1051
1066
|
// part is zero: the caller wants a real and must ask for it.
|
|
1052
|
-
Data::Complex(_) | Data::Char(_) | Data::Box(_) => None,
|
|
1067
|
+
Data::Complex(_) | Data::Char(_) | Data::Symbol(_) | Data::Box(_) => None,
|
|
1053
1068
|
}
|
|
1054
1069
|
}
|
|
1055
1070
|
|
|
@@ -1074,7 +1089,7 @@ impl Array {
|
|
|
1074
1089
|
}
|
|
1075
1090
|
Some(out)
|
|
1076
1091
|
}
|
|
1077
|
-
Data::Complex(_) | Data::Char(_) | Data::Box(_) => None,
|
|
1092
|
+
Data::Complex(_) | Data::Char(_) | Data::Symbol(_) | Data::Box(_) => None,
|
|
1078
1093
|
}
|
|
1079
1094
|
}
|
|
1080
1095
|
}
|
|
@@ -13,6 +13,9 @@ pub enum DType {
|
|
|
13
13
|
/// A complex number, held as an interleaved `[re, im]` pair.
|
|
14
14
|
Complex,
|
|
15
15
|
Char,
|
|
16
|
+
/// An interned name (J `s:`). The element is an index into the
|
|
17
|
+
/// process-wide symbol table, not the text itself.
|
|
18
|
+
Symbol,
|
|
16
19
|
/// A box: every element is itself an array (J `<`, APL `⊂`).
|
|
17
20
|
Box,
|
|
18
21
|
}
|
|
@@ -27,6 +30,7 @@ impl DType {
|
|
|
27
30
|
DType::F64 => "float",
|
|
28
31
|
DType::Complex => "complex",
|
|
29
32
|
DType::Char => "character",
|
|
33
|
+
DType::Symbol => "symbol",
|
|
30
34
|
DType::Box => "boxed",
|
|
31
35
|
}
|
|
32
36
|
}
|
|
@@ -55,6 +59,8 @@ impl DType {
|
|
|
55
59
|
(Box, _) | (_, Box) => None,
|
|
56
60
|
(Char, Char) => Some(Char),
|
|
57
61
|
(Char, _) | (_, Char) => None,
|
|
62
|
+
(Symbol, Symbol) => Some(Symbol),
|
|
63
|
+
(Symbol, _) | (_, Symbol) => None,
|
|
58
64
|
(Complex, _) | (_, Complex) => Some(Complex),
|
|
59
65
|
(F64, _) | (_, F64) => Some(F64),
|
|
60
66
|
(Rat, _) | (_, Rat) => Some(Rat),
|
|
@@ -85,5 +91,8 @@ mod tests {
|
|
|
85
91
|
fn characters_and_boxes_mix_with_nothing() {
|
|
86
92
|
assert_eq!(super::DType::promote(Char, Ext), None);
|
|
87
93
|
assert_eq!(super::DType::promote(Box, Rat), None);
|
|
94
|
+
assert_eq!(super::DType::promote(Symbol, Char), None);
|
|
95
|
+
assert_eq!(super::DType::promote(Symbol, I64), None);
|
|
96
|
+
assert_eq!(super::DType::promote(Symbol, Symbol), Some(Symbol));
|
|
88
97
|
}
|
|
89
98
|
}
|
|
@@ -426,6 +426,11 @@ fn verb_lines(v: &Verb, depth: usize, p: &Program, tr: &Trace, out: &mut String)
|
|
|
426
426
|
verb_lines(f, depth + 1, p, tr, out);
|
|
427
427
|
verb_lines(g, depth + 1, p, tr, out);
|
|
428
428
|
}
|
|
429
|
+
Verb::InnerProduct { u, v, .. } => {
|
|
430
|
+
head(out, "inner product (u . v; the matrix product for +/ . *)");
|
|
431
|
+
verb_lines(u, depth + 1, p, tr, out);
|
|
432
|
+
verb_lines(v, depth + 1, p, tr, out);
|
|
433
|
+
}
|
|
429
434
|
Verb::UserDerived { def, alpha, omega } => {
|
|
430
435
|
head(out, "a user-written operator with its operands");
|
|
431
436
|
verb_lines(def, depth + 1, p, tr, out);
|
|
@@ -52,14 +52,35 @@ pub fn format_array(a: &Array, opts: &FmtOpts) -> String {
|
|
|
52
52
|
// rather than with a nested display's extra spacing.
|
|
53
53
|
match mixed_simple_texts(a, opts) {
|
|
54
54
|
Some(texts) if opts.boxes == BoxStyle::Spaced => {
|
|
55
|
-
return laid_out(&a.shape, texts,
|
|
55
|
+
return laid_out(&a.shape, texts, Cells::Right)
|
|
56
56
|
}
|
|
57
57
|
_ => return format_boxed(a, opts),
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
let texts: Vec<String> = (0..a.count()).map(|i| format_atom(&a.data, i, opts)).collect();
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
laid_out(&a.shape, texts, Cells::of(a.dtype()))
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/// How the formatted elements of one row sit next to each other.
|
|
65
|
+
#[derive(Clone, Copy, PartialEq)]
|
|
66
|
+
enum Cells {
|
|
67
|
+
/// Numbers: one space between columns, each column right-aligned.
|
|
68
|
+
Right,
|
|
69
|
+
/// Characters: no separator at all, because the row IS the text.
|
|
70
|
+
Text,
|
|
71
|
+
/// Symbols: one space between columns, each column left-aligned and
|
|
72
|
+
/// padded on the right, which is how J prints a table of names.
|
|
73
|
+
Left,
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
impl Cells {
|
|
77
|
+
fn of(dtype: DType) -> Cells {
|
|
78
|
+
match dtype {
|
|
79
|
+
DType::Char => Cells::Text,
|
|
80
|
+
DType::Symbol => Cells::Left,
|
|
81
|
+
_ => Cells::Right,
|
|
82
|
+
}
|
|
83
|
+
}
|
|
63
84
|
}
|
|
64
85
|
|
|
65
86
|
/// The scalar each element of a boxed array holds, where every one of them
|
|
@@ -78,19 +99,23 @@ fn mixed_simple_texts(a: &Array, opts: &FmtOpts) -> Option<Vec<String>> {
|
|
|
78
99
|
|
|
79
100
|
/// One formatted element per position, laid out for the shape: a vector on
|
|
80
101
|
/// one line, higher ranks as aligned columns and planes.
|
|
81
|
-
fn laid_out(shape: &[usize], texts: Vec<String>,
|
|
102
|
+
fn laid_out(shape: &[usize], texts: Vec<String>, cells: Cells) -> String {
|
|
82
103
|
let rank = shape.len();
|
|
83
104
|
let a_shape = shape;
|
|
84
105
|
match rank {
|
|
85
106
|
0 => texts.into_iter().next().unwrap_or_default(),
|
|
86
|
-
1 if
|
|
107
|
+
1 if cells == Cells::Text => texts.concat(),
|
|
87
108
|
1 => texts.join(" "),
|
|
88
109
|
_ => {
|
|
89
110
|
let ncols = a_shape[rank - 1];
|
|
90
111
|
let nrows = a_shape[rank - 2];
|
|
91
112
|
// Column widths span every plane, so planes stay aligned with
|
|
92
113
|
// each other and not just internally.
|
|
93
|
-
let widths = if
|
|
114
|
+
let widths = if cells == Cells::Text {
|
|
115
|
+
vec![0; ncols]
|
|
116
|
+
} else {
|
|
117
|
+
column_widths(&texts, ncols)
|
|
118
|
+
};
|
|
94
119
|
let frame = &a_shape[..rank - 2];
|
|
95
120
|
let plane_size = nrows * ncols;
|
|
96
121
|
let planes: usize = frame.iter().product();
|
|
@@ -105,7 +130,7 @@ fn laid_out(shape: &[usize], texts: Vec<String>, text_layout: bool) -> String {
|
|
|
105
130
|
out.push('\n');
|
|
106
131
|
}
|
|
107
132
|
let start = p * plane_size + r * ncols;
|
|
108
|
-
push_row(&mut out, &texts[start..start + ncols], &widths,
|
|
133
|
+
push_row(&mut out, &texts[start..start + ncols], &widths, cells);
|
|
109
134
|
}
|
|
110
135
|
}
|
|
111
136
|
out
|
|
@@ -257,19 +282,27 @@ fn column_widths(texts: &[String], ncols: usize) -> Vec<usize> {
|
|
|
257
282
|
widths
|
|
258
283
|
}
|
|
259
284
|
|
|
260
|
-
fn push_row(out: &mut String, row: &[String], widths: &[usize],
|
|
285
|
+
fn push_row(out: &mut String, row: &[String], widths: &[usize], cells: Cells) {
|
|
261
286
|
for (j, cell) in row.iter().enumerate() {
|
|
262
|
-
if
|
|
287
|
+
if cells == Cells::Text {
|
|
263
288
|
out.push_str(cell);
|
|
264
289
|
continue;
|
|
265
290
|
}
|
|
266
291
|
if j > 0 {
|
|
267
292
|
out.push(' ');
|
|
268
293
|
}
|
|
269
|
-
|
|
270
|
-
|
|
294
|
+
let pad = widths[j].saturating_sub(width(cell));
|
|
295
|
+
if cells == Cells::Right {
|
|
296
|
+
for _ in 0..pad {
|
|
297
|
+
out.push(' ');
|
|
298
|
+
}
|
|
271
299
|
}
|
|
272
300
|
out.push_str(cell);
|
|
301
|
+
if cells == Cells::Left {
|
|
302
|
+
for _ in 0..pad {
|
|
303
|
+
out.push(' ');
|
|
304
|
+
}
|
|
305
|
+
}
|
|
273
306
|
}
|
|
274
307
|
}
|
|
275
308
|
|
|
@@ -287,6 +320,8 @@ fn format_atom(data: &Data, i: usize, opts: &FmtOpts) -> String {
|
|
|
287
320
|
Data::F64(v) => format_f64(v[i], opts),
|
|
288
321
|
Data::Complex(v) => format_complex(v[i], opts),
|
|
289
322
|
Data::Char(v) => v[i].to_string(),
|
|
323
|
+
// A symbol prints as its name behind the backtick that makes one.
|
|
324
|
+
Data::Symbol(v) => format!("`{}", crate::symbol::name(v[i])),
|
|
290
325
|
// Boxed data takes the drawing path before reaching here.
|
|
291
326
|
Data::Box(_) => String::new(),
|
|
292
327
|
}
|
|
@@ -168,6 +168,11 @@ enum OpGlyph {
|
|
|
168
168
|
Before,
|
|
169
169
|
/// `⌸` key (Dyalog): each distinct major cell with what shares it.
|
|
170
170
|
Key,
|
|
171
|
+
/// `.` between two functions: the inner product, `+.×` above all. The
|
|
172
|
+
/// operand on its right is a function too, as `∘.`'s is.
|
|
173
|
+
Dot,
|
|
174
|
+
/// `⍠` variant: one dialect knob overridden for this application.
|
|
175
|
+
Variant,
|
|
171
176
|
}
|
|
172
177
|
|
|
173
178
|
impl OpGlyph {
|
|
@@ -187,6 +192,8 @@ impl OpGlyph {
|
|
|
187
192
|
OpGlyph::Each => '¨',
|
|
188
193
|
OpGlyph::Before => '⍛',
|
|
189
194
|
OpGlyph::Key => '⌸',
|
|
195
|
+
OpGlyph::Dot => '.',
|
|
196
|
+
OpGlyph::Variant => '⍠',
|
|
190
197
|
}
|
|
191
198
|
}
|
|
192
199
|
}
|
|
@@ -667,7 +674,6 @@ fn quad_name(name: &str, d: Rules, span: Span) -> Result<Tok> {
|
|
|
667
674
|
/// it under. These are queue positions, not unknown characters.
|
|
668
675
|
fn queued_glyph(ch: char) -> Option<&'static str> {
|
|
669
676
|
Some(match ch {
|
|
670
|
-
'⍠' => "the variant operator (f⍠v)",
|
|
671
677
|
'⌶' => "I-beam (⌶)",
|
|
672
678
|
// Dyalog's spawn. Named as a queue position rather than reported as
|
|
673
679
|
// an unknown character; whether libjay's sandbox opens APL's
|
|
@@ -693,6 +699,10 @@ fn op_for(ch: char) -> Option<OpGlyph> {
|
|
|
693
699
|
'¨' => Some(OpGlyph::Each),
|
|
694
700
|
'⍛' => Some(OpGlyph::Before),
|
|
695
701
|
'⌸' => Some(OpGlyph::Key),
|
|
702
|
+
'⍠' => Some(OpGlyph::Variant),
|
|
703
|
+
// A `.` that is not the start of a number and not the tail of `∘.`
|
|
704
|
+
// is the inner-product operator.
|
|
705
|
+
'.' => Some(OpGlyph::Dot),
|
|
696
706
|
_ => None,
|
|
697
707
|
}
|
|
698
708
|
}
|
|
@@ -1252,7 +1262,10 @@ fn fold_operators(toks: Vec<Token>, d: Rules) -> Result<Vec<Token>> {
|
|
|
1252
1262
|
}
|
|
1253
1263
|
// `f∘g` and `f⍥g` need a function on both sides; the right one is
|
|
1254
1264
|
// taken here so the ordinary "operand to the left" path can run.
|
|
1255
|
-
if matches!(
|
|
1265
|
+
if matches!(
|
|
1266
|
+
op,
|
|
1267
|
+
OpGlyph::Jot | OpGlyph::Over | OpGlyph::Before | OpGlyph::Under | OpGlyph::Dot
|
|
1268
|
+
) {
|
|
1256
1269
|
let Some(gtok) = it.peek().filter(|x| matches!(x.kind, Tok::Func(_))) else {
|
|
1257
1270
|
return Err(Error::not_yet(
|
|
1258
1271
|
format!("{} with a value operand", op.glyph()),
|
|
@@ -1288,6 +1301,13 @@ fn fold_operators(toks: Vec<Token>, d: Rules) -> Result<Vec<Token>> {
|
|
|
1288
1301
|
let composed = Verb::Compose(Box::new(f), Box::new(g));
|
|
1289
1302
|
Verb::Atop(Box::new(back), Box::new(composed))
|
|
1290
1303
|
}
|
|
1304
|
+
// `f.g` folds with f what g made of every row and column:
|
|
1305
|
+
// `+.×` is the matrix product, `∧.=` asks which rows match.
|
|
1306
|
+
OpGlyph::Dot => Verb::InnerProduct {
|
|
1307
|
+
u: Box::new(Verb::Reduce(Box::new(f))),
|
|
1308
|
+
v: Box::new(g),
|
|
1309
|
+
apl: true,
|
|
1310
|
+
},
|
|
1291
1311
|
_ => Verb::Compose(Box::new(f), Box::new(g)),
|
|
1292
1312
|
};
|
|
1293
1313
|
out.push(Token { kind: Tok::Func(derived), span });
|
|
@@ -1315,6 +1335,8 @@ fn fold_operators(toks: Vec<Token>, d: Rules) -> Result<Vec<Token>> {
|
|
|
1315
1335
|
| OpGlyph::Stencil
|
|
1316
1336
|
| OpGlyph::Before
|
|
1317
1337
|
| OpGlyph::Key
|
|
1338
|
+
| OpGlyph::Dot
|
|
1339
|
+
| OpGlyph::Variant
|
|
1318
1340
|
| OpGlyph::Each => {
|
|
1319
1341
|
return Err(Error::parse(
|
|
1320
1342
|
format!("{} needs a function to its left", op.glyph()),
|
|
@@ -1375,6 +1397,19 @@ fn fold_operators(toks: Vec<Token>, d: Rules) -> Result<Vec<Token>> {
|
|
|
1375
1397
|
}
|
|
1376
1398
|
OpGlyph::Commute => Verb::Commute(Box::new(f)),
|
|
1377
1399
|
OpGlyph::Key => Verb::KeyPairs(Box::new(f)),
|
|
1400
|
+
// `f⍠B`: one setting of the dialect overridden for this
|
|
1401
|
+
// application and no other.
|
|
1402
|
+
OpGlyph::Variant => {
|
|
1403
|
+
let (options, ospan) = variant_options(&mut it, t.span)?;
|
|
1404
|
+
let derived = variant(f, &options, Span::merge(span, ospan))?;
|
|
1405
|
+
out.push(Token { kind: Tok::Func(derived), span: Span::merge(span, ospan) });
|
|
1406
|
+
continue;
|
|
1407
|
+
}
|
|
1408
|
+
// `.` reached with no function on its right: `+.` alone is not
|
|
1409
|
+
// a function in either lineage.
|
|
1410
|
+
OpGlyph::Dot => {
|
|
1411
|
+
return Err(Error::parse("the inner product . needs a function on its right", t.span));
|
|
1412
|
+
}
|
|
1378
1413
|
// Each: the function runs on the contents of every item and
|
|
1379
1414
|
// its result goes back into an item. A simple scalar result
|
|
1380
1415
|
// stays simple, which is APL's enclosure rule.
|
|
@@ -1507,6 +1542,119 @@ fn take_axis(
|
|
|
1507
1542
|
Ok(Some((k as usize, span)))
|
|
1508
1543
|
}
|
|
1509
1544
|
|
|
1545
|
+
/// The options `⍠` was given, as `(name, value)` pairs.
|
|
1546
|
+
///
|
|
1547
|
+
/// A bare number is the PRINCIPAL option, which for every function libjay
|
|
1548
|
+
/// gives a variant is the comparison tolerance — so it arrives here named
|
|
1549
|
+
/// `CT`. The other published spelling is one or more parenthesised pairs,
|
|
1550
|
+
/// `⍠('IO' 0)` and `⍠('IO' 0)('CT' 0)`, whose halves are both literals.
|
|
1551
|
+
/// A computed option is a named gap: the variant is settled when the
|
|
1552
|
+
/// program is compiled, as the dialect it overrides is.
|
|
1553
|
+
fn variant_options(
|
|
1554
|
+
it: &mut std::iter::Peekable<std::vec::IntoIter<Token>>,
|
|
1555
|
+
span: Span,
|
|
1556
|
+
) -> Result<(Vec<(String, Array)>, Span)> {
|
|
1557
|
+
if let Some(tok) = it.peek().filter(|t| literal(&t.kind).is_some()) {
|
|
1558
|
+
let (value, vspan) = (literal(&tok.kind).expect("peeked a literal").clone(), tok.span);
|
|
1559
|
+
it.next();
|
|
1560
|
+
return Ok((vec![("CT".to_string(), value)], vspan));
|
|
1561
|
+
}
|
|
1562
|
+
let mut options = Vec::new();
|
|
1563
|
+
let mut last = span;
|
|
1564
|
+
while it.peek().is_some_and(|t| matches!(t.kind, Tok::LParen)) {
|
|
1565
|
+
it.next();
|
|
1566
|
+
let mut inside: Vec<Array> = Vec::new();
|
|
1567
|
+
loop {
|
|
1568
|
+
let Some(tok) = it.next() else {
|
|
1569
|
+
return Err(Error::parse("unmatched ( after ⍠", span));
|
|
1570
|
+
};
|
|
1571
|
+
last = tok.span;
|
|
1572
|
+
if matches!(tok.kind, Tok::RParen) {
|
|
1573
|
+
break;
|
|
1574
|
+
}
|
|
1575
|
+
match literal(&tok.kind) {
|
|
1576
|
+
Some(a) => inside.push(a.clone()),
|
|
1577
|
+
None => {
|
|
1578
|
+
return Err(Error::not_yet(
|
|
1579
|
+
"a computed variant option (f⍠v with a name or an expression)",
|
|
1580
|
+
tok.span,
|
|
1581
|
+
));
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
let [name, value] = inside.as_slice() else {
|
|
1586
|
+
return Err(Error::parse("a variant option is a name and a value", last));
|
|
1587
|
+
};
|
|
1588
|
+
let Data::Char(cs) = &name.data else {
|
|
1589
|
+
return Err(Error::parse("a variant option starts with its name", last));
|
|
1590
|
+
};
|
|
1591
|
+
options.push((cs.as_slice().iter().collect::<String>().to_uppercase(), value.clone()));
|
|
1592
|
+
}
|
|
1593
|
+
if options.is_empty() {
|
|
1594
|
+
let where_ = it.peek().map_or(span, |t| t.span);
|
|
1595
|
+
return Err(Error::not_yet(
|
|
1596
|
+
"a computed variant option (f⍠v with a name or an expression)",
|
|
1597
|
+
where_,
|
|
1598
|
+
));
|
|
1599
|
+
}
|
|
1600
|
+
Ok((options, last))
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
/// `f⍠B`: f with the dialect settings B names overridden for this
|
|
1604
|
+
/// application. `CT` is the comparison tolerance, which is the same
|
|
1605
|
+
/// mechanism J spells `!.`; `IO` is the index origin, which is resolved
|
|
1606
|
+
/// into the primitives when the program is compiled, so overriding it
|
|
1607
|
+
/// derives the verb again.
|
|
1608
|
+
fn variant(f: Verb, options: &[(String, Array)], span: Span) -> Result<Verb> {
|
|
1609
|
+
let mut out = f;
|
|
1610
|
+
for (name, value) in options {
|
|
1611
|
+
out = match name.as_str() {
|
|
1612
|
+
"CT" => {
|
|
1613
|
+
let Some(ct) = value.to_f64_vec().and_then(|v| v.first().copied()) else {
|
|
1614
|
+
return Err(Error::domain("a comparison tolerance is a number", span));
|
|
1615
|
+
};
|
|
1616
|
+
if !out.uses_tolerance() {
|
|
1617
|
+
return Err(Error::domain(
|
|
1618
|
+
format!(
|
|
1619
|
+
"the comparison tolerance is not an option of {}: it consults none",
|
|
1620
|
+
out.name()
|
|
1621
|
+
),
|
|
1622
|
+
span,
|
|
1623
|
+
));
|
|
1624
|
+
}
|
|
1625
|
+
if !(0.0..1.0).contains(&ct) {
|
|
1626
|
+
return Err(Error::domain(
|
|
1627
|
+
"a comparison tolerance lies between 0 and 1",
|
|
1628
|
+
span,
|
|
1629
|
+
));
|
|
1630
|
+
}
|
|
1631
|
+
Verb::Fit(Box::new(out), ct)
|
|
1632
|
+
}
|
|
1633
|
+
"IO" => {
|
|
1634
|
+
let Some(io) = value.to_i64_vec().and_then(|v| v.first().copied()) else {
|
|
1635
|
+
return Err(Error::domain("an index origin is a whole number", span));
|
|
1636
|
+
};
|
|
1637
|
+
if io != 0 && io != 1 {
|
|
1638
|
+
return Err(Error::domain("an index origin is 0 or 1", span));
|
|
1639
|
+
}
|
|
1640
|
+
crate::verb::with_origin(&out, io).ok_or_else(|| {
|
|
1641
|
+
Error::domain(
|
|
1642
|
+
format!("the index origin is not an option of {}", out.name()),
|
|
1643
|
+
span,
|
|
1644
|
+
)
|
|
1645
|
+
})?
|
|
1646
|
+
}
|
|
1647
|
+
other => {
|
|
1648
|
+
return Err(Error::not_yet(
|
|
1649
|
+
format!("the variant option {other} (f⍠v)"),
|
|
1650
|
+
span,
|
|
1651
|
+
));
|
|
1652
|
+
}
|
|
1653
|
+
};
|
|
1654
|
+
}
|
|
1655
|
+
Ok(out)
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1510
1658
|
/// `(/)` is `/`: parentheses around a bare operator glyph are transparent,
|
|
1511
1659
|
/// so what decides the glyph's reading is the token outside them. The pair
|
|
1512
1660
|
/// has no other meaning — an operator has no operand inside them — and the
|