///|
/// - Does: Describes which children of one selector step are addressed.
/// - Input: One of `All`, `Index(Int)`, or `Slice(Int?, Int?, Int?)`.
/// - Returns: One `ESpan` value.
/// - Limits: Slice semantics are limited to the path parser implemented in this package.
pub(all) enum ESpan {
All
Index(Int)
Slice(Int?, Int?, Int?)
} derive(Eq, Debug)
///|
/// - Does: Stores one parsed selector in an expression path.
/// - Input: Attribute filters, type filters, and one `ESpan`.
/// - Returns: One `ESelector` value.
/// - Limits: Constructed selectors follow the parser rules in this package and do not validate against external schemas.
pub(all) struct ESelector {
attrs : Array[String]
types : Array[String]
span : ESpan
}
///|
/// - Does: Stores a parsed expression path and its selector sequence.
/// - Input: The original raw path plus parsed selectors.
/// - Returns: One `EPath` value.
/// - Limits: The path grammar is limited to the selectors implemented here.
pub(all) struct EPath {
raw : String
selectors : Array[ESelector]
}
///|
/// - Does: Parses one path string into an `EPath` selector sequence.
/// - Input: One `String`.
/// - Returns: One `EPath`.
/// - Limits: Invalid or unsupported syntax falls back to the subset accepted by the local parser instead of raising.
pub fn EPath::new(path : String) -> EPath {
EPath::{ raw: path, selectors: parse_path(path) }
}
///|
/// - Does: Selects every subexpression matched by one path.
/// - Input: One `EPath` and one root `Expr`.
/// - Returns: `Array[Expr]`.
/// - Limits: Unsupported selectors simply match nothing instead of raising.
pub fn EPath::select(self : EPath, expr : Expr) -> Array[Expr] {
let out : Array[Expr] = Array::new()
select_at(self.selectors, 0, expr, out)
out
}
///|
/// - Does: Applies one callback to every subexpression matched by one path.
/// - Input: One `EPath`, one root `Expr`, and one callback `(Expr) -> Expr`.
/// - Returns: One rewritten `Expr`.
/// - Limits: Unsupported selectors leave the expression unchanged instead of raising.
pub fn EPath::apply(self : EPath, expr : Expr, f : (Expr) -> Expr) -> Expr {
apply_at(self.selectors, 0, expr, f)
}
///|
/// - Does: Convenience wrapper for parsing a path and selecting matches immediately.
/// - Input: One path string and one root `Expr`.
/// - Returns: `Array[Expr]`.
/// - Limits: Follows the same parser and matching limitations as `EPath::new` and `EPath::select`.
pub fn epath(path : String, expr : Expr) -> Array[Expr] {
EPath::new(path).select(expr)
}
///|
/// - Does: Convenience wrapper for parsing a path and rewriting matches immediately.
/// - Input: One path string, one root `Expr`, and one callback `(Expr) -> Expr`.
/// - Returns: One rewritten `Expr`.
/// - Limits: Follows the same parser and matching limitations as `EPath::new` and `EPath::apply`.
pub fn epath_apply(path : String, expr : Expr, f : (Expr) -> Expr) -> Expr {
EPath::new(path).apply(expr, f)
}
///|
fn parse_path(path : String) -> Array[ESelector] {
match path.strip_prefix("/") {
Some(rest) => {
let selectors : Array[ESelector] = Array::new()
for raw_sel in rest.split("/") {
let sel = raw_sel.trim().to_owned()
if sel != "" {
selectors.push(parse_selector(sel))
}
}
selectors
}
None => []
}
}
///|
fn is_selector_head_char(c : Char) -> Bool {
c.is_ascii_alphabetic() ||
c.is_ascii_digit() ||
c == '_' ||
c == '|' ||
c == '?'
}
///|
fn split_selector(raw : String) -> (String, String) {
let head = StringBuilder::new()
let tail = StringBuilder::new()
let mut in_tail = false
for c in raw {
if !in_tail && is_selector_head_char(c) {
head.write_char(c)
} else {
in_tail = true
tail.write_char(c)
}
}
(head.to_string(), tail.to_string())
}
///|
fn parse_selector(raw : String) -> ESelector {
let (head, tail) = split_selector(raw)
let attrs : Array[String] = Array::new()
let types : Array[String] = Array::new()
if head != "" {
for part_view in head.split("|") {
let part = part_view.trim().to_owned()
if part != "" {
match part.strip_suffix("?") {
Some(attr) => attrs.push(attr.to_owned())
None => types.push(part)
}
}
}
}
let span = parse_selector_span(tail)
ESelector::{ attrs, types, span }
}
///|
fn parse_selector_span(tail : String) -> ESpan {
if tail == "" || tail == "*" {
ESpan::All
} else if tail.has_prefix("[") && tail.has_suffix("]") {
match tail.strip_prefix("[") {
Some(without_prefix) =>
match without_prefix.strip_suffix("]") {
Some(content) => parse_span(content.to_owned())
None => ESpan::All
}
None => ESpan::All
}
} else {
ESpan::All
}
}
///|
fn parse_int_opt(text : String) -> Int? {
let parsed = try? @string.parse_int(text)
match parsed {
Ok(value) => Some(value)
Err(_) => None
}
}
///|
fn parse_span_part(parts : Array[StringView], idx : Int) -> Int? {
if idx >= parts.length() {
return None
}
let token = parts[idx].trim().to_owned()
if token == "" {
None
} else {
parse_int_opt(token)
}
}
///|
fn parse_span(content : String) -> ESpan {
let trimmed = content.trim().to_owned()
if trimmed == "" {
return ESpan::All
}
if !trimmed.contains(":") {
match parse_int_opt(trimmed) {
Some(idx) => ESpan::Index(idx)
None => ESpan::All
}
} else {
let parts = trimmed.split(":").to_array()
let start = parse_span_part(parts, 0)
let stop = parse_span_part(parts, 1)
let step = parse_span_part(parts, 2)
ESpan::Slice(start, stop, step)
}
}
///|
fn select_at(
selectors : Array[ESelector],
idx : Int,
expr : Expr,
out : Array[Expr],
) -> Unit {
if idx >= selectors.length() {
out.push(expr)
return
}
let sel = selectors[idx]
let children = ordered_children(expr)
let indices = span_indices(sel.span, children.length())
for i in indices {
if i < 0 || i >= children.length() {
continue
}
let child = children[i]
if selector_matches(sel, child) {
select_at(selectors, idx + 1, child, out)
}
}
}
///|
fn apply_at(
selectors : Array[ESelector],
idx : Int,
expr : Expr,
f : (Expr) -> Expr,
) -> Expr {
if idx >= selectors.length() {
return f(expr)
}
let sel = selectors[idx]
let children = ordered_children(expr)
if children.is_empty() {
return expr
}
let updated = children.map(c => c)
let indices = span_indices(sel.span, children.length())
for i in indices {
if i < 0 || i >= children.length() {
continue
}
let child = children[i]
if selector_matches(sel, child) {
updated[i] = apply_at(selectors, idx + 1, child, f)
}
}
rebuild_expr(expr, updated)
}
///|
fn selector_matches(sel : ESelector, expr : Expr) -> Bool {
if sel.attrs.is_empty() && sel.types.is_empty() {
return true
}
for a in sel.attrs {
if has_attr(expr, a) {
return true
}
}
for t in sel.types {
if has_type(expr, t) {
return true
}
}
false
}
///|
fn has_type(expr : Expr, t : String) -> Bool {
match expr {
Expr::Number(_) => t == "Number" || t == "Expr"
Expr::Float(_) => t == "Float" || t == "Number" || t == "Expr"
Expr::ComplexFloat(_) =>
t == "ComplexFloat" || t == "Float" || t == "Number" || t == "Expr"
Expr::NumberSymbol(_) => t == "NumberSymbol" || t == "Number" || t == "Expr"
Expr::Boolean(_) => t == "Boolean" || t == "Expr"
Expr::Dummy(_, _) => t == "Dummy" || t == "Symbol" || t == "Expr"
Expr::Wild(_, _, _) => t == "Wild" || t == "Symbol" || t == "Expr"
Expr::FunctionHead(_) =>
t == "FunctionHead" ||
t == "UndefinedFunction" ||
t == "Function" ||
t == "Expr"
Expr::UndefinedFunction(_) =>
t == "UndefinedFunction" || t == "Function" || t == "Expr"
Expr::Apply(head, _) =>
match head {
Expr::UndefinedFunction(_) =>
t == "AppliedUndef" || t == "Apply" || t == "Function" || t == "Expr"
_ => t == "Apply" || t == "Function" || t == "Expr"
}
Expr::Symbol(_) => t == "Symbol" || t == "Expr"
Expr::Add(_) => t == "Add" || t == "Expr"
Expr::Mul(_) => t == "Mul" || t == "Expr"
Expr::Pow(_, _) => t == "Pow" || t == "Expr"
Expr::Mod(_, _) => t == "Mod" || t == "Expr"
Expr::Tuple(_) => t == "Tuple" || t == "Expr"
Expr::Dict(_) => t == "Dict" || t == "Expr"
Expr::Relational(_, _, _) => t == "Relational" || t == "Expr"
Expr::Derivative(_, _) => t == "Derivative" || t == "Expr"
Expr::Subs(_, _, _) => t == "Subs" || t == "Expr"
Expr::Lambda(_, _) => t == "Lambda" || t == "Expr"
_ if @symcore.application_parts(expr) is Some(_) =>
t == "Function" || t == "Expr"
_ => false
}
}
///|
fn has_attr(expr : Expr, a : String) -> Bool {
match a {
"is_Atom" => epath_is_atom(expr)
"__iter__" => !ordered_children(expr).is_empty()
"is_Add" =>
match expr {
Expr::Add(_) => true
_ => false
}
"is_Mul" =>
match expr {
Expr::Mul(_) => true
_ => false
}
"is_Pow" =>
match expr {
Expr::Pow(_, _) => true
_ => false
}
"is_Function" => @symcore.application_parts(expr) is Some(_)
_ => false
}
}
///|
fn epath_is_atom(expr : Expr) -> Bool {
match expr {
Expr::Number(_)
| Expr::Float(_)
| Expr::ComplexFloat(_)
| Expr::Boolean(_)
| Expr::Symbol(_) => true
_ => false
}
}
///|
fn ordered_children(expr : Expr) -> Array[Expr] {
match expr {
Expr::Add(args) | Expr::Mul(args) | Expr::Tuple(args) => args.map(x => x)
Expr::Pow(a, b) => [a, b]
Expr::Relational(_, lhs, rhs) => [lhs, rhs]
Expr::Derivative(inner, deriv_args) => {
let out : Array[Expr] = [inner]
for arg in deriv_args {
out.push(arg)
}
out
}
Expr::Subs(inner, variable, value) => [inner, variable, value]
Expr::Lambda(vars, body) => [vars, body]
_ =>
match @symcore.application_args(expr) {
Some(args) => args.map(x => x)
None => []
}
}
}
///|
fn rebuild_expr(expr : Expr, args : Array[Expr]) -> Expr {
match expr {
Expr::Add(_) => @symcore.raw_add(args)
Expr::Mul(_) => @symcore.raw_mul(args)
Expr::Pow(_, _) =>
if args.length() == 2 {
@symcore.raw_pow(args[0], args[1])
} else {
expr
}
Expr::Tuple(_) => @symcore.Expr::Tuple(args)
Expr::Relational(op, _, _) =>
if args.length() == 2 {
match op {
@symcore.RelOp::Eq =>
@symcore.Expr::Relational(@symcore.RelOp::Eq, args[0], args[1])
@symcore.RelOp::Ne =>
@symcore.Expr::Relational(@symcore.RelOp::Ne, args[0], args[1])
@symcore.RelOp::Lt =>
@symcore.Expr::Relational(@symcore.RelOp::Lt, args[0], args[1])
@symcore.RelOp::Le =>
@symcore.Expr::Relational(@symcore.RelOp::Le, args[0], args[1])
@symcore.RelOp::Gt =>
@symcore.Expr::Relational(@symcore.RelOp::Gt, args[0], args[1])
@symcore.RelOp::Ge =>
@symcore.Expr::Relational(@symcore.RelOp::Ge, args[0], args[1])
}
} else {
expr
}
Expr::Derivative(_, _) =>
if args.length() >= 2 {
@symcore.raw_function("Derivative", args)
} else {
expr
}
Expr::Subs(_, _, _) =>
if args.length() == 3 {
@symcore.subs_expr(args[0], args[1], args[2])
} else {
expr
}
Expr::Lambda(_, _) =>
if args.length() == 2 {
@symcore.lambda_expr(args[0], args[1])
} else {
expr
}
_ =>
match @symcore.application_parts(expr) {
Some((head, _)) => @symcore.raw_apply(head, args).unwrap_or(expr)
None => expr
}
}
}
///|
fn span_indices(span : ESpan, n : Int) -> Array[Int] {
match span {
ESpan::All => {
let out : Array[Int] = Array::new()
for i in 0.. [i]
ESpan::Slice(start, stop, step) => {
let s = match step {
Some(v) if v != 0 => v
_ => 1
}
let mut i = match start {
Some(v) => if v < 0 { n + v } else { v }
None => if s > 0 { 0 } else { n - 1 }
}
let end = match stop {
Some(v) => if v < 0 { n + v } else { v }
None => if s > 0 { n } else { -1 }
}
let out : Array[Int] = Array::new()
if s > 0 {
while i < end {
out.push(i)
i = i + s
}
} else {
while i > end {
out.push(i)
i = i + s
}
}
out
}
}
}