Skip to content

Commit 906cf8c

Browse files
committed
Speech API without global state
1 parent ca4b933 commit 906cf8c

17 files changed

Lines changed: 1489 additions & 1123 deletions

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ repository = "https://github.com/NSoiffer/MathCAT"
88
homepage = "https://nsoiffer.github.io/MathCAT/"
99
documentation = "https://nsoiffer.github.io/MathCAT/"
1010
edition = "2018"
11-
exclude = ["src/main.rs", "docs", "PythonScripts"] # should have "Rules/", but then one can't run build.rs to build the zip file
11+
exclude = ["src/main.rs", "examples/**", "docs", "PythonScripts"] # should have "Rules/", but then one can't run build.rs to build the zip file
1212

1313

1414
[features]

examples/stateless-example.rs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
use env_logger;
2+
use lazy_static::lazy_static;
3+
use libmathcat::{errors::Error, stateless_interface::*};
4+
use std::{ops::Deref, path::Path, sync::mpsc, thread};
5+
6+
const EXPR_1: &'static str = r#"<math><mrow><msup><mi>sin</mi><mn>2</mn></msup><mo>⁡</mo><mspace></mspace><mi>x</mi></mrow></math>"#;
7+
const EXPR_2: &'static str = r#"<math><mrow><msup><mi>cos</mi><mn>2</mn></msup><mo>⁡</mo><mspace></mspace><mi>x</mi></mrow></math>"#;
8+
9+
struct MathCatHolder {
10+
language: &'static str,
11+
mathcat: MathCat,
12+
}
13+
14+
// `mathcat` is full of RCs, need to figure out what to do here to explain to Rust
15+
// that it's OK to use a non-mut MathCat across threads because all Rcs are actually
16+
// owned by the `MathCat` instance.
17+
unsafe impl Sync for MathCatHolder {}
18+
19+
fn build_mathcat(language: &'static str) -> Result<MathCatHolder, Error> {
20+
let rules_dir = std::env::current_exe().unwrap().parent().unwrap().join("../../../Rules");
21+
let rules_dir = rules_dir.as_os_str().to_str().unwrap().to_string();
22+
23+
let mut builder = MathCatBuilder::new();
24+
builder.set_rules_dir(Path::new(&rules_dir));
25+
builder.set_pref("Language", language);
26+
Ok(MathCatHolder { language: language, mathcat: builder.build()? })
27+
}
28+
29+
fn main() -> Result<(), Error> {
30+
// Run with RUST_LOG=debug to see some debugging information.l
31+
env_logger::builder()
32+
.format_timestamp(None)
33+
.format_module_path(false)
34+
.format_indent(Some(2))
35+
.format_level(false)
36+
.init();
37+
38+
lazy_static! {
39+
static ref mathcat_en: MathCatHolder = build_mathcat("en").unwrap();
40+
static ref mathcat_es: MathCatHolder = build_mathcat("es").unwrap();
41+
}
42+
43+
// Initialization is not thread-safe, ensure everything is initialized:
44+
let _ = mathcat_en.deref();
45+
let _ = mathcat_es.deref();
46+
47+
// Once initialized, MathCat instances are thread-compatible.
48+
let (tx, rx) = mpsc::channel();
49+
let mut threads = Vec::<thread::JoinHandle<Result<(), mpsc::SendError<(&'static str, Result<String, libmathcat::errors::Error>)>>>>::new();
50+
{
51+
let tx = tx.clone();
52+
threads.push(thread::spawn(move || {
53+
tx.send((
54+
mathcat_en.language,
55+
mathcat_en.mathcat.mathml_to_spoken_text(EXPR_1)))
56+
}));
57+
}
58+
{
59+
let tx = tx.clone();
60+
threads.push(thread::spawn(move || {
61+
tx.send((
62+
mathcat_en.language,
63+
mathcat_en.mathcat.mathml_to_spoken_text(EXPR_2)))
64+
}));
65+
}
66+
{
67+
let tx = tx.clone();
68+
threads.push(thread::spawn(move || {
69+
tx.send((
70+
mathcat_es.language,
71+
mathcat_es.mathcat.mathml_to_spoken_text(EXPR_1)))
72+
}));
73+
}
74+
{
75+
let tx = tx.clone();
76+
threads.push(thread::spawn(move || {
77+
tx.send((
78+
mathcat_es.language,
79+
mathcat_es.mathcat.mathml_to_spoken_text(EXPR_2)))
80+
}));
81+
}
82+
83+
let rcv_thread = thread::spawn(move || {
84+
let mut pending = 4;
85+
let mut has_errors = false;
86+
while let Ok((language, result)) = rx.recv() {
87+
match result {
88+
Ok(text) => println!("{}: {}", language, text),
89+
Err(e) => {
90+
has_errors = true;
91+
println!("{}: Error!\n{:?}", language, e);
92+
}
93+
};
94+
pending -= 1;
95+
if pending == 0 { break; }
96+
}
97+
has_errors
98+
});
99+
100+
for thread in threads {
101+
let _ = thread.join().unwrap();
102+
}
103+
let has_errors = rcv_thread.join().unwrap();
104+
if has_errors { std::process::exit(1); }
105+
Ok(())
106+
}

src/braille.rs

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -25,35 +25,38 @@ pub fn braille_mathml(mathml: Element, nav_node_id: &str) -> Result<(String, usi
2525
return BRAILLE_RULES.with(|rules| {
2626
rules.borrow_mut().read_files()?;
2727
let rules = rules.borrow();
28-
let new_package = Package::new();
29-
let mut rules_with_context = SpeechRulesWithContext::new(&rules, new_package.as_document(), nav_node_id);
30-
let braille_string = rules_with_context.match_pattern::<String>(mathml)
31-
.chain_err(|| "Pattern match/replacement failure!")?;
32-
// debug!("braille_mathml: braille string: {}", &braille_string);
33-
let braille_string = braille_string.replace(' ', "");
34-
let pref_manager = rules_with_context.get_rules().pref_manager.borrow();
35-
let highlight_style = pref_manager.pref_to_string("BrailleNavHighlight");
36-
let braille_code = pref_manager.pref_to_string("BrailleCode");
37-
let braille = match braille_code.as_str() {
38-
"Nemeth" => nemeth_cleanup(pref_manager, braille_string),
39-
"UEB" => ueb_cleanup(pref_manager, braille_string),
40-
"Vietnam" => vietnam_cleanup(pref_manager, braille_string),
41-
"CMU" => cmu_cleanup(pref_manager, braille_string),
42-
"Finnish" => finnish_cleanup(pref_manager, braille_string),
43-
"Swedish" => swedish_cleanup(pref_manager, braille_string),
44-
"LaTeX" => LaTeX_cleanup(pref_manager, braille_string),
45-
"ASCIIMath" => ASCIIMath_cleanup(pref_manager, braille_string),
46-
_ => braille_string.trim_matches('⠀').to_string(), // probably needs cleanup if someone has another code, but this will have to get added by hand
47-
};
28+
return SPEECH_DEFINITIONS.with_borrow(|definitions| {
29+
let new_package = Package::new();
30+
let mut rules_with_context =
31+
SpeechRulesWithContext::new(&rules, definitions, new_package.as_document(), nav_node_id);
32+
let braille_string = rules_with_context.match_pattern::<String>(mathml)
33+
.chain_err(|| "Pattern match/replacement failure!")?;
34+
// debug!("braille_mathml: braille string: {}", &braille_string);
35+
let braille_string = braille_string.replace(' ', "");
36+
let pref_manager = rules_with_context.get_rules().pref_manager.borrow();
37+
let highlight_style = pref_manager.pref_to_string("BrailleNavHighlight");
38+
let braille_code = pref_manager.pref_to_string("BrailleCode");
39+
let braille = match braille_code.as_str() {
40+
"Nemeth" => nemeth_cleanup(pref_manager, braille_string),
41+
"UEB" => ueb_cleanup(pref_manager, braille_string),
42+
"Vietnam" => vietnam_cleanup(pref_manager, braille_string),
43+
"CMU" => cmu_cleanup(pref_manager, braille_string),
44+
"Finnish" => finnish_cleanup(pref_manager, braille_string),
45+
"Swedish" => swedish_cleanup(pref_manager, braille_string),
46+
"LaTeX" => LaTeX_cleanup(pref_manager, braille_string),
47+
"ASCIIMath" => ASCIIMath_cleanup(pref_manager, braille_string),
48+
_ => braille_string.trim_matches('⠀').to_string(), // probably needs cleanup if someone has another code, but this will have to get added by hand
49+
};
4850

49-
return Ok(
50-
if highlight_style != "Off" {
51-
highlight_braille_chars(braille, &braille_code, highlight_style == "All")
52-
} else {
53-
let end = braille.len()/3;
54-
(braille, 0, end)
55-
}
56-
);
51+
return Ok(
52+
if highlight_style != "Off" {
53+
highlight_braille_chars(braille, &braille_code, highlight_style == "All")
54+
} else {
55+
let end = braille.len()/3;
56+
(braille, 0, end)
57+
}
58+
);
59+
});
5760
});
5861

5962
/// highlight with dots 7 & 8 based on the highlight style

0 commit comments

Comments
 (0)