Better analysis for literals

This commit is contained in:
Laurenz 2023-01-28 23:36:27 +01:00
parent 76048a8ef4
commit 1e97d5c8cb
7 changed files with 78 additions and 31 deletions

View File

@ -131,6 +131,6 @@ pub trait Numeric:
} }
/// Round a float to two decimal places. /// Round a float to two decimal places.
fn round_2(value: f64) -> f64 { pub fn round_2(value: f64) -> f64 {
(value * 100.0).round() / 100.0 (value * 100.0).round() / 100.0
} }

View File

@ -7,6 +7,22 @@ use crate::World;
/// Try to determine a set of possible values for an expression. /// Try to determine a set of possible values for an expression.
pub fn analyze(world: &(dyn World + 'static), node: &LinkedNode) -> Vec<Value> { pub fn analyze(world: &(dyn World + 'static), node: &LinkedNode) -> Vec<Value> {
match node.cast::<ast::Expr>() { match node.cast::<ast::Expr>() {
Some(ast::Expr::None(_)) => vec![Value::None],
Some(ast::Expr::Auto(_)) => vec![Value::Auto],
Some(ast::Expr::Bool(v)) => vec![Value::Bool(v.get())],
Some(ast::Expr::Int(v)) => vec![Value::Int(v.get())],
Some(ast::Expr::Float(v)) => vec![Value::Float(v.get())],
Some(ast::Expr::Numeric(v)) => vec![Value::numeric(v.get())],
Some(ast::Expr::Str(v)) => vec![Value::Str(v.get().into())],
Some(ast::Expr::FieldAccess(access)) => {
let Some(child) = node.children().next() else { return vec![] };
analyze(world, &child)
.into_iter()
.filter_map(|target| target.field(&access.field()).ok())
.collect()
}
Some(ast::Expr::Ident(_) | ast::Expr::MathIdent(_) | ast::Expr::FuncCall(_)) => { Some(ast::Expr::Ident(_) | ast::Expr::MathIdent(_) | ast::Expr::FuncCall(_)) => {
if let Some(parent) = node.parent() { if let Some(parent) = node.parent() {
if parent.kind() == SyntaxKind::FieldAccess && node.index() > 0 { if parent.kind() == SyntaxKind::FieldAccess && node.index() > 0 {
@ -19,22 +35,9 @@ pub fn analyze(world: &(dyn World + 'static), node: &LinkedNode) -> Vec<Value> {
let route = Route::default(); let route = Route::default();
let mut tracer = Tracer::new(Some(span)); let mut tracer = Tracer::new(Some(span));
eval(world.track(), route.track(), tracer.track_mut(), source).ok(); eval(world.track(), route.track(), tracer.track_mut(), source).ok();
return tracer.finish(); tracer.finish()
} }
Some(ast::Expr::Str(s)) => return vec![Value::Str(s.get().into())], _ => vec![],
Some(ast::Expr::FieldAccess(access)) => {
if let Some(child) = node.children().next() {
return analyze(world, &child)
.into_iter()
.filter_map(|target| target.field(&access.field()).ok())
.collect();
}
}
_ => {}
} }
vec![]
} }

View File

@ -2,6 +2,7 @@ use if_chain::if_chain;
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
use super::{analyze, plain_docs_sentence, summarize_font_family}; use super::{analyze, plain_docs_sentence, summarize_font_family};
use crate::geom::{round_2, Length, Numeric};
use crate::model::{CastInfo, Tracer, Value}; use crate::model::{CastInfo, Tracer, Value};
use crate::syntax::ast; use crate::syntax::ast;
use crate::syntax::{LinkedNode, Source, SyntaxKind}; use crate::syntax::{LinkedNode, Source, SyntaxKind};
@ -22,15 +23,23 @@ pub fn tooltip(
/// Tooltip for a hovered expression. /// Tooltip for a hovered expression.
fn expr_tooltip(world: &(dyn World + 'static), leaf: &LinkedNode) -> Option<String> { fn expr_tooltip(world: &(dyn World + 'static), leaf: &LinkedNode) -> Option<String> {
if !leaf.is::<ast::Expr>() { let expr = leaf.cast::<ast::Expr>()?;
return None;
}
let values = analyze(world, leaf); let values = analyze(world, leaf);
if let [value] = values.as_slice() { if let [value] = values.as_slice() {
if let Some(docs) = value.docs() { if let Some(docs) = value.docs() {
return Some(plain_docs_sentence(docs)); return Some(plain_docs_sentence(docs));
} }
if let &Value::Length(length) = value {
if let Some(tooltip) = length_tooltip(length) {
return Some(tooltip);
}
}
}
if expr.is_literal() {
return None;
} }
let mut tooltip = String::new(); let mut tooltip = String::new();
@ -61,6 +70,19 @@ fn expr_tooltip(world: &(dyn World + 'static), leaf: &LinkedNode) -> Option<Stri
(!tooltip.is_empty()).then(|| tooltip) (!tooltip.is_empty()).then(|| tooltip)
} }
/// Tooltip text for a hovered length.
fn length_tooltip(length: Length) -> Option<String> {
length.em.is_zero().then(|| {
format!(
"{}pt = {}mm = {}cm = {}in",
round_2(length.abs.to_pt()),
round_2(length.abs.to_mm()),
round_2(length.abs.to_cm()),
round_2(length.abs.to_inches())
)
})
}
/// Tooltips for components of a named parameter. /// Tooltips for components of a named parameter.
fn named_param_tooltip( fn named_param_tooltip(
world: &(dyn World + 'static), world: &(dyn World + 'static),

View File

@ -15,7 +15,6 @@ use super::{
use crate::diag::{ use crate::diag::{
bail, error, At, SourceError, SourceResult, StrResult, Trace, Tracepoint, bail, error, At, SourceError, SourceResult, StrResult, Trace, Tracepoint,
}; };
use crate::geom::{Abs, Angle, Em, Fr, Ratio};
use crate::syntax::ast::AstNode; use crate::syntax::ast::AstNode;
use crate::syntax::{ast, Source, SourceId, Span, Spanned, SyntaxKind, SyntaxNode}; use crate::syntax::{ast, Source, SourceId, Span, Spanned, SyntaxKind, SyntaxNode};
use crate::util::PathExt; use crate::util::PathExt;
@ -660,14 +659,7 @@ impl Eval for ast::Numeric {
type Output = Value; type Output = Value;
fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> {
let (v, unit) = self.get(); Ok(Value::numeric(self.get()))
Ok(match unit {
ast::Unit::Length(unit) => Abs::with_unit(v, unit).into(),
ast::Unit::Angle(unit) => Angle::with_unit(v, unit).into(),
ast::Unit::Em => Em::new(v).into(),
ast::Unit::Fr => Fr::new(v).into(),
ast::Unit::Percent => Ratio::new(v / 100.0).into(),
})
} }
} }

View File

@ -12,7 +12,7 @@ use super::{
}; };
use crate::diag::StrResult; use crate::diag::StrResult;
use crate::geom::{Abs, Angle, Color, Em, Fr, Length, Ratio, Rel, RgbaColor}; use crate::geom::{Abs, Angle, Color, Em, Fr, Length, Ratio, Rel, RgbaColor};
use crate::syntax::Span; use crate::syntax::{ast, Span};
use crate::util::{format_eco, EcoString}; use crate::util::{format_eco, EcoString};
/// A computational value. /// A computational value.
@ -71,6 +71,18 @@ impl Value {
Self::Dyn(Dynamic::new(any)) Self::Dyn(Dynamic::new(any))
} }
/// Create a numeric value from a number with a unit.
pub fn numeric(pair: (f64, ast::Unit)) -> Self {
let (v, unit) = pair;
match unit {
ast::Unit::Length(unit) => Abs::with_unit(v, unit).into(),
ast::Unit::Angle(unit) => Angle::with_unit(v, unit).into(),
ast::Unit::Em => Em::new(v).into(),
ast::Unit::Fr => Fr::new(v).into(),
ast::Unit::Percent => Ratio::new(v / 100.0).into(),
}
}
/// The name of the stored value's type. /// The name of the stored value's type.
pub fn type_name(&self) -> &'static str { pub fn type_name(&self) -> &'static str {
match self { match self {

View File

@ -345,6 +345,20 @@ impl Expr {
_ => false, _ => false,
} }
} }
/// Is this a literal?
pub fn is_literal(&self) -> bool {
match self {
Self::None(_) => true,
Self::Auto(_) => true,
Self::Bool(_) => true,
Self::Int(_) => true,
Self::Float(_) => true,
Self::Numeric(_) => true,
Self::Str(_) => true,
_ => false,
}
}
} }
impl Default for Expr { impl Default for Expr {

View File

@ -499,7 +499,11 @@ impl Lexer<'_> {
// Read the fractional part if not already done. // Read the fractional part if not already done.
// Make sure not to confuse a range for the decimal separator. // Make sure not to confuse a range for the decimal separator.
if c != '.' && !self.s.at("..") && self.s.eat_if('.') { if c != '.'
&& !self.s.at("..")
&& !self.s.scout(1).map_or(false, is_id_start)
&& self.s.eat_if('.')
{
self.s.eat_while(char::is_ascii_digit); self.s.eat_while(char::is_ascii_digit);
} }