Skip to content
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
55 changes: 51 additions & 4 deletions Rules/Languages/en/SharedRules/general.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,49 @@
---

# W3C definite-integral arguments are the integration domain or the two bounds, not the sign.
- name: definite-integral-intent
tag: definite-integral
match: "(count(*)=1 or count(*)=2) and not(contains(@data-intent-property, ':silent:'))"
replace:
- bookmark: "@id"
- t: "integral"
- test:
if: "count(*)=1"
then: [t: "over", x: "*[1]"]
else: [t: "from", x: "*[1]", t: "to", x: "*[2]"]

# A semantic reading also works for prime notation; do not assume a Leibniz fraction.
- name: derivative-intent
tag: derivative
match: "count(*)=3 and not(contains(@data-intent-property, ':silent:'))"
replace:
- bookmark: "@id"
- t: "the"
- test:
- if: "*[3][self::m:mn and .='1']"
then: [t: "derivative of"]
- else_if: "*[3][self::m:mn and string-length(.)>0 and translate(., '0123456789', '')='']"
then: [x: "ToOrdinal(*[3])", t: "derivative of"]
else: [t: "derivative of order", x: "*[3]", t: "of"]
- x: "*[1]"
- t: "with respect to"
- x: "*[2]"

- name: blank-intent
tag: blank
match: "count(*)=0"
replace: [bookmark: "@id", t: "blank"]

# Preserve operands while suppressing the default-silent operator, including a bare operator leaf.
- name: invisible-operator-intent
tag: [invisible-times, invisible-separator]
match: "not(@data-intent-property) or contains(@data-intent-property, ':silent:')"
replace:
- test:
if: "*"
then: [x: "*"]
else: [t: ""]

# number-sets are a little messy in that the base was converted to a number-set, so we have to match that (simple) case last
- name: pos-neg-number-sets
tag: number-sets
Expand Down Expand Up @@ -190,13 +234,16 @@
- t: "of" # phrase(the square root 'of' 25 equals 5)
- x: "*[2]"

# W3C supplies the period alone; inferred MathCAT intents also carry a non-repeating prefix.
- name: repeating-decimal
tag: repeating-decimal
match: "."
match: "(count(*)=1 or count(*)=2) and not(contains(@data-intent-property, ':silent:'))"
replace:
- x: "*[1]"
- t: "with repeating digits" # phrase('with repeating digits')
- spell: "*[2]"
- test:
if: "count(*)=2"
then: [x: "*[1]", t: "with repeating digits"]
else: [t: "repeating decimal"]
- spell: "*[last()]"

- name: msubsup-skip-super
# handles single, double, etc., prime
Expand Down
27 changes: 20 additions & 7 deletions Rules/Languages/en/definitions.yaml

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I don't understand why you added these to the IntentMappings. These are defined elsewhere. Many of these are listed and commented out. Probably they should have a comment above them that says "speech rules defined elsehwere" for the commented out ones. For the others, commented out versions with the 'defined-elsewhere' comment should be added.

Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,16 @@
"binomial": "infix=binomial; choose; end binomial",

### Functions and Inverses
# Interval intents (open-/closed-interval, etc.) are handled by rules due to special cases involving ∞.
# Speech rules for open-interval, open-closed-interval, closed-interval, and closed-open-interval
# are defined elsewhere: SharedRules/general.yaml (intervals).
"inverse":"function=inverse || postfix=inverse",

"domain": "function= ; domain",
"codomain": "function= ; codomain",

"image":"function=image",
#"fraction":"function=fraction; over; end fraction", # NOTE: Fails
# Speech rules defined elsewhere: ClearSpeak_Rules.yaml and SimpleSpeak_Rules.yaml.
# "fraction":"function=fraction; over; end fraction",
"mixed-fraction":"infix=and", # NOTE: in website says function, but follow infix speech pattern.
"quotient":"function=integer part: divided by",
"evaluated-at":"infix=evaluated at",
Expand Down Expand Up @@ -121,9 +123,11 @@


### Calculus
# "definite-integral":"function=integral over || function=integral from; to", # Property ???
# "derivative":"function=the derivative; with respect to", # Property ???
# "partial-derivative":"function=partial", ## Note, included with infix, but separately under calculus tab has ??? for functionality
# Speech rules defined elsewhere: SharedRules/general.yaml (definite-integral-intent, derivative-intent).
# "definite-integral":"function=integral over || function=integral from; to",
# "derivative":"function=the derivative; with respect to",
# The active prefix mapping for partial-derivative is in Other below.
# "partial-derivative":"function=partial",

"limit": "prefix=limit as|the limit as",
"tends-to":"infix=tends to",
Expand Down Expand Up @@ -228,9 +232,14 @@
"point":"prefix=point", ## NOTE: Has ??? for property in site. Should it be prefix? Or something else.

### Separators
# Speech rules defined elsewhere: SharedRules/general.yaml (invisible-operator-intent).
# "invisible-separator": "silent=",
# "invisible-times": "silent=",
"time-separator":"infix=",

### General Concepts
# Speech rules defined elsewhere: SharedRules/general.yaml (blank-intent).
# "blank": "nofix=blank",
"fenced-group":"function=fenced-group", # appears both under general concepts and grouping
# NOTE: in site mentions "the pair x and y", due to being defined as function, needs the "of" keyword
"ordered-pair": "function=the pair: and", # Needs to be tested
Expand All @@ -251,7 +260,8 @@
### Grouping
"annotation":"infix= ; which is ;", # NOTE: Follows the same order as indexed-by, in site listed as function. Should it be infix?
"braced-group":"function=grouped; end grouped", # NOTE: not tested, site is missing "of" keyword function introduces
#"repeating-decimal":"function=repeating decimal", # NOTE: BREAKS TEST. Check again. Site is missing "of" keyword function introduces.
# Speech rules defined elsewhere: SharedRules/general.yaml (repeating-decimal).
# "repeating-decimal":"function=repeating decimal",


### Other
Expand Down Expand Up @@ -362,11 +372,14 @@
"modified-variable": "silent= ",
"say-super": "infix=super| superscript| superscript", # used with 'mo' for superscripts (e.g, "<")
"skip-super": "silent=", # used with 'mo' for superscripts (e.g, "*")
# Speech rules defined elsewhere: SharedRules/general.yaml (bigop-both, bigop-under).
# "large-op": "infix=over || other=from,to",
"lim-sup": "prefix=lim sup as| the limit superior as| the limit superior as",
"lim-inf": "prefix=lim inf as| the limit inferior as| the limit inferior as",
"logarithm-with-base": "prefix=log base| the log base| the log base",
# "pochhammer": "infix=permutations of", # arguments are in reverse order, so can't work here
# Infix speech rules defined elsewhere: SharedRules/general.yaml (permutation) reverses the arguments.
# The active function mapping above handles the other fixity.
# "pochhammer": "infix=permutations of",
"trace": "function= ; trace| trace| the trace; end trace",
"dimension": "function=; dimension| dimension| the dimension; end dimension",
"homomorphism": "function= ; homomorphism| homomorphism| the homomorphism; end homomorphism",
Expand Down
2 changes: 1 addition & 1 deletion Rules/definitions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"arccot", "arccotan", "arccotanh", "arccoth",
"arccsc", "arccsch", "arcsec", "arcsech", "arcsin", "arcsinh",
"arctan", "arctang", "arctanh",
"exp", "Exp", "expt", "arg", "im", "Im", "ℑ", re", "Re", "ℜ", "Pr", "cis",
"exp", "Exp", "expt", "arg", "im", "Im", "ℑ", "re", "Re", "ℜ", "Pr", "cis",
"gcd", "lcm",
"min", "max", "deg", "det", "rank", "rg", "sgn",
"dim", "hom", "ker", "tr", "rot", "div",
Expand Down
27 changes: 27 additions & 0 deletions src/canonicalize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1254,6 +1254,11 @@ impl CanonicalizeContext {
if element_name == "mrow" && children.len() == 1 && CanonicalizeContext::is_ok_to_merge_mrow_child(mathml) {
// "lift" the child up so all the links (e.g., siblings) are correct
let child = as_element(children[0]);
// The synthesized wrapper can disappear, but its original token must
// not inherit a marker that chemistry cleanup uses to delete added leaves.
if is_leaf(child) && mathml.attribute_value(CHANGED_ATTR).as_deref() == Some(ADDED_ATTR_VALUE) {
mathml.remove_attribute(CHANGED_ATTR);
}
mathml.replace_children(child.children());
set_mathml_name(mathml, as_str!(name(child)));
add_attrs(mathml, &child.attributes());
Expand Down Expand Up @@ -4719,6 +4724,28 @@ mod canonicalize_tests {
are_strs_canonically_equal_result(target_str, target_str, &[])
}

#[test]
fn mstyle_split_function_preserves_content() -> Result<()> {
// A style wrapper must preserve a split function name when its mi children merge.
crate::interface::set_rules_dir(abs_rules_dir_path())?;
crate::interface::set_preference("Language", "ru")?;
let canonical = crate::interface::set_mathml("<math><mi>a</mi><mo>=</mo><mstyle mathvariant='normal'><mi>R</mi><mi>e</mi></mstyle><mspace width='0.28em'/><mi>z</mi><mo>,</mo><mi>b</mi><mo>=</mo><mstyle mathvariant='normal'><mi>I</mi><mi>m</mi></mstyle><mspace width='0.28em'/><mi>z</mi></math>")?;
assert!(canonical.contains(">Re</mi>"), "{canonical}");
assert!(canonical.contains(">Im</mi>"), "{canonical}");
Ok(())
}

#[test]
fn mpadded_split_function_preserves_content() -> Result<()> {
// mpadded follows the same wrapper cleanup path; chemistry rollback must keep both names.
crate::interface::set_rules_dir(abs_rules_dir_path())?;
crate::interface::set_preference("Language", "ru")?;
let canonical = crate::interface::set_mathml("<math><mi>a</mi><mo>=</mo><mpadded><mi>R</mi><mi>e</mi></mpadded><mi>z</mi><mo>,</mo><mi>b</mi><mo>=</mo><mpadded><mi>I</mi><mi>m</mi></mpadded><mi>z</mi></math>")?;
assert!(canonical.contains(">Re</mi>"), "{canonical}");
assert!(canonical.contains(">Im</mi>"), "{canonical}");
Ok(())
}

#[test]
fn plane1_common() -> Result<()> {
let test_str = "<math>
Expand Down
2 changes: 2 additions & 0 deletions tests/Languages/en/intent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,5 @@ fn intent_prob_x() -> Result<()> {
return Ok(());

}

mod w3c;
148 changes: 148 additions & 0 deletions tests/Languages/en/intent/w3c.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
use crate::common::*;
use anyhow::Result;

// Check this language's W3C speech in both styles and at every verbosity.
fn test_w3c_speech(mathml: &str, expected: &str) -> Result<()> {
for style in ["ClearSpeak", "SimpleSpeak"] {
for verbosity in ["Terse", "Medium", "Verbose"] {
test_prefs("en", style, vec![("Verbosity", verbosity), ("IntentErrorRecovery", "Error")], mathml, expected)?;
}
}
Ok(())
}

#[test]
fn w3c_repeating_decimal_one_argument() -> Result<()> {
// W3C supplies the period alone; a missing second child must never cause an XPath error.
test_w3c_speech("<math><mover intent='repeating-decimal($repeat)'><mn arg='repeat'>45</mn><mo>_</mo></mover></math>", "repeating decimal 4 5")?;
test_w3c_speech("<math><mrow intent='repeating-decimal(45)'/></math>", "repeating decimal 4 5")
}

#[test]
fn w3c_repeating_decimal_leading_zeroes() -> Result<()> {
// A period is a digit sequence: 003 must retain both leading zeroes rather than become 3.
test_w3c_speech("<math><mover intent='repeating-decimal:function($repeat)'><mn arg='repeat'>003</mn><mo>¯</mo></mover></math>", "repeating decimal 0 0 3")
}

#[test]
fn w3c_repeating_decimal_legacy_arguments() -> Result<()> {
// Keep the existing prefix/period extension used by automatic inference.
test_w3c_speech("<math><mrow intent='repeating-decimal($prefix,$repeat)'><mn arg='prefix'>12</mn><mn arg='repeat'>003</mn></mrow></math>", "12 with repeating digits 0 0 3")?;
test_w3c_speech("<math><mn>12</mn><mover><mn>45</mn><mo>¯</mo></mover></math>", "12 with repeating digits 4 5")
}

#[test]
fn w3c_repeating_decimal_complete_w3c_example() -> Result<()> {
// The W3C surrounding decimal must retain its prefix as well as both period digits.
let mathml="<math><mn>0.0</mn><mover intent='repeating-decimal($repeat)'><mn arg='repeat'>45</mn><mo>_</mo></mover></math>";
let (language,expected) = ("en","0.0 with repeating digits 4 5");
for style in ["ClearSpeak","SimpleSpeak"] {
for verbosity in ["Terse","Medium","Verbose"] {
test_prefs(language,style,vec![("Verbosity",verbosity),("IntentErrorRecovery","Error"),("DecimalSeparators","."),("BlockSeparators",", ")],mathml,expected)?;
}
}
Ok(())
}

#[test]
fn w3c_repeating_decimal_semantic_navigation() -> Result<()> {
// Semantic reading must use the same one-argument rule when revisiting the current expression.
let (language,expected) = ("en","repeating decimal 4 5");
for style in ["ClearSpeak","SimpleSpeak"] {
init_nav(language,"<math><mrow intent='repeating-decimal(45)'/></math>")?;
set_preference("SpeechStyle",style)?;
let speech=do_navigate_command("ReadCurrent")?;
assert!(speech.contains(expected),"{language}/{style}: {speech}");
}
Ok(())
}

#[test]
fn w3c_blank_intent_representations() -> Result<()> {
// The semantic blank is independent of its visible spelling, including a space-only blank.
test_w3c_speech("<math><mi intent='blank'>__</mi></math>", "blank")?;
test_w3c_speech("<math><mo intent='blank'>?</mo></math>", "blank")?;
test_w3c_speech("<math><mspace intent='blank' width='3em'/></math>", "blank")
}

#[test]
fn w3c_invisible_times_keeps_operands() -> Result<()> {
// W3C assigns silent fixity: suppress the operator name, never its operands.
test_w3c_speech("<math><mrow intent='invisible-times($x,$y)'><mi arg='x'>x</mi><mi arg='y'>y</mi></mrow></math>", "x y")
}

#[test]
fn w3c_invisible_separator_keeps_operands() -> Result<()> {
// A silent separator must not add the word 'comma' or drop any listed operand.
test_w3c_speech("<math><mrow intent='invisible-separator($x,$y,$z)'><mi arg='x'>x</mi><mi arg='y'>y</mi><mi arg='z'>z</mi></mrow></math>", "x y z")
}

#[test]
fn w3c_invisible_operator_leaves_are_silent() -> Result<()> {
// Explicit annotations on the Unicode operator itself have no spoken name or argument.
test_w3c_speech("<math><mo intent='invisible-times'>&#x2062;</mo></math>", "")?;
test_w3c_speech("<math><mo intent='invisible-separator'>&#x2063;</mo></math>", "")
}

#[test]
fn w3c_definite_integral_domain() -> Result<()> {
// The single W3C argument is an integration domain, not an integrand or integral sign.
test_w3c_speech("<math><msub intent='definite-integral($d)'><mo>∫</mo><mi arg='d'>x</mi></msub></math>", "integral over x")
}

#[test]
fn w3c_definite_integral_bounds() -> Result<()> {
// Two W3C arguments are lower and upper bounds, in that order; neither may disappear.
test_w3c_speech("<math><msubsup intent='definite-integral($lo,$hi)'><mo>∫</mo><mn arg='lo'>0</mn><mn arg='hi'>1</mn></msubsup></math>", "integral from 0 to 1")
}

#[test]
fn w3c_definite_integral_infinite_bounds() -> Result<()> {
// Infinite bounds must retain both signs and endpoint values.
let mathml="<math><msubsup intent='definite-integral($lo,$hi)'><mo>∫</mo><mrow arg='lo'><mo>−</mo><mi>∞</mi></mrow><mi arg='hi'>∞</mi></msubsup></math>";
test_w3c_speech(mathml, "integral from negative infinity to infinity")
}

#[test]
fn w3c_derivative_leibniz_second_order() -> Result<()> {
// The three arguments denote function, variable, and order, regardless of fraction layout.
let mathml = "<math><mfrac intent='derivative($f,$x,$n)'><mrow><msup><mi>d</mi><mn arg='n'>2</mn></msup><mi arg='f'>y</mi></mrow><mrow><mi>d</mi><msup><mi arg='x'>x</mi><mn>2</mn></msup></mrow></mfrac></math>";
test_w3c_speech(mathml, "the second derivative of y with respect to x")
}

#[test]
fn w3c_derivative_prime_first_order() -> Result<()> {
// Prime notation needs the same semantic meaning, without assuming a spoken Leibniz fraction.
let mathml = "<math><mrow intent='derivative($f,$x,1)'><msup><mi arg='f'>f</mi><mo>′</mo></msup><mo>(</mo><mi arg='x'>x</mi><mo>)</mo></mrow></math>";
test_w3c_speech(mathml, "the derivative of f with respect to x")
}

#[test]
fn w3c_derivative_symbolic_order() -> Result<()> {
// Symbolic orders need an order phrase, not a numeric-ordinal conversion of the letter n.
let mathml="<math><mrow intent='derivative($f,$x,$n)'><mi arg='f'>f</mi><mi arg='x'>x</mi><mi arg='n'>n</mi></mrow></math>";
test_w3c_speech(mathml, "the derivative of order n of f with respect to x")
}

#[test]
fn w3c_calculus_intent_read_current() -> Result<()> {
// Semantic navigation must use the same integral and derivative readings as full speech.
let (language, integral, derivative) = ("en", "integral from 0 to 1", "the derivative of f with respect to x");
for style in ["ClearSpeak", "SimpleSpeak"] {
init_nav(language, "<math><msubsup intent='definite-integral(0,1)'><mo>∫</mo><mn>0</mn><mn>1</mn></msubsup></math>")?;
set_preference("SpeechStyle", style)?;
let speech=do_navigate_command("ReadCurrent")?.split_whitespace().collect::<Vec<_>>().join(" ");
assert!(speech.contains(integral), "{language}/{style}: {speech}");
init_nav(language, "<math><msup intent='derivative($f,x,1)'><mi arg='f'>f</mi><mo>′</mo></msup></math>")?;
set_preference("SpeechStyle", style)?;
let speech=do_navigate_command("ReadCurrent")?.split_whitespace().collect::<Vec<_>>().join(" ");
assert!(speech.contains(derivative), "{language}/{style}: {speech}");
}
Ok(())
}

#[test]
fn w3c_binomial_coefficient() -> Result<()> {
// The W3C alias reuses binomial speech without changing inferred semantics.
test_w3c_speech("<math><mrow intent='binomial-coefficient($n,$k)'><mn arg='n'>7</mn><mn arg='k'>3</mn></mrow></math>", "7 choose 3")
}
2 changes: 1 addition & 1 deletion tests/Languages/intent.rs

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I don't like test_core_speech() because it is just ru and en. Once it is more than just en (which probably should only be used to make sure something that is language independent but currently is not that disiplined), this should be split so that it is in each language's directory.

For tests in the intent directory, the tests should not be testing the language speech, they should be testing what the mathml turned into in the intent tree. The speech for that should go into Languages/lang/intent.rs.

Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
mod general;
mod calculus;
mod linear_algebra;
mod tables;
mod tables;
Loading
Loading