Skip to content

Pattern-matching fixes, optimization, tests #248

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 32 additions & 11 deletions src/api.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@

# 01

## Compute Engine

Expand Down Expand Up @@ -1273,6 +1273,10 @@ is canonical.

:::info[Note]
Applicable to canonical and non-canonical expressions.

If this is a function, an empty substitution is given, and the computed value of `canonical`
does not differ from that of this expr.: then a call this method is analagous to requesting a
*clone*.
:::

####### sub
Expand Down Expand Up @@ -1344,11 +1348,24 @@ Transform the expression by applying one or more replacement rules:

See also `expr.subs()` for a simple substitution of symbols.

If `options.canonical` is not set, the result is canonical if `this`
is canonical.
Procedure for the determining the canonical-status of the input expression and replacements:

- If `options.canonical` is set, the *entire expr.* is canonicalized to this degree: whether
the replacement occurs at the top-level, or within/recursively.

- If otherwise, the *direct replacement will be canonical* if either the 'replaced' expression
is canonical, or the given replacement (- is a BoxedExpression and -) is canonical.
Notably also, if this replacement takes place recursively (not at the top-level), then exprs.
containing the replaced expr. will still however have their (previous) canonical-status
*preserved*... unless this expr. was previously non-canonical, and *replacements have resulted
in canonical operands*. In this case, an expr. meeting this criteria will be updated to
canonical status. (Canonicalization is opportunistic here, in other words).

:::info[Note]
Applicable to canonical and non-canonical expressions.

To specify a match for single symbol (non wildcard), it must be boxed (e.g. `{ match:
ce.box('x'), ... }`), but it is suggested to use method *'subs()'* for this.
:::

####### rules
Expand Down Expand Up @@ -2647,9 +2664,9 @@ type PatternMatchOptions = {

Control how a pattern is matched to an expression.

- `substitution`: if present, assumes these values for the named wildcards,
and ensure that subsequent occurrence of the same wildcard have the same
value.
- `substitution`: if present, assumes these values for a subset of
named wildcards, and ensure that subsequent occurrence of the same
wildcard have the same value.
- `recursive`: if true, match recursively, otherwise match only the top
level.
- `useVariations`: if false, only match expressions that are structurally identical.
Expand Down Expand Up @@ -2784,18 +2801,22 @@ type Rule =
};
```

A rule describes how to modify an expressions that matches a pattern `match`
A rule describes how to modify an expression that matches a pattern `match`
into a new expression `replace`.

- `x-1` \( \to \) `1-x`
- `(x+1)(x-1)` \( \to \) `x^2-1

The patterns can be expressed as LaTeX strings or a MathJSON expressions.
The patterns can be expressed as LaTeX strings or `SemiBoxedExpression`'s.
Alternatively, match/replace logic may be specified by a `RuleFunction`, allowing both custom
logic/conditions for the match, and either a *BoxedExpression* (or `RuleStep` if being
descriptive) for the replacement.

As a shortcut, a rule can be defined as a LaTeX string: `x-1 -> 1-x`.
The expression to the left of `->` is the `match` and the expression to the
right is the `replace`. When using LaTeX strings, single character variables
are assumed to be wildcards.
are assumed to be wildcards. The rule LHS ('match') and RHS ('replace') may also be supplied
separately: in this case following the same rules.

When using MathJSON expressions, anonymous wildcards (`_`) will match any
expression. Named wildcards (`_x`, `_a`, etc...) will match any expression
Expand Down Expand Up @@ -8042,7 +8063,7 @@ valueOf(): string

</MemberCard>


# 02

## MathJSON

Expand Down Expand Up @@ -8213,7 +8234,7 @@ The dictionary and function nodes can contain expressions themselves.

</MemberCard>


# 03

## Type

Expand Down
39 changes: 34 additions & 5 deletions src/common/utils.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,39 @@
export function permutations<T>(
xs: ReadonlyArray<T>
/**
*
* <!--
* !@consider?
* - In terms of BoxedExpressions - optimizations which are always desirable to take place are
* possible...
* ^Perhaps then, a wrapper BoxedExpr. utility for specifying these permutations via 'condition'
* would be apt...?
*
* - ^If wishing to take adv. of this, the 'condition' callback would likely benefit from a second parameter typed as a collection
* ('Set' if enforcing unique) with all hitherto (arbitrary representations) of generated
* permutations.
* (See commented snippets within function signature below.)
* -->
*
* @export
* @template T
* @param xs
* @param [condition]
* @returns
*/
export function permutations<T /* , Y extends any = any */>(
xs: ReadonlyArray<T>,
condition?: (
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thought this made sense (see call from matchPermutation beneath), but feel free to revert if feeling it doesn't belong

xs: ReadonlyArray<T> /* , generated: Set<Y> | Set<[Y,T]>? */
) => boolean
// cacheKey?: (T) => Y
): ReadonlyArray<ReadonlyArray<T>> {
const result: ReadonlyArray<T>[] = [];

const permute = (arr, m = []) => {
const permute = (arr: T[], m: T[] = []) => {
if (arr.length === 0) {
result.push(m);
if (!condition || condition(m)) {
// Use spread operator to create a shallow copy of m
result.push([...m]);
}
} else {
for (let i = 0; i < arr.length; i++) {
const curr = arr.slice();
Expand All @@ -15,7 +43,8 @@ export function permutations<T>(
}
};

permute(xs);
//@fix: (typing)
permute(xs as T[]);

return result;
}
Expand Down
51 changes: 51 additions & 0 deletions src/compute-engine/boxed-expression/boxed-patterns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ export function isWildcard(expr: BoxedExpression): expr is BoxedSymbol {
);
}

/**
* Return the string representing this wildcard, including any optional (one-character) name, or
* `null` if not a wildcard expression.
*
* @export
* @param expr
* @returns
*/
export function wildcardName(expr: BoxedExpression): string | null {
if (expr.symbol?.startsWith('_')) return expr.symbol;

Expand All @@ -27,3 +35,46 @@ export function wildcardName(expr: BoxedExpression): string | null {

return null;
}

/**
*
* <!--
* @todo:
* - Utilize moreso (e.g. ./match.ts)
* - 'Wildcard' -> 'Universal', for clarity...?
* -->
*
* @export
* @param expr
* @returns
*/
export function wildcardType(
expr: BoxedExpression | string
): 'Wildcard' | 'Sequence' | 'OptionalSequence' | null {

if (typeof expr === 'string') {
if (expr.startsWith('_')) {
if (expr.startsWith('__')) {
if (expr.startsWith('___')) return 'Sequence';
return 'OptionalSequence';
}
return 'Wildcard';
}
return null;
}

if (expr.symbol !== null) {
const symbol = expr.symbol!;
if (!symbol.startsWith('_')) return null;
if (!symbol.startsWith('__')) return 'Wildcard';
return symbol.startsWith('___') ? 'OptionalSequence' : 'Sequence';
}

if (expr.isFunctionExpression) {
if (expr.operator === 'Wildcard') return 'Wildcard';
if (expr.operator === 'WildcardSequence') return 'Sequence';
if (expr.operator === 'WildcardOptionalSequence') return 'OptionalSequence';
}

return null;
}
Loading