add ord and chr to calculator
This commit is contained in:
parent
b09058cb90
commit
f350ca4202
@ -1,3 +1,7 @@
|
|||||||
|
use std::{sync::LazyLock, time::Instant};
|
||||||
|
|
||||||
|
use fend_core::SpanKind;
|
||||||
|
|
||||||
use crate::engines::EngineResponse;
|
use crate::engines::EngineResponse;
|
||||||
|
|
||||||
use super::regex;
|
use super::regex;
|
||||||
@ -22,7 +26,7 @@ pub fn request_autocomplete(query: &str) -> Vec<String> {
|
|||||||
let query = clean_query(query.to_string());
|
let query = clean_query(query.to_string());
|
||||||
|
|
||||||
if let Some(result) = evaluate(&query, false) {
|
if let Some(result) = evaluate(&query, false) {
|
||||||
results.push(format!("{query}={result}"));
|
results.push(format!("= {result}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
results
|
results
|
||||||
@ -32,44 +36,32 @@ fn clean_query(query: String) -> String {
|
|||||||
query.strip_suffix('=').unwrap_or(&query).trim().to_string()
|
query.strip_suffix('=').unwrap_or(&query).trim().to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Span {
|
||||||
|
pub text: String,
|
||||||
|
pub kind: SpanKind,
|
||||||
|
}
|
||||||
|
|
||||||
fn evaluate(query: &str, html: bool) -> Option<String> {
|
fn evaluate(query: &str, html: bool) -> Option<String> {
|
||||||
// at least 3 characters and not one of the short constants
|
let spans = evaluate_into_spans(query, html);
|
||||||
if query.len() < 3 && !matches!(query.to_lowercase().as_str(), "pi" | "e" | "c") {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
// probably a query operator thing or a url, fend evaluates these but it shouldn't
|
if spans.is_empty() {
|
||||||
if regex!("^[a-z]{2,}:").is_match(query) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut context = fend_core::Context::new();
|
|
||||||
|
|
||||||
// make lowercase f and c work
|
|
||||||
context.define_custom_unit_v1("f", "f", "°F", &fend_core::CustomUnitAttribute::Alias);
|
|
||||||
context.define_custom_unit_v1("c", "c", "°C", &fend_core::CustomUnitAttribute::Alias);
|
|
||||||
// make random work
|
|
||||||
context.set_random_u32_fn(rand::random::<u32>);
|
|
||||||
if html {
|
|
||||||
// this makes it generate slightly nicer outputs for some queries like 2d6
|
|
||||||
context.set_output_mode_terminal();
|
|
||||||
}
|
|
||||||
|
|
||||||
let Ok(result) = fend_core::evaluate(query, &mut context) else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
let main_result = result.get_main_result();
|
|
||||||
if main_result == query {
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !html {
|
if !html {
|
||||||
return Some(main_result.to_string());
|
return Some(
|
||||||
|
spans
|
||||||
|
.iter()
|
||||||
|
.map(|span| span.text.clone())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(""),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut result_html = String::new();
|
let mut result_html = String::new();
|
||||||
for span in result.get_main_result_spans() {
|
for span in &spans {
|
||||||
let class = match span.kind() {
|
let class = match span.kind {
|
||||||
fend_core::SpanKind::Number
|
fend_core::SpanKind::Number
|
||||||
| fend_core::SpanKind::Boolean
|
| fend_core::SpanKind::Boolean
|
||||||
| fend_core::SpanKind::Date => "answer-calc-constant",
|
| fend_core::SpanKind::Date => "answer-calc-constant",
|
||||||
@ -79,12 +71,151 @@ fn evaluate(query: &str, html: bool) -> Option<String> {
|
|||||||
if !class.is_empty() {
|
if !class.is_empty() {
|
||||||
result_html.push_str(&format!(
|
result_html.push_str(&format!(
|
||||||
r#"<span class="{class}">{text}</span>"#,
|
r#"<span class="{class}">{text}</span>"#,
|
||||||
text = html_escape::encode_text(span.string())
|
text = html_escape::encode_text(&span.text)
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
result_html.push_str(&html_escape::encode_text(span.string()));
|
result_html.push_str(&html_escape::encode_text(&span.text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the result was a single hex number then we add the decimal equivalent below
|
||||||
|
if spans.len() == 1
|
||||||
|
&& spans[0].kind == fend_core::SpanKind::Number
|
||||||
|
&& spans[0].text.starts_with("0x")
|
||||||
|
{
|
||||||
|
let hex = spans[0].text.trim_start_matches("0x");
|
||||||
|
if let Ok(num) = u64::from_str_radix(hex, 16) {
|
||||||
|
result_html.push_str(&format!(
|
||||||
|
r#" <span class="answer-calc-comment">= {num}</span>"#,
|
||||||
|
num = num
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(result_html)
|
Some(result_html)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub static FEND_CONTEXT: LazyLock<fend_core::Context> = LazyLock::new(|| {
|
||||||
|
let mut context = fend_core::Context::new();
|
||||||
|
|
||||||
|
// make lowercase f and c work
|
||||||
|
context.define_custom_unit_v1("f", "f", "°F", &fend_core::CustomUnitAttribute::Alias);
|
||||||
|
context.define_custom_unit_v1("c", "c", "°C", &fend_core::CustomUnitAttribute::Alias);
|
||||||
|
// make random work
|
||||||
|
context.set_random_u32_fn(rand::random::<u32>);
|
||||||
|
|
||||||
|
fend_core::evaluate("ord=(x: x to codepoint)", &mut context).unwrap();
|
||||||
|
|
||||||
|
context
|
||||||
|
});
|
||||||
|
|
||||||
|
struct TimeoutInterrupt {
|
||||||
|
start: Instant,
|
||||||
|
timeout: u128,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TimeoutInterrupt {
|
||||||
|
fn new_with_timeout(timeout: u128) -> Self {
|
||||||
|
Self {
|
||||||
|
start: Instant::now(),
|
||||||
|
timeout,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fend_core::Interrupt for TimeoutInterrupt {
|
||||||
|
fn should_interrupt(&self) -> bool {
|
||||||
|
Instant::now().duration_since(self.start).as_millis() > self.timeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn evaluate_into_spans(query: &str, multiline: bool) -> Vec<Span> {
|
||||||
|
// match queries like "chr(8831)" or "8831 to char"
|
||||||
|
let re = regex!(
|
||||||
|
r"^(?:(?:chr|charcode|char|charcode)(?:| for| of)\s*\(?\s*(\d+)\s*\)?)|(?:(\d+) (?:|to |into |as )(?:charcode|char|character))$"
|
||||||
|
);
|
||||||
|
if let Some(m) = re.captures(query) {
|
||||||
|
if let Some(ord) = m
|
||||||
|
.get(1)
|
||||||
|
.or_else(|| m.get(2))
|
||||||
|
.and_then(|m| m.as_str().parse::<u32>().ok())
|
||||||
|
{
|
||||||
|
let chr = std::char::from_u32(ord);
|
||||||
|
if let Some(chr) = chr {
|
||||||
|
return vec![Span {
|
||||||
|
text: format!("'{chr}'"),
|
||||||
|
kind: fend_core::SpanKind::String,
|
||||||
|
}];
|
||||||
|
} else {
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// // match queries like "ord(≿)" or just "≿"
|
||||||
|
// let re = regex!(
|
||||||
|
// r"^(?:(?:chr|charcode|char|charcode)(?:| for| of)\s*\(?\s*(\d+)\s*\)?)|(?:(\d+) (?:|to |into |as )(?:charcode|char|character))$"
|
||||||
|
// );
|
||||||
|
// if let Some(m) = re.captures(query) {
|
||||||
|
// if let Some(ord) = m
|
||||||
|
// .get(1)
|
||||||
|
// .or_else(|| m.get(2))
|
||||||
|
// .and_then(|m| m.as_str().parse::<u32>().ok())
|
||||||
|
// {
|
||||||
|
// let chr = std::char::from_u32(ord);
|
||||||
|
// if let Some(chr) = chr {
|
||||||
|
// return vec![Span {
|
||||||
|
// text: format!("'{chr}'"),
|
||||||
|
// kind: fend_core::SpanKind::String,
|
||||||
|
// }];
|
||||||
|
// } else {
|
||||||
|
// return vec![];
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fend incorrectly triggers on these often
|
||||||
|
{
|
||||||
|
// at least 3 characters and not one of the short constants
|
||||||
|
if query.len() < 3 && !matches!(query.to_lowercase().as_str(), "pi" | "e" | "c") {
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
|
||||||
|
// probably a query operator thing or a url, fend evaluates these but it shouldn't
|
||||||
|
if regex!("^[a-z]{2,}:").is_match(query) {
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
|
||||||
|
// if it starts and ends with quotes then the person was just searching in quotes and didn't mean to evaluate a string
|
||||||
|
if query.starts_with('"')
|
||||||
|
&& query.ends_with('"')
|
||||||
|
&& query.chars().filter(|c| *c == '"').count() == 2
|
||||||
|
{
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut context = FEND_CONTEXT.clone();
|
||||||
|
if multiline {
|
||||||
|
// this makes it generate slightly nicer outputs for some queries like 2d6
|
||||||
|
context.set_output_mode_terminal();
|
||||||
|
}
|
||||||
|
|
||||||
|
// not a perfect anti-abuse but good enough for our purposes
|
||||||
|
let interrupt = TimeoutInterrupt::new_with_timeout(10);
|
||||||
|
let Ok(result) = fend_core::evaluate_with_interrupt(query, &mut context, &interrupt) else {
|
||||||
|
return vec![];
|
||||||
|
};
|
||||||
|
let main_result = result.get_main_result();
|
||||||
|
if main_result == query {
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
.get_main_result_spans()
|
||||||
|
.filter(|span| !span.string().is_empty())
|
||||||
|
.map(|span| Span {
|
||||||
|
text: span.string().to_string(),
|
||||||
|
kind: span.kind(),
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
@ -96,8 +96,10 @@ h1 {
|
|||||||
.search-input-suggestion {
|
.search-input-suggestion {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 0.3em 0.3em;
|
padding: 0.3em 0.3em;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.search-input-suggestion.focused {
|
.search-input-suggestion.focused,
|
||||||
|
.search-input-suggestion:hover {
|
||||||
background: #234;
|
background: #234;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,6 +111,7 @@ h1 {
|
|||||||
}
|
}
|
||||||
.search-result-anchor {
|
.search-result-anchor {
|
||||||
display: block;
|
display: block;
|
||||||
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
.search-result-url {
|
.search-result-url {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -169,6 +172,7 @@ h1 {
|
|||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
border: 1px solid #234;
|
border: 1px solid #234;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
.answer h3 {
|
.answer h3 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -191,6 +195,10 @@ h1 {
|
|||||||
.answer-calc-special {
|
.answer-calc-special {
|
||||||
color: #e6b673;
|
color: #e6b673;
|
||||||
}
|
}
|
||||||
|
.answer-calc-comment {
|
||||||
|
color: #acb6bf8c;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
/* infobox */
|
/* infobox */
|
||||||
.infobox {
|
.infobox {
|
||||||
|
Loading…
Reference in New Issue
Block a user