libjay 0.3.0__tar.gz → 0.3.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.3.0 → libjay-0.3.1}/Cargo.lock +5 -5
- {libjay-0.3.0 → libjay-0.3.1}/Cargo.toml +1 -1
- {libjay-0.3.0 → libjay-0.3.1}/PKG-INFO +1 -1
- {libjay-0.3.0 → libjay-0.3.1}/crates/libjay/src/explain.rs +7 -6
- {libjay-0.3.0 → libjay-0.3.1}/crates/libjay/src/frontend/apl.rs +191 -31
- {libjay-0.3.0 → libjay-0.3.1}/crates/libjay/src/frontend/j.rs +3 -3
- {libjay-0.3.0 → libjay-0.3.1}/crates/libjay/src/frontend/mod.rs +49 -0
- {libjay-0.3.0 → libjay-0.3.1}/crates/libjay/src/ir.rs +192 -33
- {libjay-0.3.0 → libjay-0.3.1}/crates/libjay/src/lib.rs +1 -1
- {libjay-0.3.0 → libjay-0.3.1}/crates/libjay/src/verb.rs +45 -8
- {libjay-0.3.0 → libjay-0.3.1}/crates/libjay-python/src/lib.rs +32 -2
- {libjay-0.3.0 → libjay-0.3.1}/crates/libjay-testkit/src/eval.rs +23 -12
- {libjay-0.3.0 → libjay-0.3.1}/python/jay/__init__.py +2 -0
- {libjay-0.3.0 → libjay-0.3.1}/python/jay/lang.py +18 -0
- {libjay-0.3.0 → libjay-0.3.1}/LICENSE +0 -0
- {libjay-0.3.0 → libjay-0.3.1}/crates/libjay/Cargo.toml +0 -0
- {libjay-0.3.0 → libjay-0.3.1}/crates/libjay/LICENSE +0 -0
- {libjay-0.3.0 → libjay-0.3.1}/crates/libjay/README.md +0 -0
- {libjay-0.3.0 → libjay-0.3.1}/crates/libjay/src/array.rs +0 -0
- {libjay-0.3.0 → libjay-0.3.1}/crates/libjay/src/complex.rs +0 -0
- {libjay-0.3.0 → libjay-0.3.1}/crates/libjay/src/device/codegen.rs +0 -0
- {libjay-0.3.0 → libjay-0.3.1}/crates/libjay/src/device/gpu.rs +0 -0
- {libjay-0.3.0 → libjay-0.3.1}/crates/libjay/src/device/mod.rs +0 -0
- {libjay-0.3.0 → libjay-0.3.1}/crates/libjay/src/dtype.rs +0 -0
- {libjay-0.3.0 → libjay-0.3.1}/crates/libjay/src/error.rs +0 -0
- {libjay-0.3.0 → libjay-0.3.1}/crates/libjay/src/exact.rs +0 -0
- {libjay-0.3.0 → libjay-0.3.1}/crates/libjay/src/fmt.rs +0 -0
- {libjay-0.3.0 → libjay-0.3.1}/crates/libjay/src/fuse.rs +0 -0
- {libjay-0.3.0 → libjay-0.3.1}/crates/libjay/src/gerund.rs +0 -0
- {libjay-0.3.0 → libjay-0.3.1}/crates/libjay/src/limits.rs +0 -0
- {libjay-0.3.0 → libjay-0.3.1}/crates/libjay/src/par.rs +0 -0
- {libjay-0.3.0 → libjay-0.3.1}/crates/libjay/src/rng.rs +0 -0
- {libjay-0.3.0 → libjay-0.3.1}/crates/libjay/src/simd.rs +0 -0
- {libjay-0.3.0 → libjay-0.3.1}/crates/libjay/src/sparse.rs +0 -0
- {libjay-0.3.0 → libjay-0.3.1}/crates/libjay/src/symbol.rs +0 -0
- {libjay-0.3.0 → libjay-0.3.1}/crates/libjay-python/Cargo.toml +0 -0
- {libjay-0.3.0 → libjay-0.3.1}/crates/libjay-python/src/data.rs +0 -0
- {libjay-0.3.0 → libjay-0.3.1}/crates/libjay-testkit/Cargo.toml +0 -0
- {libjay-0.3.0 → libjay-0.3.1}/crates/libjay-testkit/src/compare.rs +0 -0
- {libjay-0.3.0 → libjay-0.3.1}/crates/libjay-testkit/src/corpus.rs +0 -0
- {libjay-0.3.0 → libjay-0.3.1}/crates/libjay-testkit/src/dialect.rs +0 -0
- {libjay-0.3.0 → libjay-0.3.1}/crates/libjay-testkit/src/lib.rs +0 -0
- {libjay-0.3.0 → libjay-0.3.1}/crates/libjay-testkit/src/replay.rs +0 -0
- {libjay-0.3.0 → libjay-0.3.1}/crates/libjay-testkit/src/snapshot.rs +0 -0
- {libjay-0.3.0 → libjay-0.3.1}/pyproject.toml +0 -0
- {libjay-0.3.0 → libjay-0.3.1}/python/README.md +0 -0
- {libjay-0.3.0 → libjay-0.3.1}/python/jay/_cli.py +0 -0
- {libjay-0.3.0 → libjay-0.3.1}/python/jay/_tstring.py +0 -0
|
@@ -494,7 +494,7 @@ checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
|
|
494
494
|
|
|
495
495
|
[[package]]
|
|
496
496
|
name = "libjay"
|
|
497
|
-
version = "0.3.
|
|
497
|
+
version = "0.3.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.3.
|
|
513
|
+
version = "0.3.1"
|
|
514
514
|
dependencies = [
|
|
515
515
|
"libjay",
|
|
516
516
|
]
|
|
517
517
|
|
|
518
518
|
[[package]]
|
|
519
519
|
name = "libjay-devtools"
|
|
520
|
-
version = "0.3.
|
|
520
|
+
version = "0.3.1"
|
|
521
521
|
dependencies = [
|
|
522
522
|
"libjay",
|
|
523
523
|
"libjay-testkit",
|
|
@@ -526,7 +526,7 @@ dependencies = [
|
|
|
526
526
|
|
|
527
527
|
[[package]]
|
|
528
528
|
name = "libjay-python"
|
|
529
|
-
version = "0.3.
|
|
529
|
+
version = "0.3.1"
|
|
530
530
|
dependencies = [
|
|
531
531
|
"arrow-array",
|
|
532
532
|
"arrow-buffer",
|
|
@@ -538,7 +538,7 @@ dependencies = [
|
|
|
538
538
|
|
|
539
539
|
[[package]]
|
|
540
540
|
name = "libjay-testkit"
|
|
541
|
-
version = "0.3.
|
|
541
|
+
version = "0.3.1"
|
|
542
542
|
dependencies = [
|
|
543
543
|
"libjay",
|
|
544
544
|
]
|
|
@@ -254,12 +254,13 @@ fn control_lines(c: &Control, depth: usize, p: &Program, tr: &Trace, out: &mut S
|
|
|
254
254
|
block("test", test, out);
|
|
255
255
|
block("body", body, out);
|
|
256
256
|
}
|
|
257
|
-
Control::For {
|
|
258
|
-
let
|
|
259
|
-
|
|
260
|
-
"{
|
|
261
|
-
|
|
262
|
-
|
|
257
|
+
Control::For { names, source, body } => {
|
|
258
|
+
let bound = match names.as_slice() {
|
|
259
|
+
[] => String::new(),
|
|
260
|
+
[n] => format!(", item in {n}, index in {n}_index"),
|
|
261
|
+
many => format!(", item taken apart into {}", many.join(" ")),
|
|
262
|
+
};
|
|
263
|
+
let _ = writeln!(out, "{pad}for over items{bound}");
|
|
263
264
|
block("source", std::slice::from_ref(source), out);
|
|
264
265
|
block("body", body, out);
|
|
265
266
|
}
|
|
@@ -12,8 +12,8 @@ use std::sync::Arc;
|
|
|
12
12
|
use crate::array::{Array, Data};
|
|
13
13
|
use crate::error::{Error, Result, Span};
|
|
14
14
|
use crate::frontend::{
|
|
15
|
-
DefaultArg, DepthSign, DfnResult, FirstDisclose, IndexForm, LookupLeft,
|
|
16
|
-
Rules, Segment, SourceParts,
|
|
15
|
+
ControlStrictness, DefaultArg, DepthSign, DfnResult, FirstDisclose, IndexForm, LookupLeft,
|
|
16
|
+
NestedModel, Partition, Rules, Segment, SourceParts,
|
|
17
17
|
};
|
|
18
18
|
use crate::ir::{Branch, Control, ExplicitDef, Expr, Scope};
|
|
19
19
|
use crate::verb::{
|
|
@@ -28,6 +28,9 @@ use crate::verb::{
|
|
|
28
28
|
pub fn parse(src: &SourceParts, d: Rules) -> Result<Vec<Expr>> {
|
|
29
29
|
let sentences = lex(src, d)?;
|
|
30
30
|
let mut verbs: HashMap<String, Verb> = HashMap::new();
|
|
31
|
+
for name in fixed_names(&sentences, d) {
|
|
32
|
+
verbs.entry(name.clone()).or_insert_with(|| Verb::Named(name.clone()));
|
|
33
|
+
}
|
|
31
34
|
let mut stmts = Vec::with_capacity(sentences.len());
|
|
32
35
|
let mut i = 0usize;
|
|
33
36
|
while i < sentences.len() {
|
|
@@ -36,6 +39,22 @@ pub fn parse(src: &SourceParts, d: Rules) -> Result<Vec<Expr>> {
|
|
|
36
39
|
stmts.push(stmt);
|
|
37
40
|
continue;
|
|
38
41
|
}
|
|
42
|
+
// A control structure standing on its own, outside any definition:
|
|
43
|
+
// its sentences are the lines of a body with nothing around them.
|
|
44
|
+
if let Some(Tok::Control(w)) = sentences[i].first().map(|t| &t.kind)
|
|
45
|
+
&& matches!(*w, "If" | "While" | "Repeat" | "For" | "Select")
|
|
46
|
+
{
|
|
47
|
+
let end = control_block_end(&sentences, i).unwrap_or(sentences.len());
|
|
48
|
+
let mut items = Vec::with_capacity(end - i);
|
|
49
|
+
for line in &sentences[i..end] {
|
|
50
|
+
let mut label = None;
|
|
51
|
+
items.push(to_item(line.clone(), d, &mut verbs, &mut label)?);
|
|
52
|
+
}
|
|
53
|
+
let mut cursor = AplCursor { items: &items, at: 0, d, loops: 0 };
|
|
54
|
+
stmts.push(parse_apl_control(&mut cursor)?);
|
|
55
|
+
i = end;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
39
58
|
let mut sentence = sentences[i].clone();
|
|
40
59
|
i += 1;
|
|
41
60
|
// `⎕FX` defines a function where it stands; the sentence keeps only
|
|
@@ -57,6 +76,67 @@ pub fn parse(src: &SourceParts, d: Rules) -> Result<Vec<Expr>> {
|
|
|
57
76
|
Ok(stmts)
|
|
58
77
|
}
|
|
59
78
|
|
|
79
|
+
/// The functions the program fixes anywhere in it, by name.
|
|
80
|
+
///
|
|
81
|
+
/// APL settles a name's class when the line runs; libjay compiles first, so
|
|
82
|
+
/// a definition whose body calls a function fixed AFTER it would have
|
|
83
|
+
/// nothing to call. The names every `∇` and `⎕FX` in the program gives a
|
|
84
|
+
/// function are therefore collected before anything is parsed, and each
|
|
85
|
+
/// stands as a verb resolved when it is applied — the definition it names
|
|
86
|
+
/// has been fixed by then. A header this cannot read is skipped: parsing
|
|
87
|
+
/// the definition itself reports the fault.
|
|
88
|
+
fn fixed_names(sentences: &[Vec<Token>], d: Rules) -> Vec<String> {
|
|
89
|
+
let mut out = Vec::new();
|
|
90
|
+
for line in sentences {
|
|
91
|
+
let head = match line.first().map(|t| &t.kind) {
|
|
92
|
+
Some(Tok::Del) => line[1..].to_vec(),
|
|
93
|
+
_ => {
|
|
94
|
+
let Some(at) = line.iter().position(|t| matches!(t.kind, Tok::QuadFx)) else {
|
|
95
|
+
continue;
|
|
96
|
+
};
|
|
97
|
+
let Some(Tok::Value(a)) = line.get(at + 1).map(|t| &t.kind) else {
|
|
98
|
+
continue;
|
|
99
|
+
};
|
|
100
|
+
let Data::Char(cs) = &a.data else { continue };
|
|
101
|
+
if a.rank() > 1 {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
let text: String = cs.iter().collect();
|
|
105
|
+
let src = SourceParts::from_parts(&[&text], &[]);
|
|
106
|
+
match lex(&src, d) {
|
|
107
|
+
Ok(mut lexed) if lexed.len() == 1 => lexed.pop().unwrap_or_default(),
|
|
108
|
+
_ => continue,
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
let Some(first) = head.first() else { continue };
|
|
113
|
+
if let Ok((name, ..)) = parse_header(&head, first.span) {
|
|
114
|
+
out.push(name);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
out
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/// The sentence after the one that closes the control structure opening at
|
|
121
|
+
/// `at`. None where nothing closes it, which the block parser reports.
|
|
122
|
+
fn control_block_end(sentences: &[Vec<Token>], at: usize) -> Option<usize> {
|
|
123
|
+
let mut depth = 0usize;
|
|
124
|
+
for (k, line) in sentences.iter().enumerate().skip(at) {
|
|
125
|
+
let Some(Tok::Control(w)) = line.first().map(|t| &t.kind) else { continue };
|
|
126
|
+
match *w {
|
|
127
|
+
"If" | "While" | "Repeat" | "For" | "Select" => depth += 1,
|
|
128
|
+
"EndIf" | "EndWhile" | "EndFor" | "EndSelect" | "Until" | "End" => {
|
|
129
|
+
depth -= 1;
|
|
130
|
+
if depth == 0 {
|
|
131
|
+
return Some(k + 1);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
_ => {}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
None
|
|
138
|
+
}
|
|
139
|
+
|
|
60
140
|
/// Where a `⎕FX` stands in a sentence of its own rather than inside a dfn's
|
|
61
141
|
/// braces. One inside a body belongs to that body, which is compiled when
|
|
62
142
|
/// the definition is, so it is left where it is and named there as a gap.
|
|
@@ -195,11 +275,11 @@ fn parse_statement(
|
|
|
195
275
|
}
|
|
196
276
|
return Ok(Some(Expr::VerbDef { name: n.clone(), verb: v, span }));
|
|
197
277
|
}
|
|
278
|
+
// A control word that opens a structure is taken before the sentence
|
|
279
|
+
// is; anything left here continues or closes one that never opened.
|
|
198
280
|
if let Some(t) = toks.iter().find(|t| matches!(t.kind, Tok::Control(_))) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
t.span,
|
|
202
|
-
));
|
|
281
|
+
let Tok::Control(w) = t.kind else { unreachable!() };
|
|
282
|
+
return Err(Error::parse(format!(":{w} has no matching opening word"), t.span));
|
|
203
283
|
}
|
|
204
284
|
if let Some(t) = toks.iter().find(|t| matches!(t.kind, Tok::Arrow)) {
|
|
205
285
|
return Err(Error::parse(
|
|
@@ -2434,9 +2514,10 @@ fn match_lparen(toks: &[Token], lo: usize, rparen: usize) -> Result<usize> {
|
|
|
2434
2514
|
/// APL's control words, without their colon. `:End` closes any of the
|
|
2435
2515
|
/// structures, which is the spelling GNU APL's manual gives as an
|
|
2436
2516
|
/// alternative to the named closers.
|
|
2437
|
-
const CONTROL_WORDS: [&str;
|
|
2438
|
-
"If", "ElseIf", "Else", "EndIf", "
|
|
2439
|
-
"
|
|
2517
|
+
const CONTROL_WORDS: [&str; 21] = [
|
|
2518
|
+
"If", "ElseIf", "Else", "EndIf", "AndIf", "OrIf", "While", "EndWhile", "Repeat", "Until",
|
|
2519
|
+
"For", "In", "EndFor", "Select", "Case", "CaseList", "EndSelect", "Return", "Leave",
|
|
2520
|
+
"Continue", "End",
|
|
2440
2521
|
];
|
|
2441
2522
|
|
|
2442
2523
|
/// The control word a `:name` spells, case-insensitively as the references
|
|
@@ -2812,7 +2893,7 @@ fn build_tradfn(
|
|
|
2812
2893
|
items.push(item);
|
|
2813
2894
|
}
|
|
2814
2895
|
let item_count = items.len();
|
|
2815
|
-
let mut cursor = AplCursor { items: &items, at: 0, d };
|
|
2896
|
+
let mut cursor = AplCursor { items: &items, at: 0, d, loops: 0 };
|
|
2816
2897
|
let mut body = parse_apl_block(&mut cursor, &[])?;
|
|
2817
2898
|
// A label is the number of a LINE, so the statements have to be the
|
|
2818
2899
|
// lines: a control structure folds several of them into one and the
|
|
@@ -2968,6 +3049,9 @@ struct AplCursor<'a> {
|
|
|
2968
3049
|
at: usize,
|
|
2969
3050
|
/// The dialect, for the sentences a control word carries.
|
|
2970
3051
|
d: Rules,
|
|
3052
|
+
/// How many loops enclose the position being parsed, which is what
|
|
3053
|
+
/// decides whether a `:Leave` has one to leave.
|
|
3054
|
+
loops: usize,
|
|
2971
3055
|
}
|
|
2972
3056
|
|
|
2973
3057
|
impl<'a> AplCursor<'a> {
|
|
@@ -2985,6 +3069,14 @@ impl<'a> AplCursor<'a> {
|
|
|
2985
3069
|
.map_or_else(|| Span::new(0, 0), AplItem::span)
|
|
2986
3070
|
}
|
|
2987
3071
|
|
|
3072
|
+
/// Parse a loop's body, with the loop counted around it.
|
|
3073
|
+
fn in_loop<T>(&mut self, f: impl FnOnce(&mut Self) -> Result<T>) -> Result<T> {
|
|
3074
|
+
self.loops += 1;
|
|
3075
|
+
let out = f(self);
|
|
3076
|
+
self.loops -= 1;
|
|
3077
|
+
out
|
|
3078
|
+
}
|
|
3079
|
+
|
|
2988
3080
|
/// Consume the closing word, which may also be spelled `:End`.
|
|
2989
3081
|
fn close(&mut self, want: &str) -> Result<()> {
|
|
2990
3082
|
match self.peek_word() {
|
|
@@ -3031,8 +3123,14 @@ fn parse_apl_control(cur: &mut AplCursor<'_>) -> Result<Expr> {
|
|
|
3031
3123
|
let mut test = rest;
|
|
3032
3124
|
loop {
|
|
3033
3125
|
let test_expr = condition(test, start, cur.d)?;
|
|
3126
|
+
let test_expr = continued_condition(cur, test_expr)?;
|
|
3034
3127
|
let body = parse_apl_block(cur, &["ElseIf", "Else", "EndIf"])?;
|
|
3035
|
-
arms.push(Branch {
|
|
3128
|
+
arms.push(Branch {
|
|
3129
|
+
test: Some(vec![test_expr]),
|
|
3130
|
+
body,
|
|
3131
|
+
fall_through: false,
|
|
3132
|
+
list: false,
|
|
3133
|
+
});
|
|
3036
3134
|
match cur.peek_word() {
|
|
3037
3135
|
Some("ElseIf") => {
|
|
3038
3136
|
let Some(AplItem::Word { rest, .. }) = cur.peek() else { unreachable!() };
|
|
@@ -3055,7 +3153,8 @@ fn parse_apl_control(cur: &mut AplCursor<'_>) -> Result<Expr> {
|
|
|
3055
3153
|
}
|
|
3056
3154
|
"While" => {
|
|
3057
3155
|
let test = condition(rest, start, cur.d)?;
|
|
3058
|
-
let
|
|
3156
|
+
let test = continued_condition(cur, test)?;
|
|
3157
|
+
let body = cur.in_loop(|cur| parse_apl_block(cur, &["EndWhile"]))?;
|
|
3059
3158
|
cur.close("EndWhile")?;
|
|
3060
3159
|
Control::While { test: vec![test], body, body_first: false, until: false }
|
|
3061
3160
|
}
|
|
@@ -3063,40 +3162,50 @@ fn parse_apl_control(cur: &mut AplCursor<'_>) -> Result<Expr> {
|
|
|
3063
3162
|
if !rest.is_empty() {
|
|
3064
3163
|
return Err(Error::parse(":Repeat takes no condition", start));
|
|
3065
3164
|
}
|
|
3066
|
-
let body = parse_apl_block(cur, &["Until"])?;
|
|
3165
|
+
let body = cur.in_loop(|cur| parse_apl_block(cur, &["Until"]))?;
|
|
3067
3166
|
let Some(AplItem::Word { rest, span, .. }) = cur.peek() else {
|
|
3068
3167
|
return Err(Error::parse("this :Repeat needs an :Until", cur.last_span()));
|
|
3069
3168
|
};
|
|
3070
|
-
let
|
|
3169
|
+
let (rest, span) = (rest.clone(), *span);
|
|
3170
|
+
let test = condition(rest, span, cur.d)?;
|
|
3071
3171
|
cur.at += 1;
|
|
3172
|
+
let test = continued_condition(cur, test)?;
|
|
3072
3173
|
Control::While { test: vec![test], body, body_first: true, until: true }
|
|
3073
3174
|
}
|
|
3074
3175
|
"For" => {
|
|
3075
|
-
// `:For name :In source`.
|
|
3076
|
-
let (
|
|
3077
|
-
let body = parse_apl_block(cur, &["EndFor"])?;
|
|
3176
|
+
// `:For name… :In source`.
|
|
3177
|
+
let (names, source) = for_header(&rest, start, cur.d)?;
|
|
3178
|
+
let body = cur.in_loop(|cur| parse_apl_block(cur, &["EndFor"]))?;
|
|
3078
3179
|
cur.close("EndFor")?;
|
|
3079
|
-
Control::For {
|
|
3180
|
+
Control::For { names, source: Box::new(source), body }
|
|
3080
3181
|
}
|
|
3081
3182
|
"Select" => {
|
|
3082
3183
|
let subject = condition(rest, start, cur.d)?;
|
|
3083
3184
|
let mut cases = Vec::new();
|
|
3084
3185
|
loop {
|
|
3085
3186
|
match cur.peek() {
|
|
3086
|
-
Some(AplItem::Word { word: "Case", rest, span }) => {
|
|
3187
|
+
Some(AplItem::Word { word: w @ ("Case" | "CaseList"), rest, span }) => {
|
|
3188
|
+
let list = *w == "CaseList";
|
|
3087
3189
|
let test = condition(rest.clone(), *span, cur.d)?;
|
|
3088
3190
|
cur.at += 1;
|
|
3089
|
-
let body =
|
|
3191
|
+
let body =
|
|
3192
|
+
parse_apl_block(cur, &["Case", "CaseList", "Else", "EndSelect"])?;
|
|
3090
3193
|
cases.push(Branch {
|
|
3091
3194
|
test: Some(vec![test]),
|
|
3092
3195
|
body,
|
|
3093
3196
|
fall_through: false,
|
|
3197
|
+
list,
|
|
3094
3198
|
});
|
|
3095
3199
|
}
|
|
3096
3200
|
Some(AplItem::Word { word: "Else", .. }) => {
|
|
3097
3201
|
cur.at += 1;
|
|
3098
3202
|
let body = parse_apl_block(cur, &["EndSelect"])?;
|
|
3099
|
-
cases.push(Branch {
|
|
3203
|
+
cases.push(Branch {
|
|
3204
|
+
test: None,
|
|
3205
|
+
body,
|
|
3206
|
+
fall_through: false,
|
|
3207
|
+
list: false,
|
|
3208
|
+
});
|
|
3100
3209
|
cur.close("EndSelect")?;
|
|
3101
3210
|
break;
|
|
3102
3211
|
}
|
|
@@ -3109,8 +3218,14 @@ fn parse_apl_control(cur: &mut AplCursor<'_>) -> Result<Expr> {
|
|
|
3109
3218
|
Control::Select { subject: Box::new(subject), cases }
|
|
3110
3219
|
}
|
|
3111
3220
|
"Return" => Control::Return,
|
|
3112
|
-
|
|
3113
|
-
|
|
3221
|
+
// Dyalog wants a loop around `:Leave` and `:Continue`; the lenient
|
|
3222
|
+
// reading lets a stray one leave the definition instead.
|
|
3223
|
+
"Leave" | "Continue" => {
|
|
3224
|
+
if cur.loops == 0 && cur.d.control_strictness == ControlStrictness::Strict {
|
|
3225
|
+
return Err(Error::parse(format!(":{word} belongs inside a loop"), start));
|
|
3226
|
+
}
|
|
3227
|
+
if word == "Leave" { Control::Break } else { Control::Continue }
|
|
3228
|
+
}
|
|
3114
3229
|
other => {
|
|
3115
3230
|
return Err(Error::parse(format!(":{other} has no matching opening word"), start));
|
|
3116
3231
|
}
|
|
@@ -3118,6 +3233,46 @@ fn parse_apl_control(cur: &mut AplCursor<'_>) -> Result<Expr> {
|
|
|
3118
3233
|
Ok(Expr::Control(Box::new(control), Span::merge(start, cur.last_span())))
|
|
3119
3234
|
}
|
|
3120
3235
|
|
|
3236
|
+
/// The `:AndIf` and `:OrIf` lines that continue the condition above them.
|
|
3237
|
+
///
|
|
3238
|
+
/// Both SHORT-CIRCUIT: the second test does not run where the first has
|
|
3239
|
+
/// settled the answer. A test is a block, whose value is its last
|
|
3240
|
+
/// sentence's, so the two cannot simply be listed; the continuation is an
|
|
3241
|
+
/// `:If` of its own instead, answering with the next test where the first
|
|
3242
|
+
/// leaves the question open and with the settled truth value where it does
|
|
3243
|
+
/// not. They chain left to right, `:AndIf` and `:OrIf` alike.
|
|
3244
|
+
fn continued_condition(cur: &mut AplCursor<'_>, mut test: Expr) -> Result<Expr> {
|
|
3245
|
+
loop {
|
|
3246
|
+
let (word, rest, span) = match cur.peek() {
|
|
3247
|
+
Some(AplItem::Word { word: w @ ("AndIf" | "OrIf"), rest, span }) => {
|
|
3248
|
+
(*w, rest.clone(), *span)
|
|
3249
|
+
}
|
|
3250
|
+
_ => return Ok(test),
|
|
3251
|
+
};
|
|
3252
|
+
cur.at += 1;
|
|
3253
|
+
let next = condition(rest, span, cur.d)?;
|
|
3254
|
+
let settled = Expr::Const(Array::scalar_bool(word == "OrIf"), span);
|
|
3255
|
+
let (body, otherwise) = if word == "AndIf" {
|
|
3256
|
+
(vec![next], vec![settled])
|
|
3257
|
+
} else {
|
|
3258
|
+
(vec![settled], vec![next])
|
|
3259
|
+
};
|
|
3260
|
+
let whole = Span::merge(test.span(), span);
|
|
3261
|
+
test = Expr::Control(
|
|
3262
|
+
Box::new(Control::If {
|
|
3263
|
+
arms: vec![Branch {
|
|
3264
|
+
test: Some(vec![test]),
|
|
3265
|
+
body,
|
|
3266
|
+
fall_through: false,
|
|
3267
|
+
list: false,
|
|
3268
|
+
}],
|
|
3269
|
+
otherwise: Some(otherwise),
|
|
3270
|
+
}),
|
|
3271
|
+
whole,
|
|
3272
|
+
);
|
|
3273
|
+
}
|
|
3274
|
+
}
|
|
3275
|
+
|
|
3121
3276
|
/// The tokens after a control word, as one expression.
|
|
3122
3277
|
fn condition(rest: Vec<Token>, span: Span, d: Rules) -> Result<Expr> {
|
|
3123
3278
|
match rest.first() {
|
|
@@ -3135,23 +3290,28 @@ fn condition(rest: Vec<Token>, span: Span, d: Rules) -> Result<Expr> {
|
|
|
3135
3290
|
}
|
|
3136
3291
|
}
|
|
3137
3292
|
|
|
3138
|
-
/// `:For name :In source`.
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
return Err(Error::parse(":For needs a name to bind", span));
|
|
3142
|
-
};
|
|
3293
|
+
/// `:For name… :In source`. Several names take each item apart between
|
|
3294
|
+
/// them, one of its own items each.
|
|
3295
|
+
fn for_header(rest: &[Token], span: Span, d: Rules) -> Result<(Vec<String>, Expr)> {
|
|
3143
3296
|
let Some(k) = rest.iter().position(|t| matches!(t.kind, Tok::Control("In"))) else {
|
|
3144
3297
|
return Err(Error::parse(":For needs an :In", span));
|
|
3145
3298
|
};
|
|
3146
|
-
|
|
3147
|
-
|
|
3299
|
+
let mut names = Vec::with_capacity(k);
|
|
3300
|
+
for t in &rest[..k] {
|
|
3301
|
+
match &t.kind {
|
|
3302
|
+
Tok::Name(n) => names.push(n.clone()),
|
|
3303
|
+
_ => return Err(Error::parse(":For binds names, one per item", t.span)),
|
|
3304
|
+
}
|
|
3305
|
+
}
|
|
3306
|
+
if names.is_empty() {
|
|
3307
|
+
return Err(Error::parse(":For needs a name to bind", span));
|
|
3148
3308
|
}
|
|
3149
3309
|
let source = &rest[k + 1..];
|
|
3150
3310
|
let Some(first) = source.first() else {
|
|
3151
3311
|
return Err(Error::parse(":In needs a value", span));
|
|
3152
3312
|
};
|
|
3153
3313
|
let hint = Span::merge(first.span, source[source.len() - 1].span);
|
|
3154
|
-
Ok((
|
|
3314
|
+
Ok((names, parse_prepared(source, hint, d)?))
|
|
3155
3315
|
}
|
|
3156
3316
|
|
|
3157
3317
|
/// Parse a token run that has already had its names and dfns folded.
|
|
@@ -841,7 +841,7 @@ fn parse_control(cur: &mut Cursor<'_>, scope: &mut Names) -> Result<Expr> {
|
|
|
841
841
|
let body = parse_block(cur, scope, &["end."])?;
|
|
842
842
|
let end = cur.expect("end.")?;
|
|
843
843
|
let source = one_expr(source, Span::merge(start, end))?;
|
|
844
|
-
Control::For {
|
|
844
|
+
Control::For { names: suffix.iter().cloned().collect(), source: Box::new(source), body }
|
|
845
845
|
}
|
|
846
846
|
"select." => parse_select(cur, scope, start)?,
|
|
847
847
|
"try." => {
|
|
@@ -883,7 +883,7 @@ fn parse_if(cur: &mut Cursor<'_>, scope: &mut Names) -> Result<Control> {
|
|
|
883
883
|
let test = parse_block(cur, scope, &["do."])?;
|
|
884
884
|
cur.expect("do.")?;
|
|
885
885
|
let body = parse_block(cur, scope, &["elseif.", "else.", "end."])?;
|
|
886
|
-
arms.push(Branch { test: Some(test), body, fall_through: false });
|
|
886
|
+
arms.push(Branch { test: Some(test), body, fall_through: false, list: false });
|
|
887
887
|
match cur.peek_word() {
|
|
888
888
|
Some("elseif.") => {
|
|
889
889
|
cur.at += 1;
|
|
@@ -927,7 +927,7 @@ fn parse_select(cur: &mut Cursor<'_>, scope: &mut Names, start: Span) -> Result<
|
|
|
927
927
|
let body = parse_block(cur, scope, &["case.", "fcase.", "end."])?;
|
|
928
928
|
// `case. do.` with no test is the default arm.
|
|
929
929
|
let test = (!test.is_empty()).then_some(test);
|
|
930
|
-
cases.push(Branch { test, body, fall_through });
|
|
930
|
+
cases.push(Branch { test, body, fall_through, list: false });
|
|
931
931
|
}
|
|
932
932
|
Ok(Control::Select { subject: Box::new(subject), cases })
|
|
933
933
|
}
|
|
@@ -219,6 +219,43 @@ pub enum EncodeDigits {
|
|
|
219
219
|
Exact,
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
+
/// How strictly a control structure reads what it is given.
|
|
223
|
+
///
|
|
224
|
+
/// The lenient reading is the one both languages ship: a condition is true
|
|
225
|
+
/// where its first atom is, whatever else it holds, and a `:Leave` outside
|
|
226
|
+
/// a loop leaves the definition. Dyalog reads both strictly — a condition
|
|
227
|
+
/// is one element and no more, and `:Leave` belongs to a loop — and says so
|
|
228
|
+
/// rather than answering. GNU APL has no control structures at all, so
|
|
229
|
+
/// nothing it records turns on this.
|
|
230
|
+
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
|
231
|
+
pub enum ControlStrictness {
|
|
232
|
+
/// The first atom decides, and a stray `:Leave` is a return.
|
|
233
|
+
#[default]
|
|
234
|
+
Lenient,
|
|
235
|
+
/// Dyalog: a condition is a single value, and `:Leave` needs its loop.
|
|
236
|
+
Strict,
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/// Where the each in the inner product's definition sits.
|
|
240
|
+
///
|
|
241
|
+
/// `f.g` is a fold over a pairing, and the two lines put the each on
|
|
242
|
+
/// different halves of it. GNU APL puts it on the FOLD — `f/¨ (⊂[last]x)
|
|
243
|
+
/// ∘.g (⊂[first]y)` — so `g` meets one whole vector from each side and what
|
|
244
|
+
/// the fold makes of a pair is enclosed once more. Dyalog puts it on the
|
|
245
|
+
/// PAIRING — `f/ row g¨ column` — so `g` meets one element from each side
|
|
246
|
+
/// and the fold's own value stands as the cell. `1 2+.,3 4` is `10` under
|
|
247
|
+
/// the first and an enclosed `3 7` under the second. The two agree wherever
|
|
248
|
+
/// `g` is a scalar function and the fold ends in a number, which is every
|
|
249
|
+
/// published use, so `+.×` and the Life idiom's `∨.∧` differ only in depth.
|
|
250
|
+
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
|
251
|
+
pub enum InnerEach {
|
|
252
|
+
/// GNU APL: `f/¨` over the outer product.
|
|
253
|
+
#[default]
|
|
254
|
+
OnFold,
|
|
255
|
+
/// Dyalog: `f/` over `g¨`.
|
|
256
|
+
OnPair,
|
|
257
|
+
}
|
|
258
|
+
|
|
222
259
|
/// Dialect settings supplied by the host.
|
|
223
260
|
///
|
|
224
261
|
/// This is what a host asks for; [`Rules`] is what the compiler and the
|
|
@@ -252,6 +289,8 @@ pub struct Dialect {
|
|
|
252
289
|
pub near_count: NearCount,
|
|
253
290
|
pub floor_rule: FloorRule,
|
|
254
291
|
pub encode_digits: EncodeDigits,
|
|
292
|
+
pub inner_each: InnerEach,
|
|
293
|
+
pub control_strictness: ControlStrictness,
|
|
255
294
|
/// Whether a function may stand where a value belongs: a run of
|
|
256
295
|
/// functions is then a train, and `F←+/` names one. Both readings are
|
|
257
296
|
/// implemented, so this is a choice and not a gap. It ships on, as an
|
|
@@ -290,6 +329,8 @@ impl Dialect {
|
|
|
290
329
|
near_count: NearCount::Absolute,
|
|
291
330
|
floor_rule: FloorRule::Shift,
|
|
292
331
|
encode_digits: EncodeDigits::Tolerant,
|
|
332
|
+
inner_each: InnerEach::OnFold,
|
|
333
|
+
control_strictness: ControlStrictness::Lenient,
|
|
293
334
|
trains: true,
|
|
294
335
|
}
|
|
295
336
|
}
|
|
@@ -320,6 +361,8 @@ impl Dialect {
|
|
|
320
361
|
near_count: NearCount::Tolerant,
|
|
321
362
|
floor_rule: FloorRule::Scaled,
|
|
322
363
|
encode_digits: EncodeDigits::Exact,
|
|
364
|
+
inner_each: InnerEach::OnPair,
|
|
365
|
+
control_strictness: ControlStrictness::Strict,
|
|
323
366
|
trains: true,
|
|
324
367
|
}
|
|
325
368
|
}
|
|
@@ -395,6 +438,8 @@ impl Dialect {
|
|
|
395
438
|
near_count: self.near_count,
|
|
396
439
|
floor_rule: self.floor_rule,
|
|
397
440
|
encode_digits: self.encode_digits,
|
|
441
|
+
inner_each: self.inner_each,
|
|
442
|
+
control_strictness: self.control_strictness,
|
|
398
443
|
trains: self.trains,
|
|
399
444
|
})
|
|
400
445
|
}
|
|
@@ -427,6 +472,8 @@ pub struct Rules {
|
|
|
427
472
|
pub near_count: NearCount,
|
|
428
473
|
pub floor_rule: FloorRule,
|
|
429
474
|
pub encode_digits: EncodeDigits,
|
|
475
|
+
pub inner_each: InnerEach,
|
|
476
|
+
pub control_strictness: ControlStrictness,
|
|
430
477
|
pub trains: bool,
|
|
431
478
|
}
|
|
432
479
|
|
|
@@ -456,6 +503,8 @@ impl Rules {
|
|
|
456
503
|
near_count: self.near_count,
|
|
457
504
|
floor_rule: self.floor_rule,
|
|
458
505
|
encode_digits: self.encode_digits,
|
|
506
|
+
inner_each: self.inner_each,
|
|
507
|
+
control_strictness: self.control_strictness,
|
|
459
508
|
trains: self.trains,
|
|
460
509
|
}
|
|
461
510
|
}
|