mirror of
https://gitlab.com/c3d/db48x.git
synced 2024-09-29 05:36:58 +02:00
Implement subst
and where
Implement the `subst` and `where` commands. The `where` command can also be used as an operator `|` in expressions, for example `'A+B|A=X-3'` will evaluate as `'X-3+B'`. The HP syntax with parentheses for multiple substitutions, for example `'A+B|(A=3;B=2)'`, is not implemented in this commit. It can be replaced with sequences, like `'A+B|A=3|B=2'`. Fixes: #1187 Signed-off-by: Christophe de Dinechin <christophe@dinechin.org>
This commit is contained in:
parent
9812e0532f
commit
3c79ef7f9c
6 changed files with 208 additions and 12 deletions
|
@ -2424,6 +2424,7 @@ COMMAND_BODY(Apply)
|
|||
}
|
||||
|
||||
|
||||
|
||||
// ============================================================================
|
||||
//
|
||||
// User-accessible match commands
|
||||
|
@ -2965,3 +2966,152 @@ FUNCTION_BODY(Simplify)
|
|||
{
|
||||
return do_rewrite(x, &expression::simplify);
|
||||
}
|
||||
|
||||
|
||||
static expression_p substitute(expression_r pattern,
|
||||
symbol_r name,
|
||||
expression_r to)
|
||||
// ----------------------------------------------------------------------------
|
||||
// Substitute a single name with the corresponding expression
|
||||
// ----------------------------------------------------------------------------
|
||||
{
|
||||
scribble scr;
|
||||
size_t replsz = 0;
|
||||
object_g replobj = to->objects(&replsz);
|
||||
for (object_g obj : *pattern)
|
||||
{
|
||||
symbol_p oname = obj->as<symbol>();
|
||||
if (oname && name->is_same_as(oname))
|
||||
{
|
||||
byte *objcopy = rt.allocate(replsz);
|
||||
if (!objcopy)
|
||||
return nullptr;
|
||||
memmove(objcopy, +replobj, replsz);
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t sz = obj->size();
|
||||
byte *objcopy = rt.allocate(sz);
|
||||
if (!objcopy)
|
||||
return nullptr;
|
||||
memmove(objcopy, +obj, sz);
|
||||
}
|
||||
}
|
||||
expression_g result = expression_p(list::make(object::ID_expression,
|
||||
scr.scratch(), scr.growth()));
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
static expression_p substitute(expression_r pattern,
|
||||
expression_r repl)
|
||||
// ----------------------------------------------------------------------------
|
||||
// Run a rewrite up or down
|
||||
// ----------------------------------------------------------------------------
|
||||
{
|
||||
expression_g from = repl->left_of_equation();
|
||||
expression_g to = repl->right_of_equation();
|
||||
if (!from || !to)
|
||||
return nullptr;
|
||||
symbol_g name = from->as_quoted<symbol>();
|
||||
if (+from == +to || !name)
|
||||
{
|
||||
rt.value_error();
|
||||
return nullptr;
|
||||
}
|
||||
return substitute(pattern, name, to);
|
||||
}
|
||||
|
||||
|
||||
NFUNCTION_BODY(Subst)
|
||||
// ----------------------------------------------------------------------------
|
||||
// Perform a substitution without evaluating the resulting expression
|
||||
// ----------------------------------------------------------------------------
|
||||
{
|
||||
if (expression_g pat = args[1]->as<expression>())
|
||||
if (expression_g repl = args[0]->as<expression>())
|
||||
return substitute(pat, repl);
|
||||
|
||||
if (args[1]->is_real() || args[1]->is_complex())
|
||||
return args[1];
|
||||
|
||||
rt.type_error();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
COMMAND_BODY(Where)
|
||||
// ----------------------------------------------------------------------------
|
||||
// Perform a substitution and evaluate the resulting expression
|
||||
// ----------------------------------------------------------------------------
|
||||
{
|
||||
if (object_p patobj = rt.stack(1))
|
||||
{
|
||||
id patty = patobj->type();
|
||||
if (patty == ID_expression)
|
||||
{
|
||||
expression_g pat = expression_p(patobj);
|
||||
if (object_p replobj = rt.stack(0))
|
||||
{
|
||||
id rty = replobj->type();
|
||||
if (rty == ID_expression)
|
||||
{
|
||||
expression_g repl = expression_p(replobj);
|
||||
if (algebraic_g res = substitute(pat, repl))
|
||||
if (rt.drop() && rt.top(+res))
|
||||
return OK;
|
||||
}
|
||||
else if (rty == ID_list || rty == ID_array)
|
||||
{
|
||||
symbol_g name;
|
||||
expression_g repl;
|
||||
for (object_g item : *list_p(replobj))
|
||||
{
|
||||
if (!name)
|
||||
{
|
||||
name = item->as_quoted<symbol>();
|
||||
if (!name)
|
||||
{
|
||||
if (expression_g p = item->as<expression>())
|
||||
{
|
||||
pat = substitute(pat, p);
|
||||
if (!pat)
|
||||
return ERROR;
|
||||
}
|
||||
else
|
||||
{
|
||||
rt.value_error();
|
||||
return ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
repl = item->as<expression>();
|
||||
if (!repl)
|
||||
{
|
||||
rt.value_error();
|
||||
return ERROR;
|
||||
}
|
||||
pat = substitute(pat, name, repl);
|
||||
if (!pat)
|
||||
return ERROR;
|
||||
name = nullptr;
|
||||
}
|
||||
}
|
||||
if (rt.drop() && rt.top(+pat))
|
||||
return OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (is_real(patty) || is_complex(patty))
|
||||
{
|
||||
rt.drop();
|
||||
return OK;
|
||||
}
|
||||
|
||||
}
|
||||
if (!rt.error())
|
||||
rt.type_error();
|
||||
return ERROR;
|
||||
}
|
||||
|
|
|
@ -620,5 +620,7 @@ FUNCTION(ReorderTerms);
|
|||
FUNCTION(Simplify);
|
||||
|
||||
COMMAND_DECLARE(Apply, 2);
|
||||
COMMAND_DECLARE_SPECIAL(Where, arithmetic, 2, PREC_DECL(WHERE); );
|
||||
NFUNCTION(Subst, 2, static bool can_be_symbolic(uint) { return true; } );
|
||||
|
||||
#endif // EXPRESSION_H
|
||||
|
|
|
@ -395,6 +395,9 @@ CMD(Max)
|
|||
CMD(NSub)
|
||||
CMD(EndSub)
|
||||
|
||||
OP(Where, "|")
|
||||
NAMED(Subst, "Substitute")
|
||||
|
||||
CMD(re)
|
||||
CMD(im)
|
||||
CMD(arg)
|
||||
|
|
21
src/menu.cc
21
src/menu.cc
|
@ -434,18 +434,19 @@ MENU(NumbersMenu,
|
|||
"∏", ID_Product,
|
||||
"QuoRem", ID_Div2,
|
||||
"Factors", ID_Unimplemented,
|
||||
|
||||
"Ran#", ID_RandomNumber,
|
||||
"Random", ID_Random,
|
||||
|
||||
"→Num", ID_ToDecimal,
|
||||
"→Q", ID_ToFraction,
|
||||
"→Qπ", ID_Unimplemented,
|
||||
"R#Seed", ID_RandomSeed,
|
||||
RandomGeneratorBits::label, ID_RandomGeneratorBits,
|
||||
RandomGeneratorOrder::label, ID_RandomGeneratorOrder,
|
||||
|
||||
"IsPrime", ID_Unimplemented,
|
||||
"NextPr", ID_Unimplemented,
|
||||
"PrevPr", ID_Unimplemented,
|
||||
|
||||
"R#Seed", ID_RandomSeed,
|
||||
RandomGeneratorBits::label, ID_RandomGeneratorBits,
|
||||
RandomGeneratorOrder::label, ID_RandomGeneratorOrder
|
||||
);
|
||||
"PrevPr", ID_Unimplemented);
|
||||
|
||||
|
||||
MENU(AnglesMenu,
|
||||
|
@ -783,8 +784,6 @@ MENU(SymbolicMenu,
|
|||
ID_Expand,
|
||||
ID_Simplify,
|
||||
"→Poly", ID_ToPolynomial,
|
||||
"→Num", ID_ToDecimal,
|
||||
"→Q", ID_ToFraction,
|
||||
|
||||
"Algbra", ID_AlgebraMenu,
|
||||
"Arith", ID_ArithmeticMenu,
|
||||
|
@ -808,8 +807,8 @@ MENU(AlgebraMenu,
|
|||
"↑Match", ID_MatchUp,
|
||||
"Isolate", ID_Unimplemented,
|
||||
"Apply", ID_Apply,
|
||||
"→Num", ID_ToDecimal,
|
||||
"→Q", ID_ToFraction,
|
||||
"Subst", ID_Subst,
|
||||
"|", ID_Where,
|
||||
|
||||
"∂", ID_Unimplemented,
|
||||
"∫", ID_Integrate,
|
||||
|
|
|
@ -36,7 +36,8 @@ enum precedence
|
|||
{
|
||||
NONE = 0, // No precedence
|
||||
LOWEST = 1, // Lowest precedence (when parsing parentheses)
|
||||
COMPLEX = 3, // Complex numbers
|
||||
WHERE = 3, // | (where) operator
|
||||
COMPLEX = 5, // Complex numbers
|
||||
|
||||
LOGICAL = 10, // and, or, xor
|
||||
RELATIONAL = 12, // <, >, =, etc
|
||||
|
|
41
src/tests.cc
41
src/tests.cc
|
@ -6423,6 +6423,46 @@ void tests::expand_collect_simplify()
|
|||
step("Apply function call for algebraic function with incorrect type")
|
||||
.test(CLEAR, "2 'F' APPLY", ENTER)
|
||||
.error("Bad argument type");
|
||||
|
||||
step("Substitution with simple polynomial")
|
||||
.test(CLEAR, "'X^2+3*X+7' 'X=Z+1' SUBST", ENTER)
|
||||
.expect("'(Z+1)↑2+3·(Z+1)+7'")
|
||||
.test("'Z=sin(A+B)' SUBST", ENTER)
|
||||
.expect("'(sin(A+B)+1)↑2+3·(sin(A+B)+1)+7'");
|
||||
step("Substitution with numerical value")
|
||||
.test(CLEAR, "42 'X=Z+1' SUBST", ENTER)
|
||||
.expect("42");
|
||||
step("Type error on value to substitute")
|
||||
.test(CLEAR, "\"ABC\" 'X=Z+1' SUBST", ENTER)
|
||||
.error("Bad argument type");
|
||||
step("Bad argument value for substitution")
|
||||
.test(CLEAR, "'X^2+3*X+7' 'Z-1=Z+1' SUBST", ENTER)
|
||||
.error("Bad argument value");
|
||||
|
||||
step("WHERE command with simple polynomial")
|
||||
.test(CLEAR, "'X^2+3*X+7' 'X=Z+1' WHERE", ENTER)
|
||||
.expect("'(Z+1)↑2+3·(Z+1)+7'")
|
||||
.test("{ 'Z=sin(A+B)' 'A=42' } WHERE", ENTER)
|
||||
.expect("'(sin(42+B)+1)↑2+3·(sin(42+B)+1)+7'");
|
||||
step("Substitution with numerical value")
|
||||
.test(CLEAR, "42 'X=Z+1' WHERE", ENTER)
|
||||
.expect("42");
|
||||
step("Type error on value to substitute in WHERE")
|
||||
.test(CLEAR, "\"ABC\" 'X=Z+1' WHERE", ENTER)
|
||||
.error("Bad argument type");
|
||||
step("Bad argument value for substitution in WHERE")
|
||||
.test(CLEAR, "'X^2+3*X+7' 'Z-1=Z+1' WHERE", ENTER)
|
||||
.error("Bad argument value");
|
||||
step("| operator")
|
||||
.test(CLEAR, "'X^2+3*X+7|X=Z+1'", ENTER)
|
||||
.expect("'X↑2+3·X+7|X=Z+1'")
|
||||
.test(RUNSTOP)
|
||||
.expect("'(Z+1)²+3·(Z+1)+7'");
|
||||
step("Chained | operator")
|
||||
.test("'X^2+3*X+7|X=Z+1|Z=sin(A+B)|A=42'", ENTER)
|
||||
.expect("'X↑2+3·X+7|X=Z+1|Z=sin(A+B)|A=42'")
|
||||
.test(RUNSTOP)
|
||||
.expect("'(sin(42+B)+1)²+3·(sin(42+B)+1)+7'");
|
||||
}
|
||||
|
||||
|
||||
|
@ -10275,6 +10315,7 @@ tests &tests::itest(cstring txt)
|
|||
case '@': k = KEY2; alpha = true; xshift = true; break;
|
||||
case '$': k = KEY3; alpha = true; xshift = true; break;
|
||||
case '#': k = KEY4; alpha = true; xshift = true; break;
|
||||
case '|': k = KEY6; alpha = true; xshift = true; break;
|
||||
case '\\': k = ADD; alpha = true; xshift = true; break;
|
||||
case '\n': k = BSP; alpha = true; xshift = true; break;
|
||||
case L'«': k = RUNSTOP; alpha = false; shift = true; del = true; break;
|
||||
|
|
Loading…
Reference in a new issue