1pub mod apl;
4pub mod j;
5
6use crate::error::{Error, ErrorKind, Result};
7use crate::fmt::FmtOpts;
8use crate::ir::{ParamSpec, Program};
9use crate::verb::{Agreement, Tol};
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub enum Lang {
13 J,
14 Apl,
15}
16
17impl Lang {
18 pub fn from_name(name: &str) -> Option<Lang> {
19 match name.to_ascii_lowercase().as_str() {
20 "j" => Some(Lang::J),
21 "apl" => Some(Lang::Apl),
22 _ => None,
23 }
24 }
25}
26
27#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
33pub enum NestedModel {
34 #[default]
35 Floating,
36 Grounded,
37}
38
39#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
44pub enum FirstDisclose {
45 #[default]
46 UpIsFirst,
47 UpIsMix,
48}
49
50#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
56pub enum IndexForm {
57 #[default]
58 ScalarPerAxis,
59 AxisVectors,
60}
61
62#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
68pub enum DfnResult {
69 #[default]
70 LastSentence,
71 FirstNonAssignment,
72}
73
74#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
80pub enum DefaultArg {
81 #[default]
82 Eager,
83 Lazy,
84}
85
86#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
93pub enum ComplexOrder {
94 #[default]
95 RealThenImaginary,
96 MagnitudeThenAngle,
97}
98
99#[derive(Clone, Copy, Debug, Default, PartialEq)]
112pub struct Dialect {
113 pub index_origin: Option<i64>,
115 pub comparison_tolerance: Option<f64>,
117 pub nested_model: NestedModel,
118 pub first_disclose: FirstDisclose,
119 pub index_form: IndexForm,
120 pub dfn_result: DfnResult,
121 pub default_arg: DefaultArg,
122 pub complex_order: ComplexOrder,
123 pub trains: bool,
125}
126
127impl Dialect {
128 pub fn gnu_apl() -> Dialect {
133 Dialect {
134 index_origin: None,
135 comparison_tolerance: None,
136 nested_model: NestedModel::Floating,
137 first_disclose: FirstDisclose::UpIsFirst,
138 index_form: IndexForm::ScalarPerAxis,
139 dfn_result: DfnResult::LastSentence,
140 default_arg: DefaultArg::Eager,
141 complex_order: ComplexOrder::RealThenImaginary,
142 trains: false,
143 }
144 }
145
146 pub fn j() -> Dialect {
150 Dialect::default()
151 }
152
153 pub fn rules(&self, lang: Lang) -> Result<Rules> {
160 let refuse = |what: &str| -> Error {
163 Error::new(
164 ErrorKind::NotYet,
165 format!("{what} (the reading of another APL dialect) is not supported yet"),
166 None,
167 )
168 .note("libjay implements the APL2/ISO line, which is the one its oracle verifies")
169 };
170 if let Some(ct) = self.comparison_tolerance {
171 if !(ct.is_finite() && ct >= 0.0) {
172 return Err(Error::new(
173 ErrorKind::Domain,
174 "the comparison tolerance must be a finite value at or above zero",
175 None,
176 ));
177 }
178 }
179 match self.nested_model {
180 NestedModel::Floating => {}
181 NestedModel::Grounded => return Err(refuse("a grounded nested array model")),
182 }
183 match self.first_disclose {
184 FirstDisclose::UpIsFirst => {}
185 FirstDisclose::UpIsMix => return Err(refuse("↑ as mix and ⊃ as first")),
186 }
187 match self.index_form {
188 IndexForm::ScalarPerAxis => {}
189 IndexForm::AxisVectors => return Err(refuse("⌷ over index vectors")),
190 }
191 match self.dfn_result {
192 DfnResult::LastSentence => {}
193 DfnResult::FirstNonAssignment => {
194 return Err(refuse("a dfn that answers with its first non-assignment sentence"))
195 }
196 }
197 match self.default_arg {
198 DefaultArg::Eager => {}
199 DefaultArg::Lazy => return Err(refuse("a lazy ⍺← default")),
200 }
201 match self.complex_order {
202 ComplexOrder::RealThenImaginary => {}
203 ComplexOrder::MagnitudeThenAngle => {
204 return Err(refuse("grading complex values by magnitude and angle"))
205 }
206 }
207 if self.trains {
208 return Err(refuse("trains"));
209 }
210 let origin = match lang {
211 Lang::J => 0,
212 Lang::Apl => self.index_origin.unwrap_or(1),
213 };
214 let ct = self.comparison_tolerance.unwrap_or(match lang {
215 Lang::J => Tol::J.ct,
216 Lang::Apl => Tol::APL.ct,
217 });
218 Ok(Rules {
219 lang,
220 origin,
221 ct,
222 nested_model: self.nested_model,
223 first_disclose: self.first_disclose,
224 index_form: self.index_form,
225 dfn_result: self.dfn_result,
226 default_arg: self.default_arg,
227 complex_order: self.complex_order,
228 trains: self.trains,
229 })
230 }
231}
232
233#[derive(Clone, Copy, Debug, PartialEq)]
238pub struct Rules {
239 pub lang: Lang,
240 pub origin: i64,
242 pub ct: f64,
246 pub nested_model: NestedModel,
247 pub first_disclose: FirstDisclose,
248 pub index_form: IndexForm,
249 pub dfn_result: DfnResult,
250 pub default_arg: DefaultArg,
251 pub complex_order: ComplexOrder,
252 pub trains: bool,
253}
254
255impl Rules {
256 pub fn tol(&self) -> Tol {
258 Tol { ct: self.ct, by_smaller: self.lang == Lang::J }
259 }
260
261 pub fn dialect(&self) -> Dialect {
264 Dialect {
265 index_origin: Some(self.origin),
266 comparison_tolerance: Some(self.ct),
267 nested_model: self.nested_model,
268 first_disclose: self.first_disclose,
269 index_form: self.index_form,
270 dfn_result: self.dfn_result,
271 default_arg: self.default_arg,
272 complex_order: self.complex_order,
273 trains: self.trains,
274 }
275 }
276}
277
278impl Default for Rules {
279 fn default() -> Rules {
281 Dialect::default().rules(Lang::J).expect("J's defaults are implemented")
282 }
283}
284
285#[derive(Clone, Debug)]
288pub struct SourceParts {
289 pub display: String,
290 pub segments: Vec<Segment>,
291 pub param_names: Vec<String>,
292}
293
294#[derive(Clone, Debug)]
295pub enum Segment {
296 Text { text: String, offset: usize },
298 Param { index: usize, offset: usize, len: usize },
300}
301
302impl SourceParts {
303 pub fn from_parts(parts: &[&str], names: &[&str]) -> SourceParts {
307 assert_eq!(parts.len(), names.len() + 1, "N parts need N-1 holes");
308 let mut display = String::new();
309 let mut segments = Vec::new();
310 let mut param_names: Vec<String> = Vec::new();
311 for (i, part) in parts.iter().enumerate() {
312 if !part.is_empty() {
313 segments.push(Segment::Text { text: (*part).to_string(), offset: display.len() });
314 display.push_str(part);
315 }
316 if i < names.len() {
317 let name = names[i];
318 let index = param_names
319 .iter()
320 .position(|n| n == name)
321 .unwrap_or_else(|| {
322 param_names.push(name.to_string());
323 param_names.len() - 1
324 });
325 let shown = format!("{{{name}}}");
326 segments.push(Segment::Param { index, offset: display.len(), len: shown.len() });
327 display.push_str(&shown);
328 }
329 }
330 SourceParts { display, segments, param_names }
331 }
332
333 pub fn from_source(src: &str) -> Result<SourceParts> {
336 let bytes = src.as_bytes();
337 let mut parts: Vec<String> = vec![String::new()];
338 let mut names: Vec<String> = Vec::new();
339 let mut in_quote = false;
340 let mut i = 0;
341 while i < src.len() {
342 let ch = src[i..].chars().next().unwrap();
343 if ch == '\'' {
344 in_quote = !in_quote;
345 parts.last_mut().unwrap().push(ch);
346 i += 1;
347 continue;
348 }
349 if ch == '{' && !in_quote {
350 let rest = &src[i + 1..];
354 if let Some(end) = rest.find('}') {
355 let name = &rest[..end];
356 if is_identifier(name) {
357 names.push(name.to_string());
358 parts.push(String::new());
359 i += 2 + end;
360 continue;
361 }
362 }
363 }
364 parts.last_mut().unwrap().push(ch);
365 i += ch.len_utf8();
366 }
367 let _ = bytes;
368 let part_refs: Vec<&str> = parts.iter().map(|s| s.as_str()).collect();
369 let name_refs: Vec<&str> = names.iter().map(|s| s.as_str()).collect();
370 Ok(SourceParts::from_parts(&part_refs, &name_refs))
371 }
372}
373
374fn is_identifier(s: &str) -> bool {
375 let mut chars = s.chars();
376 match chars.next() {
377 Some(c) if c.is_ascii_alphabetic() || c == '_' => {}
378 _ => return false,
379 }
380 chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
381}
382
383pub fn compile(lang: Lang, source: &str, dialect: &Dialect) -> Result<Program> {
385 let sp = SourceParts::from_source(source)?;
386 compile_source_parts(lang, sp, dialect)
387}
388
389pub fn compile_parts(
391 lang: Lang,
392 parts: &[&str],
393 names: &[&str],
394 dialect: &Dialect,
395) -> Result<Program> {
396 compile_source_parts(lang, SourceParts::from_parts(parts, names), dialect)
397}
398
399fn compile_source_parts(lang: Lang, sp: SourceParts, dialect: &Dialect) -> Result<Program> {
400 let rules = dialect.rules(lang)?;
401 let tol = rules.tol();
402 let (mut stmts, agreement, fmt) = match lang {
403 Lang::J => (j::parse(&sp)?, Agreement::LeadingPrefix, FmtOpts::J),
404 Lang::Apl => (apl::parse(&sp, rules)?, Agreement::ExactOrScalar, FmtOpts::APL),
405 };
406 for stmt in &stmts {
410 crate::verb::check_nesting(stmt.depth(), stmt.span())?;
411 }
412 crate::fuse::pass(&mut stmts, tol);
413 let params = sp.param_names.into_iter().map(|name| ParamSpec { name }).collect();
414 Ok(Program { stmts, params, display_src: sp.display, agreement, fmt, rules })
415}