String -> Float conversions

This commit is contained in:
Olivier Goffart 2020-11-03 15:19:34 +01:00
parent 3d7b69ecad
commit 11e55dd8d2
10 changed files with 177 additions and 7 deletions

View file

@ -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.

View file

@ -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
}
```

View file

@ -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 } => {

View file

@ -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"),

View file

@ -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"),

View file

@ -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,
}
}

View file

@ -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))

View file

@ -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;

View file

@ -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 } => {

View 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));
```
*/