mirror of
https://github.com/slint-ui/slint.git
synced 2025-10-01 14:21:16 +00:00
String -> Float conversions
This commit is contained in:
parent
3d7b69ecad
commit
11e55dd8d2
10 changed files with 177 additions and 7 deletions
|
@ -82,6 +82,9 @@ struct SharedString
|
|||
return cbindgen_private::sixtyfps_shared_string_bytes(this);
|
||||
}
|
||||
|
||||
const char *begin() const { return data(); }
|
||||
const char *end() const { return std::string_view(*this).end(); }
|
||||
|
||||
/// Creates a new SharedString from the given number \a n. The string representation of the
|
||||
/// number uses a minimal formatting scheme: If \a n has no fractional part, the number will be
|
||||
/// formatted as an integer.
|
||||
|
|
|
@ -244,6 +244,8 @@ Example := Window {
|
|||
* Object types convert with another object type if they have the same property names and their types can be converted.
|
||||
The source object can have either missing properties, or extra properties. But not both.
|
||||
* Array generaly do not convert between eachother. But array literal can be converted if the type does convert.
|
||||
* String can be converted to float by using the `to_float` function. That function returns 0 if the string is not
|
||||
a valid number. you can check with `is_float` if the string contains a valid number
|
||||
|
||||
```60
|
||||
Example := Window {
|
||||
|
@ -255,6 +257,10 @@ Example := Window {
|
|||
property<{a: string, b: int}> prop2: { a: "x", b: 12, c: 42 };
|
||||
// ERROR: b is missing and c is extra, this does not compile, because it could be a typo.
|
||||
// property<{a: string, b: int}> prop2: { a: "x", c: 42 };
|
||||
|
||||
property<string> xxx: "42.1";
|
||||
property<float> xxx1: xxx.to_float(); // 42.1
|
||||
property<bool> xxx2: xxx.is_float(); // true
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ LICENSE END */
|
|||
use crate::diagnostics::{BuildDiagnostics, Spanned, SpannedWithSourceFile};
|
||||
use crate::langtype::{BuiltinElement, EnumerationValue, Type};
|
||||
use crate::object_tree::*;
|
||||
use crate::parser::SyntaxNodeWithSourceFile;
|
||||
use crate::parser::{NodeOrTokenWithSourceFile, SyntaxNodeWithSourceFile};
|
||||
use core::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::hash::Hash;
|
||||
|
@ -50,6 +50,10 @@ pub enum BuiltinFunction {
|
|||
GetWindowScaleFactor,
|
||||
Debug,
|
||||
SetFocusItem,
|
||||
/// the "42".to_float()
|
||||
StringToFloat,
|
||||
/// the "42".is_float()
|
||||
StringIsFloat,
|
||||
}
|
||||
|
||||
impl BuiltinFunction {
|
||||
|
@ -65,6 +69,12 @@ impl BuiltinFunction {
|
|||
return_type: Box::new(Type::Void),
|
||||
args: vec![Type::ElementReference],
|
||||
},
|
||||
BuiltinFunction::StringToFloat => {
|
||||
Type::Function { return_type: Box::new(Type::Float32), args: vec![Type::String] }
|
||||
}
|
||||
BuiltinFunction::StringIsFloat => {
|
||||
Type::Function { return_type: Box::new(Type::Bool), args: vec![Type::String] }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -200,7 +210,7 @@ pub enum Expression {
|
|||
/// a regular FunctionCall expression where the base becomes the first argument.
|
||||
MemberFunction {
|
||||
base: Box<Expression>,
|
||||
base_node: SyntaxNodeWithSourceFile,
|
||||
base_node: NodeOrTokenWithSourceFile,
|
||||
member: Box<Expression>,
|
||||
},
|
||||
|
||||
|
@ -370,7 +380,10 @@ impl Expression {
|
|||
},
|
||||
Expression::Cast { to, .. } => to.clone(),
|
||||
Expression::CodeBlock(sub) => sub.last().map_or(Type::Void, |e| e.ty()),
|
||||
Expression::FunctionCall { function, .. } => function.ty(),
|
||||
Expression::FunctionCall { function, .. } => match function.ty() {
|
||||
Type::Function { return_type, .. } => *return_type,
|
||||
_ => Type::Invalid,
|
||||
},
|
||||
Expression::SelfAssignment { .. } => Type::Void,
|
||||
Expression::ResourceReference { .. } => Type::Resource,
|
||||
Expression::Condition { condition: _, true_expr, false_expr } => {
|
||||
|
|
|
@ -509,6 +509,7 @@ pub fn generate(doc: &Document, diag: &mut BuildDiagnostics) -> Option<impl std:
|
|||
|
||||
file.includes.push("<array>".into());
|
||||
file.includes.push("<limits>".into());
|
||||
file.includes.push("<cstdlib>".into()); // TODO: ideally only include this if needed (by to_float)
|
||||
file.includes.push("<sixtyfps.h>".into());
|
||||
|
||||
for ty in &doc.inner_structs {
|
||||
|
@ -1262,6 +1263,25 @@ fn compile_expression(e: &crate::expression_tree::Expression, component: &Rc<Com
|
|||
BuiltinFunction::SetFocusItem => {
|
||||
format!("{}.set_focus_item", window_ref_expression(component))
|
||||
}
|
||||
|
||||
/* std::from_chars is unfortunately not yet implemented in gcc
|
||||
BuiltinFunction::SringIsFloat => {
|
||||
"[](const auto &a){ double v; auto r = std::from_chars(std::begin(a), std::end(a), v); return r.ptr == std::end(a); }"
|
||||
.into()
|
||||
}
|
||||
BuiltinFunction::StringToFloat => {
|
||||
"[](const auto &a){ double v; auto r = std::from_chars(std::begin(a), std::end(a), v); return r.ptr == std::end(a) ? v : 0; }"
|
||||
.into()
|
||||
}*/
|
||||
BuiltinFunction::StringIsFloat => {
|
||||
"[](const auto &a){ auto e1 = std::end(a); auto e2 = const_cast<char*>(e1); std::strtod(std::begin(a), &e2); return e1 == e2; }"
|
||||
.into()
|
||||
}
|
||||
BuiltinFunction::StringToFloat => {
|
||||
"[](const auto &a){ auto e1 = std::end(a); auto e2 = const_cast<char*>(e1); auto r = std::strtod(std::begin(a), &e2); return e1 == e2 ? r : 0; }"
|
||||
.into()
|
||||
}
|
||||
|
||||
},
|
||||
Expression::ElementReference(_) => todo!("Element references are only supported in the context of built-in function calls at the moment"),
|
||||
Expression::MemberFunction { .. } => panic!("member function expressions must not appear in the code generator anymore"),
|
||||
|
|
|
@ -1049,7 +1049,15 @@ fn compile_expression(e: &Expression, component: &Rc<Component>) -> TokenStream
|
|||
quote!(#window_ref.scale_factor)
|
||||
}
|
||||
BuiltinFunction::Debug => quote!((|x| println!("{:?}", x))),
|
||||
BuiltinFunction::SetFocusItem => panic!("internal error: SetFocusItem is handled directly in CallFunction")
|
||||
BuiltinFunction::SetFocusItem => {
|
||||
panic!("internal error: SetFocusItem is handled directly in CallFunction")
|
||||
}
|
||||
BuiltinFunction::StringToFloat => {
|
||||
quote!((|x: SharedString| -> f64 { ::core::str::FromStr::from_str(x.as_str()).unwrap_or_default() } ))
|
||||
}
|
||||
BuiltinFunction::StringIsFloat => {
|
||||
quote!((|x: SharedString| { <f64 as ::core::str::FromStr>::from_str(x.as_str()).is_ok() } ))
|
||||
}
|
||||
},
|
||||
Expression::ElementReference(_) => todo!("Element references are only supported in the context of built-in function calls at the moment"),
|
||||
Expression::MemberFunction{ .. } => panic!("member function expressions must not appear in the code generator anymore"),
|
||||
|
|
|
@ -238,7 +238,9 @@ impl Type {
|
|||
|
||||
pub fn lookup_member_function(&self, name: &str) -> Expression {
|
||||
match self {
|
||||
Type::Builtin(builtin) => builtin.member_functions.get(name).unwrap().clone(),
|
||||
Type::Builtin(builtin) => {
|
||||
builtin.member_functions.get(name).cloned().unwrap_or(Expression::Invalid)
|
||||
}
|
||||
_ => Expression::Invalid,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -639,6 +639,12 @@ impl Spanned for SyntaxToken {
|
|||
}
|
||||
}
|
||||
|
||||
impl Spanned for rowan::NodeOrToken<SyntaxNode, SyntaxToken> {
|
||||
fn span(&self) -> crate::diagnostics::Span {
|
||||
crate::diagnostics::Span::new(self.text_range().start().into())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SyntaxNodeWithSourceFile {
|
||||
pub node: SyntaxNode,
|
||||
|
@ -699,11 +705,24 @@ impl SyntaxNodeWithSourceFile {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NodeOrTokenWithSourceFile {
|
||||
node_or_token: rowan::NodeOrToken<SyntaxNode, SyntaxToken>,
|
||||
source_file: Option<SourceFile>,
|
||||
}
|
||||
|
||||
impl From<SyntaxNodeWithSourceFile> for NodeOrTokenWithSourceFile {
|
||||
fn from(n: SyntaxNodeWithSourceFile) -> Self {
|
||||
Self { node_or_token: n.node.into(), source_file: n.source_file }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SyntaxTokenWithSourceFile> for NodeOrTokenWithSourceFile {
|
||||
fn from(n: SyntaxTokenWithSourceFile) -> Self {
|
||||
Self { node_or_token: n.token.into(), source_file: n.source_file }
|
||||
}
|
||||
}
|
||||
|
||||
impl NodeOrTokenWithSourceFile {
|
||||
pub fn kind(&self) -> SyntaxKind {
|
||||
self.node_or_token.kind()
|
||||
|
@ -767,6 +786,18 @@ impl SpannedWithSourceFile for SyntaxTokenWithSourceFile {
|
|||
}
|
||||
}
|
||||
|
||||
impl Spanned for NodeOrTokenWithSourceFile {
|
||||
fn span(&self) -> crate::diagnostics::Span {
|
||||
self.node_or_token.span()
|
||||
}
|
||||
}
|
||||
|
||||
impl SpannedWithSourceFile for NodeOrTokenWithSourceFile {
|
||||
fn source_file(&self) -> Option<&SourceFile> {
|
||||
self.source_file.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
/// return the normalized identifier string of the first SyntaxKind::Identifier in this node
|
||||
pub fn identifier_text(node: &SyntaxNodeWithSourceFile) -> Option<String> {
|
||||
node.child_text(SyntaxKind::Identifier).map(|x| normalize_identifier(&x))
|
||||
|
|
|
@ -518,7 +518,7 @@ impl Expression {
|
|||
ctx: &mut LookupCtx,
|
||||
) -> Expression {
|
||||
let mut sub_expr =
|
||||
node.Expression().map(|n| (Self::from_expression_node(n.clone(), ctx), n.0));
|
||||
node.Expression().map(|n| (Self::from_expression_node(n.clone(), ctx), n.0.into()));
|
||||
|
||||
let mut arguments = Vec::new();
|
||||
|
||||
|
@ -814,7 +814,7 @@ fn continue_lookup_within_element(
|
|||
let member = elem.borrow().base_type.lookup_member_function(&prop_name);
|
||||
return Expression::MemberFunction {
|
||||
base: Box::new(Expression::ElementReference(Rc::downgrade(elem))),
|
||||
base_node: node,
|
||||
base_node: node.into(),
|
||||
member: Box::new(member),
|
||||
};
|
||||
} else {
|
||||
|
@ -876,6 +876,24 @@ fn maybe_lookup_object(
|
|||
});
|
||||
}
|
||||
}
|
||||
Type::String => {
|
||||
return Expression::MemberFunction {
|
||||
base: Box::new(base),
|
||||
base_node: next.clone().into(), // Note that this is not the base_node, but the function's node
|
||||
member: Box::new(match next_str.as_str() {
|
||||
"is_float" => {
|
||||
Expression::BuiltinFunctionReference(BuiltinFunction::StringIsFloat)
|
||||
}
|
||||
"to_float" => {
|
||||
Expression::BuiltinFunctionReference(BuiltinFunction::StringToFloat)
|
||||
}
|
||||
_ => {
|
||||
ctx.diag.push_error("Cannot access fields of string".into(), &next);
|
||||
return Expression::Invalid;
|
||||
}
|
||||
}),
|
||||
};
|
||||
}
|
||||
_ => {
|
||||
ctx.diag.push_error("Cannot access fields of property".into(), &next);
|
||||
return Expression::Invalid;
|
||||
|
|
|
@ -384,6 +384,26 @@ pub fn eval_expression(e: &Expression, local_context: &mut EvalLocalContext) ->
|
|||
panic!("internal error: argument to SetFocusItem must be an element")
|
||||
}
|
||||
}
|
||||
Expression::BuiltinFunctionReference(BuiltinFunction::StringIsFloat) => {
|
||||
if arguments.len() != 1 {
|
||||
panic!("internal error: incorrect argument count to StringIsFloat")
|
||||
}
|
||||
if let Value::String(s) = eval_expression(&arguments[0], local_context) {
|
||||
Value::Bool(<f64 as core::str::FromStr>::from_str(s.as_str()).is_ok())
|
||||
} else {
|
||||
panic!("Argument not a string");
|
||||
}
|
||||
}
|
||||
Expression::BuiltinFunctionReference(BuiltinFunction::StringToFloat) => {
|
||||
if arguments.len() != 1 {
|
||||
panic!("internal error: incorrect argument count to StringToFloat")
|
||||
}
|
||||
if let Value::String(s) = eval_expression(&arguments[0], local_context) {
|
||||
Value::Number(core::str::FromStr::from_str(s.as_str()).unwrap_or(0.))
|
||||
} else {
|
||||
panic!("Argument not a string");
|
||||
}
|
||||
}
|
||||
_ => panic!("call of something not a signal"),
|
||||
}
|
||||
Expression::SelfAssignment { lhs, rhs, op } => {
|
||||
|
|
49
tests/cases/types/string_to_float.60
Normal file
49
tests/cases/types/string_to_float.60
Normal file
|
@ -0,0 +1,49 @@
|
|||
/* LICENSE BEGIN
|
||||
This file is part of the SixtyFPS Project -- https://sixtyfps.io
|
||||
Copyright (c) 2020 Olivier Goffart <olivier.goffart@sixtyfps.io>
|
||||
Copyright (c) 2020 Simon Hausmann <simon.hausmann@sixtyfps.io>
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-only
|
||||
This file is also available under commercial licensing terms.
|
||||
Please contact info@sixtyfps.io for more information.
|
||||
LICENSE END */
|
||||
TestCase := Rectangle {
|
||||
property<string> hello: "hello";
|
||||
property<string> number: "42.56";
|
||||
property<string> invalid: "132a";
|
||||
property<string> negative: "-1200000.1";
|
||||
|
||||
property<float> number_as_float: number.to_float();
|
||||
property<float> negative_as_float: negative.to_float();
|
||||
property<bool> test_is_float: !hello.is_float() && number.is_float() &&
|
||||
!invalid.is_float() && negative.is_float();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
|
||||
```cpp
|
||||
auto fuzzy_compare = [](float a, float b) { return std::abs(a - b) < 0.00000001; };
|
||||
TestCase instance;
|
||||
assert(instance.get_test_is_float());
|
||||
assert(fuzzy_compare(instance.get_number_as_float(), 42.56));
|
||||
assert(fuzzy_compare(instance.get_negative_as_float(), -1200000.1));
|
||||
```
|
||||
|
||||
```rust
|
||||
let instance = TestCase::new();
|
||||
let instance = instance.as_ref();
|
||||
assert!(instance.get_test_is_float());
|
||||
assert_eq!(instance.get_number_as_float(), 42.56);
|
||||
assert_eq!(instance.get_negative_as_float(), -1200000.1);
|
||||
```
|
||||
|
||||
```js
|
||||
function n(a) { return Math.round(a*10000) }
|
||||
var instance = new sixtyfps.TestCase({});
|
||||
assert(instance.test_is_float);
|
||||
assert.equal(n(instance.number_as_float), n(42.56));
|
||||
assert.equal(n(instance.negative_as_float/1000), n(-1200000.1/1000));
|
||||
```
|
||||
|
||||
*/
|
Loading…
Add table
Add a link
Reference in a new issue