diff --git a/expreduce/builtin_pattern.go b/expreduce/builtin_pattern.go index 4cd790a..f210da7 100644 --- a/expreduce/builtin_pattern.go +++ b/expreduce/builtin_pattern.go @@ -577,5 +577,19 @@ func GetPatternDefinitions() (defs []Definition) { &SameTest{"True", "MatchQ[x^x, (x^x)^Optional[exp_]]"}, }, }) + defs = append(defs, Definition{ + Name: "Verbatim", + Usage: "`Verbatim[expr]` matches `expr` exactly, even if it has patterns.", + // Not fully supported. Don't document + OmitDocumentation: true, + Tests: []TestInstruction{ + &SameTest{"{{a,b},{b,c},{c,d}}", "ReplaceList[a+b+c+d,Verbatim[Plus][___,x_,y_,___]->{x,y}]"}, + &SameTest{"{}", "ReplaceList[a+b+c+d,Verbatim[Times][___,x_,y_,___]->{x,y}]"}, + &SameTest{"{}", "ReplaceList[a+b*c+d,Verbatim[Times][___,x_,y_,___]->{x,y}]"}, + &SameTest{"{{a,b},{b,c},{c,d}}", "ReplaceList[foo[a,b,c,d],Verbatim[foo][___,x_,y_,___]->{x,y}]"}, + &SameTest{"{{a,b},{b,c},{c,d}}", "ReplaceList[(a+b)[a,b,c,d],Verbatim[a+b][___,x_,y_,___]->{x,y}]"}, + &SameTest{"{}", "ReplaceList[(a+b)[a,b,c,d],Verbatim[a+_][___,x_,y_,___]->{x,y}]"}, + }, + }) return } diff --git a/expreduce/ex_expression.go b/expreduce/ex_expression.go index b79d161..f4aebcd 100644 --- a/expreduce/ex_expression.go +++ b/expreduce/ex_expression.go @@ -40,6 +40,22 @@ func headExAssertion(ex Ex, head Ex, cl *CASLogger) (*Expression, bool) { return NewEmptyExpression(), false } +func OperatorAssertion(ex Ex, opHead string) (*Expression, *Expression, bool) { + expr, isExpr := ex.(*Expression) + if isExpr { + headExpr, headIsExpr := expr.Parts[0].(*Expression) + if headIsExpr { + sym, isSym := headExpr.Parts[0].(*Symbol) + if isSym { + if sym.Name == opHead { + return expr, headExpr, true + } + } + } + } + return NewEmptyExpression(), NewEmptyExpression(), false +} + func tryReturnValue(e Ex) (Ex, bool) { asReturn, isReturn := HeadAssertion(e, "Return") if !isReturn { diff --git a/expreduce/matchq.go b/expreduce/matchq.go index be4f9b4..1c905e7 100644 --- a/expreduce/matchq.go +++ b/expreduce/matchq.go @@ -116,6 +116,19 @@ func NewMatchIter(a Ex, b Ex, pm *PDManager, es *EvalState) (matchIter, bool) { aExpression, aIsExpression := a.(*Expression) bExpression, bIsExpression := b.(*Expression) + // Special case for the operator form of Verbatim + forceOrdered := false + verbatimOp, opExpr, isVerbatimOp := OperatorAssertion(b, "Verbatim") + if aIsExpression && isVerbatimOp { + if len(opExpr.Parts) == 2 { + if IsSameQ(aExpression.Parts[0], opExpr.Parts[1], &es.CASLogger) { + b = NewExpression(append([]Ex{opExpr.Parts[1]}, verbatimOp.Parts[1:]...)) + bExpression, bIsExpression = b.(*Expression) + forceOrdered = true + } + } + } + // This initial value is just a randomly chosen placeholder // TODO, convert headStr to symbol type, have Ex implement getHead() Symbol headStr := "Unknown" @@ -211,7 +224,9 @@ func NewMatchIter(a Ex, b Ex, pm *PDManager, es *EvalState) (matchIter, bool) { } } - nomi, ok := NewSequenceMatchIter(aExpression.Parts[startI:], bExpression.Parts[startI:], attrs.Orderless, attrs.Flat, sequenceHead, pm, es) + isOrderless := attrs.Orderless && !forceOrdered + isFlat := attrs.Flat && !forceOrdered + nomi, ok := NewSequenceMatchIter(aExpression.Parts[startI:], bExpression.Parts[startI:], isOrderless, isFlat, sequenceHead, pm, es) if !ok { return &dummyMatchIter{false, pm, true}, true }