mirror of
https://github.com/slint-ui/slint.git
synced 2025-10-01 06:11: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);
|
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
|
/// 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
|
/// number uses a minimal formatting scheme: If \a n has no fractional part, the number will be
|
||||||
/// formatted as an integer.
|
/// 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.
|
* 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.
|
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.
|
* 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
|
```60
|
||||||
Example := Window {
|
Example := Window {
|
||||||
|
@ -255,6 +257,10 @@ Example := Window {
|
||||||
property<{a: string, b: int}> prop2: { a: "x", b: 12, c: 42 };
|
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.
|
// 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<{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::diagnostics::{BuildDiagnostics, Spanned, SpannedWithSourceFile};
|
||||||
use crate::langtype::{BuiltinElement, EnumerationValue, Type};
|
use crate::langtype::{BuiltinElement, EnumerationValue, Type};
|
||||||
use crate::object_tree::*;
|
use crate::object_tree::*;
|
||||||
use crate::parser::SyntaxNodeWithSourceFile;
|
use crate::parser::{NodeOrTokenWithSourceFile, SyntaxNodeWithSourceFile};
|
||||||
use core::cell::RefCell;
|
use core::cell::RefCell;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
|
@ -50,6 +50,10 @@ pub enum BuiltinFunction {
|
||||||
GetWindowScaleFactor,
|
GetWindowScaleFactor,
|
||||||
Debug,
|
Debug,
|
||||||
SetFocusItem,
|
SetFocusItem,
|
||||||
|
/// the "42".to_float()
|
||||||
|
StringToFloat,
|
||||||
|
/// the "42".is_float()
|
||||||
|
StringIsFloat,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BuiltinFunction {
|
impl BuiltinFunction {
|
||||||
|
@ -65,6 +69,12 @@ impl BuiltinFunction {
|
||||||
return_type: Box::new(Type::Void),
|
return_type: Box::new(Type::Void),
|
||||||
args: vec![Type::ElementReference],
|
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.
|
/// a regular FunctionCall expression where the base becomes the first argument.
|
||||||
MemberFunction {
|
MemberFunction {
|
||||||
base: Box<Expression>,
|
base: Box<Expression>,
|
||||||
base_node: SyntaxNodeWithSourceFile,
|
base_node: NodeOrTokenWithSourceFile,
|
||||||
member: Box<Expression>,
|
member: Box<Expression>,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -370,7 +380,10 @@ impl Expression {
|
||||||
},
|
},
|
||||||
Expression::Cast { to, .. } => to.clone(),
|
Expression::Cast { to, .. } => to.clone(),
|
||||||
Expression::CodeBlock(sub) => sub.last().map_or(Type::Void, |e| e.ty()),
|
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::SelfAssignment { .. } => Type::Void,
|
||||||
Expression::ResourceReference { .. } => Type::Resource,
|
Expression::ResourceReference { .. } => Type::Resource,
|
||||||
Expression::Condition { condition: _, true_expr, false_expr } => {
|
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("<array>".into());
|
||||||
file.includes.push("<limits>".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());
|
file.includes.push("<sixtyfps.h>".into());
|
||||||
|
|
||||||
for ty in &doc.inner_structs {
|
for ty in &doc.inner_structs {
|
||||||
|
@ -1262,6 +1263,25 @@ fn compile_expression(e: &crate::expression_tree::Expression, component: &Rc<Com
|
||||||
BuiltinFunction::SetFocusItem => {
|
BuiltinFunction::SetFocusItem => {
|
||||||
format!("{}.set_focus_item", window_ref_expression(component))
|
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::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"),
|
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)
|
quote!(#window_ref.scale_factor)
|
||||||
}
|
}
|
||||||
BuiltinFunction::Debug => quote!((|x| println!("{:?}", x))),
|
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::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"),
|
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 {
|
pub fn lookup_member_function(&self, name: &str) -> Expression {
|
||||||
match self {
|
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,
|
_ => 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)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct SyntaxNodeWithSourceFile {
|
pub struct SyntaxNodeWithSourceFile {
|
||||||
pub node: SyntaxNode,
|
pub node: SyntaxNode,
|
||||||
|
@ -699,11 +705,24 @@ impl SyntaxNodeWithSourceFile {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct NodeOrTokenWithSourceFile {
|
pub struct NodeOrTokenWithSourceFile {
|
||||||
node_or_token: rowan::NodeOrToken<SyntaxNode, SyntaxToken>,
|
node_or_token: rowan::NodeOrToken<SyntaxNode, SyntaxToken>,
|
||||||
source_file: Option<SourceFile>,
|
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 {
|
impl NodeOrTokenWithSourceFile {
|
||||||
pub fn kind(&self) -> SyntaxKind {
|
pub fn kind(&self) -> SyntaxKind {
|
||||||
self.node_or_token.kind()
|
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
|
/// return the normalized identifier string of the first SyntaxKind::Identifier in this node
|
||||||
pub fn identifier_text(node: &SyntaxNodeWithSourceFile) -> Option<String> {
|
pub fn identifier_text(node: &SyntaxNodeWithSourceFile) -> Option<String> {
|
||||||
node.child_text(SyntaxKind::Identifier).map(|x| normalize_identifier(&x))
|
node.child_text(SyntaxKind::Identifier).map(|x| normalize_identifier(&x))
|
||||||
|
|
|
@ -518,7 +518,7 @@ impl Expression {
|
||||||
ctx: &mut LookupCtx,
|
ctx: &mut LookupCtx,
|
||||||
) -> Expression {
|
) -> Expression {
|
||||||
let mut sub_expr =
|
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();
|
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);
|
let member = elem.borrow().base_type.lookup_member_function(&prop_name);
|
||||||
return Expression::MemberFunction {
|
return Expression::MemberFunction {
|
||||||
base: Box::new(Expression::ElementReference(Rc::downgrade(elem))),
|
base: Box::new(Expression::ElementReference(Rc::downgrade(elem))),
|
||||||
base_node: node,
|
base_node: node.into(),
|
||||||
member: Box::new(member),
|
member: Box::new(member),
|
||||||
};
|
};
|
||||||
} else {
|
} 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);
|
ctx.diag.push_error("Cannot access fields of property".into(), &next);
|
||||||
return Expression::Invalid;
|
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")
|
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"),
|
_ => panic!("call of something not a signal"),
|
||||||
}
|
}
|
||||||
Expression::SelfAssignment { lhs, rhs, op } => {
|
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