pub mod apl;
pub mod j;
use crate::error::{Error, ErrorKind, Result};
use crate::fmt::FmtOpts;
use crate::ir::{ParamSpec, Program};
use crate::verb::{Agreement, Tol};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Lang {
J,
Apl,
}
impl Lang {
pub fn from_name(name: &str) -> Option<Lang> {
match name.to_ascii_lowercase().as_str() {
"j" => Some(Lang::J),
"apl" => Some(Lang::Apl),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum NestedModel {
#[default]
Floating,
Grounded,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum FirstDisclose {
#[default]
UpIsFirst,
UpIsMix,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum IndexForm {
#[default]
ScalarPerAxis,
AxisVectors,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum DfnResult {
#[default]
LastSentence,
FirstNonAssignment,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum DefaultArg {
#[default]
Eager,
Lazy,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum ComplexOrder {
#[default]
RealThenImaginary,
MagnitudeThenAngle,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum NestedGrade {
#[default]
Apl2,
TotalOrder,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Dialect {
pub index_origin: Option<i64>,
pub comparison_tolerance: Option<f64>,
pub nested_model: NestedModel,
pub first_disclose: FirstDisclose,
pub index_form: IndexForm,
pub dfn_result: DfnResult,
pub default_arg: DefaultArg,
pub complex_order: ComplexOrder,
pub nested_grade: NestedGrade,
pub trains: bool,
}
impl Default for Dialect {
fn default() -> Dialect {
Dialect::gnu_apl()
}
}
impl Dialect {
pub fn gnu_apl() -> Dialect {
Dialect {
index_origin: None,
comparison_tolerance: None,
nested_model: NestedModel::Floating,
first_disclose: FirstDisclose::UpIsFirst,
index_form: IndexForm::ScalarPerAxis,
dfn_result: DfnResult::LastSentence,
default_arg: DefaultArg::Eager,
complex_order: ComplexOrder::RealThenImaginary,
nested_grade: NestedGrade::Apl2,
trains: true,
}
}
pub fn j() -> Dialect {
Dialect::default()
}
pub fn rules(&self, lang: Lang) -> Result<Rules> {
let refuse = |what: &str| -> Error {
Error::new(
ErrorKind::NotYet,
format!("{what} (the reading of another APL dialect) is not supported yet"),
None,
)
.note("libjay implements the APL2/ISO line, which is the one its oracle verifies")
};
if let Some(ct) = self.comparison_tolerance && !(ct.is_finite() && ct >= 0.0) {
return Err(Error::new(
ErrorKind::Domain,
"the comparison tolerance must be a finite value at or above zero",
None,
));
}
match self.nested_model {
NestedModel::Floating => {}
NestedModel::Grounded => return Err(refuse("a grounded nested array model")),
}
match self.first_disclose {
FirstDisclose::UpIsFirst => {}
FirstDisclose::UpIsMix => return Err(refuse("↑ as mix and ⊃ as first")),
}
match self.index_form {
IndexForm::ScalarPerAxis => {}
IndexForm::AxisVectors => return Err(refuse("⌷ over index vectors")),
}
match self.dfn_result {
DfnResult::LastSentence => {}
DfnResult::FirstNonAssignment => {
return Err(refuse("a dfn that answers with its first non-assignment sentence"))
}
}
match self.default_arg {
DefaultArg::Eager => {}
DefaultArg::Lazy => return Err(refuse("a lazy ⍺← default")),
}
match self.complex_order {
ComplexOrder::RealThenImaginary => {}
ComplexOrder::MagnitudeThenAngle => {
return Err(refuse("grading complex values by magnitude and angle"))
}
}
match self.nested_grade {
NestedGrade::Apl2 => {}
NestedGrade::TotalOrder => {
return Err(refuse("a total array ordering for a nested grade"))
}
}
let origin = match lang {
Lang::J => 0,
Lang::Apl => self.index_origin.unwrap_or(1),
};
let ct = self.comparison_tolerance.unwrap_or(match lang {
Lang::J => Tol::J.ct,
Lang::Apl => Tol::APL.ct,
});
Ok(Rules {
lang,
origin,
ct,
nested_model: self.nested_model,
first_disclose: self.first_disclose,
index_form: self.index_form,
dfn_result: self.dfn_result,
default_arg: self.default_arg,
complex_order: self.complex_order,
nested_grade: self.nested_grade,
trains: self.trains,
})
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Rules {
pub lang: Lang,
pub origin: i64,
pub ct: f64,
pub nested_model: NestedModel,
pub first_disclose: FirstDisclose,
pub index_form: IndexForm,
pub dfn_result: DfnResult,
pub default_arg: DefaultArg,
pub complex_order: ComplexOrder,
pub nested_grade: NestedGrade,
pub trains: bool,
}
impl Rules {
pub fn tol(&self) -> Tol {
Tol { ct: self.ct, by_smaller: self.lang == Lang::J }
}
pub fn dialect(&self) -> Dialect {
Dialect {
index_origin: Some(self.origin),
comparison_tolerance: Some(self.ct),
nested_model: self.nested_model,
first_disclose: self.first_disclose,
index_form: self.index_form,
dfn_result: self.dfn_result,
default_arg: self.default_arg,
complex_order: self.complex_order,
nested_grade: self.nested_grade,
trains: self.trains,
}
}
}
impl Default for Rules {
fn default() -> Rules {
Dialect::default().rules(Lang::J).expect("J's defaults are implemented")
}
}
#[derive(Clone, Debug)]
pub struct SourceParts {
pub display: String,
pub segments: Vec<Segment>,
pub param_names: Vec<String>,
}
#[derive(Clone, Debug)]
pub enum Segment {
Text { text: String, offset: usize },
Param { index: usize, offset: usize, len: usize },
}
impl SourceParts {
pub fn from_parts(parts: &[&str], names: &[&str]) -> SourceParts {
assert_eq!(parts.len(), names.len() + 1, "N parts need N-1 holes");
let mut display = String::new();
let mut segments = Vec::new();
let mut param_names: Vec<String> = Vec::new();
for (i, part) in parts.iter().enumerate() {
if !part.is_empty() {
segments.push(Segment::Text { text: (*part).to_string(), offset: display.len() });
display.push_str(part);
}
if i < names.len() {
let name = names[i];
let index = param_names
.iter()
.position(|n| n == name)
.unwrap_or_else(|| {
param_names.push(name.to_string());
param_names.len() - 1
});
let shown = format!("{{{name}}}");
segments.push(Segment::Param { index, offset: display.len(), len: shown.len() });
display.push_str(&shown);
}
}
SourceParts { display, segments, param_names }
}
pub fn from_source(src: &str) -> Result<SourceParts> {
let bytes = src.as_bytes();
let mut parts: Vec<String> = vec![String::new()];
let mut names: Vec<String> = Vec::new();
let mut in_quote = false;
let mut i = 0;
while i < src.len() {
let ch = src[i..].chars().next().unwrap();
if ch == '\'' {
in_quote = !in_quote;
parts.last_mut().unwrap().push(ch);
i += 1;
continue;
}
if ch == '{' && !in_quote {
let rest = &src[i + 1..];
if let Some(end) = rest.find('}') {
let name = &rest[..end];
if is_identifier(name) {
names.push(name.to_string());
parts.push(String::new());
i += 2 + end;
continue;
}
}
}
parts.last_mut().unwrap().push(ch);
i += ch.len_utf8();
}
let _ = bytes;
let part_refs: Vec<&str> = parts.iter().map(|s| s.as_str()).collect();
let name_refs: Vec<&str> = names.iter().map(|s| s.as_str()).collect();
Ok(SourceParts::from_parts(&part_refs, &name_refs))
}
}
fn is_identifier(s: &str) -> bool {
let mut chars = s.chars();
match chars.next() {
Some(c) if c.is_ascii_alphabetic() || c == '_' => {}
_ => return false,
}
chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
}
pub fn compile(lang: Lang, source: &str, dialect: &Dialect) -> Result<Program> {
let sp = SourceParts::from_source(source)?;
compile_source_parts(lang, sp, dialect)
}
pub fn compile_parts(
lang: Lang,
parts: &[&str],
names: &[&str],
dialect: &Dialect,
) -> Result<Program> {
compile_source_parts(lang, SourceParts::from_parts(parts, names), dialect)
}
fn compile_source_parts(lang: Lang, sp: SourceParts, dialect: &Dialect) -> Result<Program> {
let rules = dialect.rules(lang)?;
let tol = rules.tol();
let (mut stmts, agreement, fmt) = match lang {
Lang::J => (j::parse(&sp)?, Agreement::LeadingPrefix, FmtOpts::J),
Lang::Apl => (apl::parse(&sp, rules)?, Agreement::ExactOrScalar, FmtOpts::APL),
};
for stmt in &stmts {
crate::verb::check_nesting(stmt.depth(), stmt.span())?;
}
crate::fuse::pass(&mut stmts, tol);
let params = sp.param_names.into_iter().map(|name| ParamSpec { name }).collect();
Ok(Program { stmts, params, display_src: sp.display, agreement, fmt, rules })
}