Skip to content

Commit 05863bc

Browse files
committed
Improvements to navigation.
Handle intent literals and others by giving them 'id's that are based off other id's. Previously, 'id's were reused and that breaks navigation.
1 parent 839ac33 commit 05863bc

File tree

5 files changed

+223
-66
lines changed

5 files changed

+223
-66
lines changed

Rules/Intent/general.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@
309309
children:
310310
- intent:
311311
name: "logarithm-with-base"
312-
attrs: "data-intent-property='concat(data-intent-property, \":prefix:\")'"
312+
attrs: "data-intent-property='concat(data-intent-property, \":prefix:\")', id='concat(@id, \"-log-base\")'"
313313
children: [x: "*[2]"] # grab the base
314314
- x: "*[3]" # grab the exponent
315315

@@ -691,7 +691,7 @@
691691
children:
692692
- intent:
693693
name: "indexed-by"
694-
attrs: "DEBUG(data-intent-property='concat(data-intent-property, \":infix:\")', data-id-offset='1')"
694+
attrs: "data-intent-property='concat(data-intent-property, \":infix:\")', id='concat(@id, \"-indexed-by\")'"
695695
children:
696696
- x: "*[1]"
697697
- x: "*[2]"
@@ -833,7 +833,7 @@
833833
children:
834834
- intent:
835835
name: "mrow"
836-
attrs: "data-id-offset='2'"
836+
attrs: "id='concat(@id, \"-mrow\")'"
837837
children: [x: "*[not(contains(@intent, ':equation-label'))]"]
838838

839839
-

Rules/Languages/en/definitions.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
"root": "root; root index",
6868
"msub": "base; subscript",
6969
"sub": "base; subscript",
70+
"logarithm-with-base": "base",
7071
"indexed-by": "base; subscript",
7172
"msup": "base; superscript",
7273
"say-super": "base; superscript",

Rules/Languages/en/navigate.yaml

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -223,10 +223,9 @@
223223
if: "$ReadZoomLevel !=-1"
224224
then:
225225
- set_variables: [ReadZoomLevel: "0"]
226-
- test:
227-
if: "string-length(.) > 1 and $MatchCounter = 1 and $NavNodeOffset = 0"
228-
then: [set_variables: [NavNodeOffset: "1"]]
229-
- set_variables: [NavNode: "@id"]
226+
- set_variables:
227+
- NavNode: "@id"
228+
- NavNodeOffset: "IfThenElse(string-length(.) > 1 and $MatchCounter = 1 and $NavNodeOffset = 0, '1', '0')"
230229

231230
# special case of zooming into a table -- move to the first row (if only one row, first column)
232231
- name: zoom-in-table
@@ -236,12 +235,7 @@
236235
- with:
237236
variables: [SayCommand: "string($NavVerbosity = 'Verbose')"]
238237
replace: [x: "."]
239-
- test:
240-
if: "count(*)=1"
241-
then:
242-
- set_variables: [NavNode: "*[1]/*[1]/@id"]
243-
else:
244-
- set_variables: [NavNode: "*[1]/@id"]
238+
- set_variables: [NavNode: "IfThenElse(count(*)=1, (*[1]/*[1]/@id), (*[1]/@id))"]
245239

246240
- name: zoom-in-mrow-in-math
247241
# zooming in only once is meaningless because 'math' has only a single child and it was spoken at the math level -- dig inside and do it again
@@ -255,7 +249,6 @@
255249

256250
- # For msqrt and menclose, if the single child isn't an mrow, don't zoom in
257251
name: zoom-in-again
258-
# If there is only one child, this isn't an interesting move -- zoom in again
259252
tag: "*"
260253
match:
261254
- "($NavCommand = 'ZoomIn' or "
@@ -271,7 +264,7 @@
271264
if: "not(*[1][self::m:mrow or @data-from-mathml='mrow'])"
272265
then:
273266
- with:
274-
variables: [Move2D: "'in'", Child2D: "IfThenElse(count(*)=0, $Move2D, $Move2D)"] # phrase('in' the denominator)
267+
variables: [Move2D: "'in'", Child2D: "IfThenElse($NavCommand = 'MovePreviousZoom', (*[last()]), (*[1]))"] # phrase('in' the denominator)
275268
replace: [x: "IfThenElse($NavCommand = 'MovePreviousZoom', 1, $Child2D)"]
276269
- test:
277270
if: "$NavCommand = 'MovePreviousZoom'"
@@ -303,7 +296,9 @@
303296
variables: [Move2D: "'in'", Child2D: "*[1]"] # phrase('in' the denominator)
304297
replace: [x: "."]
305298
# "(...)" to get around some weird parse bug"
306-
- set_variables: [NavNode: "*[1]/@id", NavNodeOffset: "IfThenElse(*[1]/@data-id-offset, (*[1]/@data-id-offset), '0')"]
299+
- set_variables:
300+
- NavNode: "*[1]/@id"
301+
- NavNodeOffset: "IfThenElse(*[1]/@data-id-offset, (*[1]/@data-id-offset), '0')"
307302

308303

309304
- name: zoom-in-simple
@@ -314,8 +309,11 @@
314309
variables: [SayCommand: "string($NavVerbosity = 'Verbose')"]
315310
replace: [x: "."]
316311
- test:
317-
if: "$MatchCounter > 1 and IsNode(., '2D') "
318-
then: [set_variables: [NavNode: "@id"]] # time to stop, not going "in" to next thing, so before "Move2D"
312+
if: "$MatchCounter > 1 and IsNode(., '2D')"
313+
then:
314+
- set_variables: # time to stop, not going "in" to next thing, so before "Move2D"
315+
- NavNode: "@id"
316+
- NavNodeOffset: "IfThenElse(@data-id-offset, @data-id-offset, '0')"
319317
else:
320318
- with:
321319
variables: [Move2D: "'in'", Child2D: "*[1]"] # phrase('in' the denominator)
@@ -418,7 +416,10 @@
418416
- test:
419417
- if: "count(*)> 1 or IsNode(., 'leaf') or
420418
@data-from-mathml='msqrt' or self::m:msqrt or @data-from-mathml='menclose' or self::m:menclose"
421-
then: [set_variables: [NavNode: "@id"]]
419+
then:
420+
- set_variables:
421+
- NavNode: "@id"
422+
- NavNodeOffset: "IfThenElse(@data-id-offset, @data-id-offset, '0')"
422423
else: [x: "*[1]"]
423424

424425
- name: move-next-zoom-not-enhanced
@@ -433,7 +434,9 @@
433434
# - with:
434435
# variables: [Move2D: "'in'", Child2D: "following-sibling::*[1]"] # phrase('in' the denominator)
435436
# replace: [x: ".."]
436-
- set_variables: [NavNode: "@id"]
437+
- set_variables:
438+
- NavNode: "@id"
439+
- NavNodeOffset: "IfThenElse(@data-id-offset, @data-id-offset, '0')"
437440
else:
438441
- with:
439442
variables: [Move2D: "'in'", Child2D: "*[1]"] # phrase('in' the denominator)
@@ -452,7 +455,9 @@
452455
# - with:
453456
# variables: [Move2D: "'in'", Child2D: "preceding-sibling::*[1]"] # phrase('in' the denominator)
454457
# replace: [x: ".."]
455-
- set_variables: [NavNode: "@id"]
458+
- set_variables:
459+
- NavNode: "@id"
460+
- NavNodeOffset: "IfThenElse(@data-id-offset, @data-id-offset, '0')"
456461
else:
457462
- with:
458463
variables: [Move2D: "'in'", Child2D: "*[last()]"] # phrase('in' the denominator)
@@ -461,8 +466,6 @@
461466

462467
# ********* ZoomOut ***************
463468
- name: zoom-out-default
464-
465-
466469
tag: mtd
467470
match: "$Move2D = '' and ($NavCommand = 'ZoomOut')"
468471
replace:
@@ -474,7 +477,9 @@
474477
# # if we let the speech rules speak the row, it is given just the MathML for the row, so the row # will always be '1'
475478
# - x: "count(../preceding-sibling::*)+1"
476479
# - pause: medium
477-
- set_variables: [NavNode: "../@id"]
480+
- set_variables:
481+
- NavNode: "../@id"
482+
- NavNodeOffset: "IfThenElse(../@data-id-offset, ../@data-id-offset, '0')"
478483

479484
- name: zoom-out
480485
# a row around a single element -- these might duplicate the position/offset, so we jump an extra level here
@@ -485,24 +490,26 @@
485490
variables: [SayCommand: "string($NavVerbosity = 'Verbose')"]
486491
replace: [x: "."]
487492
- test:
488-
- if: "$NavNodeOffset > 0" # Inside leaf -- just reset offset
493+
- if: "$NavNodeOffset > 0 and IsNode(., 'leaf')" # Inside leaf -- just reset offset, intent offset doesn't change
489494
then:
490495
- set_variables: [NavNodeOffset: "0"]
491496
# NavNode remains the same
492497
- else_if: "$NavMode='Enhanced' and parent::*[self::m:mrow and (IsBracketed(., '(', ')', false) or IsBracketed(., '[', ']', false))]"
493498
then: [x: ".."] # auto-zoom: move out a level and retry
494499
else:
495500
- with:
496-
variables: [Move2D: "'out of'", Child2D: "."]
501+
variables: [Move2D: "'out of'", Child2D: "."] # phrase('out of' the denominator)
497502
replace: [x: ".."]
498503
- test:
499504
if: "parent::m:mtd"
500505
then: [x: ".."]
501506
else:
502507
- test:
503-
if: "$ReadZoomLevel!=-1"
508+
if: "DEBUG($ReadZoomLevel)!=-1"
504509
then: [set_variables: [ReadZoomLevel: "DistanceFromLeaf(.., true, $NavMode!='Character')"]]
505-
- set_variables: [NavNode: "../@id"]
510+
- set_variables:
511+
- NavNode: "../@id"
512+
- NavNodeOffset: "IfThenElse(../@data-id-offset, ../@data-id-offset, '0')"
506513

507514
# ********* MoveStart/End ***************
508515
- name: math-move-to-start-or-end

src/infer_intent.rs

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use regex::Regex;
1717
use phf::phf_set;
1818

1919
const IMPLICIT_FUNCTION_NAME: &str = "apply-function";
20+
2021
pub fn infer_intent<'r, 'c, 's:'c, 'm:'c>(rules_with_context: &'r mut SpeechRulesWithContext<'c,'s,'m>, mathml: Element<'c>) -> Result<Element<'m>> {
2122
match catch_errors_building_intent(rules_with_context, mathml) {
2223
Ok(intent) => return Ok(intent),
@@ -47,7 +48,8 @@ pub fn infer_intent<'r, 'c, 's:'c, 'm:'c>(rules_with_context: &'r mut SpeechRule
4748
if let Some(intent_str) = mathml.attribute_value(INTENT_ATTR) {
4849
// debug!("Before intent: {}", crate::pretty_print::mml_to_string(mathml));
4950
let mut lex_state = LexState::init(intent_str.trim())?;
50-
let result = build_intent(rules_with_context, &mut lex_state, mathml)
51+
let mut intent_offset = 0;
52+
let result = build_intent(rules_with_context, &mut lex_state, mathml, &mut intent_offset)
5153
.with_context(|| format!("occurs before '{}' in intent attribute value '{}'", lex_state.remaining_str, intent_str))?;
5254
if lex_state.token != Token::None {
5355
bail!("Error in intent value: extra unparsed intent '{}' in intent attribute value '{}'", lex_state.remaining_str, intent_str);
@@ -183,7 +185,7 @@ pub fn add_fixity_children(intent: Element) -> Element {
183185
fn create_operator_element<'a>(intent_name: &str, fixity: &str, id: &str, id_inc: usize, doc: &Document<'a>) -> ChildOfElement<'a> {
184186
let intent_name = intent_speech_for_name(intent_name, &PreferenceManager::get().borrow().pref_to_string("NavMode"), fixity);
185187
let element = create_mathml_element(doc, &intent_name);
186-
element.set_attribute_value("id", &format!("{id}-{id_inc}"));
188+
element.set_attribute_value("id", &format!("{id}-fixity-{id_inc}"));
187189
element.set_attribute_value(MATHML_FROM_NAME_ATTR, "mo");
188190
return ChildOfElement::Element(element);
189191
}
@@ -368,7 +370,8 @@ impl<'i> LexState<'i> {
368370

369371
fn build_intent<'b, 'r, 'c, 's:'c, 'm:'c>(rules_with_context: &'r mut SpeechRulesWithContext<'c,'s,'m>,
370372
lex_state: &mut LexState<'b>,
371-
mathml: Element<'c>) -> Result<Element<'m>> {
373+
mathml: Element<'c>,
374+
intent_offset: &mut u32) -> Result<Element<'m>> {
372375
// intent := self-property-list | expression
373376
// self-property-list := property+ S
374377
// expression := S ( term property* | application ) S
@@ -384,7 +387,7 @@ fn build_intent<'b, 'r, 'c, 's:'c, 'm:'c>(rules_with_context: &'r mut SpeechRule
384387
// debug!(" start build_intent: state: {}", lex_state);
385388
let doc = rules_with_context.get_document();
386389
let mut intent;
387-
// debug!(" build_intent: start mathml name={}", name(mathml));
390+
debug!(" build_intent: start mathml name={}, intent_offset={}", name(mathml), intent_offset);
388391
match lex_state.token {
389392
Token::Property(_) => {
390393
// We only have a property -- we want to keep this tag/element
@@ -419,7 +422,8 @@ fn build_intent<'b, 'r, 'c, 's:'c, 'm:'c>(rules_with_context: &'r mut SpeechRule
419422
if word == mathml.attribute_value(INTENT_ATTR).unwrap_or_default() {name(mathml)} else {leaf_name});
420423
intent.set_text(word); // '-' and '_' get removed by the rules.
421424
if let Some(id) = mathml.attribute_value("id") {
422-
intent.set_attribute_value("id", id);
425+
intent.set_attribute_value("id", &format!("{}-literal-{}", id, intent_offset));
426+
*intent_offset += 1;
423427
}
424428
lex_state.get_next()?;
425429
if let Token::Property(_) = lex_state.token {
@@ -428,7 +432,7 @@ fn build_intent<'b, 'r, 'c, 's:'c, 'm:'c>(rules_with_context: &'r mut SpeechRule
428432
}
429433
},
430434
Token::ArgRef(word) => {
431-
intent = match find_arg(rules_with_context, &word[1..], mathml, true, false)? {
435+
intent = match find_arg(rules_with_context, &word[1..], mathml, intent_offset, true, false)? {
432436
Some(e) => {
433437
lex_state.get_next()?;
434438
e
@@ -443,7 +447,7 @@ fn build_intent<'b, 'r, 'c, 's:'c, 'm:'c>(rules_with_context: &'r mut SpeechRule
443447
_ => bail!("Illegal 'intent' syntax: found {}", lex_state.token),
444448
};
445449
if lex_state.is_terminal("(") {
446-
intent = build_function(intent, rules_with_context, lex_state, mathml)?;
450+
intent = build_function(intent, rules_with_context, lex_state, mathml, intent_offset)?;
447451
}
448452
// debug!(" end build_intent: state: {} piece: {}", lex_state, mml_to_string(intent));
449453
add_fixity(intent);
@@ -482,7 +486,8 @@ fn build_function<'b, 'r, 'c, 's:'c, 'm:'c>(
482486
function_name: Element<'m>,
483487
rules_with_context: &'r mut SpeechRulesWithContext<'c,'s,'m>,
484488
lex_state: &mut LexState<'b>,
485-
mathml: Element<'c>) -> Result<Element<'m>> {
489+
mathml: Element<'c>,
490+
intent_offset: &mut u32) -> Result<Element<'m>> {
486491
// debug!(" start build_function: name: {}, state: {}", name(function_name), lex_state);
487492
// application := intent '(' arguments? S ')' where 'function_name' is 'intent'
488493
assert!(lex_state.is_terminal("("));
@@ -494,7 +499,7 @@ fn build_function<'b, 'r, 'c, 's:'c, 'm:'c>(
494499
// grammar requires at least one argument
495500
bail!("Illegal 'intent' syntax: missing argument for intent name '{}'", name(function_name));
496501
}
497-
let children = build_arguments(rules_with_context, lex_state, mathml)?;
502+
let children = build_arguments(rules_with_context, lex_state, mathml, intent_offset)?;
498503
function = lift_function_name(rules_with_context.get_document(), function, children);
499504

500505
if !lex_state.is_terminal(")") {
@@ -514,18 +519,19 @@ fn build_function<'b, 'r, 'c, 's:'c, 'm:'c>(
514519
fn build_arguments<'b, 'r, 'c, 's:'c, 'm:'c>(
515520
rules_with_context: &'r mut SpeechRulesWithContext<'c,'s,'m>,
516521
lex_state: &mut LexState<'b>,
517-
mathml: Element<'c>) -> Result<Vec<Element<'m>>> {
522+
mathml: Element<'c>,
523+
intent_offset: &mut u32) -> Result<Vec<Element<'m>>> {
518524
// arguments := intent ( ',' intent )*'
519525
// debug!(" start build_args state: {}", lex_state);
520526

521527
// there is at least one arg
522528
let mut children = Vec::with_capacity(lex_state.remaining_str.len()/3 + 1); // conservative estimate ('3' - "$x,");
523-
children.push( build_intent(rules_with_context, lex_state, mathml)? ); // arg before ','
529+
children.push( build_intent(rules_with_context, lex_state, mathml, intent_offset)? ); // arg before ','
524530
// debug!(" build_args: # children {}; state: {}", children.len(), lex_state);
525531

526532
while lex_state.is_terminal(",") {
527533
lex_state.get_next()?;
528-
children.push( build_intent(rules_with_context, lex_state, mathml)? ); // arg before ','
534+
children.push( build_intent(rules_with_context, lex_state, mathml, intent_offset)? ); // arg before ','
529535
// debug!(" build_args, # children {}; state: {}", children.len(), lex_state);
530536
}
531537

@@ -566,7 +572,13 @@ fn lift_function_name<'m>(doc: Document<'m>, function_name: Element<'m>, childre
566572

567573
/// look for @arg=name in mathml
568574
/// if 'check_intent', then look at an @intent for this element (typically false for non-recursive calls)
569-
fn find_arg<'r, 'c, 's:'c, 'm:'c>(rules_with_context: &'r mut SpeechRulesWithContext<'c,'s,'m>, name: &str, mathml: Element<'c>, skip_self: bool, no_check_inside: bool) -> Result<Option<Element<'m>>> {
575+
fn find_arg<'r, 'c, 's:'c, 'm:'c>(
576+
rules_with_context: &'r mut SpeechRulesWithContext<'c,'s,'m>,
577+
name: &str,
578+
mathml: Element<'c>,
579+
intent_offset: &mut u32,
580+
skip_self: bool,
581+
no_check_inside: bool) -> Result<Option<Element<'m>>> {
570582
// debug!("Looking for '{}' in\n{}", name, mml_to_string(mathml));
571583
if !skip_self {
572584
if let Some(arg_val) = mathml.attribute_value("arg") {
@@ -575,7 +587,7 @@ fn find_arg<'r, 'c, 's:'c, 'm:'c>(rules_with_context: &'r mut SpeechRulesWithCon
575587
// check to see if this mathml has an intent value -- if so the value is the value of its intent value
576588
if let Some(intent_str) = mathml.attribute_value(INTENT_ATTR) {
577589
let mut lex_state = LexState::init(intent_str.trim())?;
578-
return Ok( Some( build_intent(rules_with_context, &mut lex_state, mathml)? ) );
590+
return Ok( Some( build_intent(rules_with_context, &mut lex_state, mathml, intent_offset)? ) );
579591
} else {
580592
return Ok( Some( rules_with_context.match_pattern::<Element<'m>>(mathml)? ) );
581593
}
@@ -595,7 +607,7 @@ fn find_arg<'r, 'c, 's:'c, 'm:'c>(rules_with_context: &'r mut SpeechRulesWithCon
595607

596608
for child in mathml.children() {
597609
let child = as_element(child);
598-
if let Some(element) = find_arg(rules_with_context, name, child, false, true)? {
610+
if let Some(element) = find_arg(rules_with_context, name, child, intent_offset, false, true)? {
599611
return Ok( Some(element) );
600612
}
601613
}

0 commit comments

Comments
 (0)