mirror of
https://github.com/slint-ui/slint.git
synced 2025-10-01 14:21:16 +00:00
Rework the Rust entry point API for the interpreter
Switch to the stateful `ComponentCompiler` concept that we discussed for the C++ API.
This commit is contained in:
parent
6de1c4e3b5
commit
4a9cd954b8
9 changed files with 169 additions and 119 deletions
|
@ -62,14 +62,11 @@ fn load(mut cx: FunctionContext) -> JsResult<JsValue> {
|
|||
}
|
||||
None => vec![],
|
||||
};
|
||||
let compiler_config =
|
||||
sixtyfps_interpreter::CompilerConfiguration::new().with_include_paths(include_paths);
|
||||
let (c, diags) = spin_on::spin_on(sixtyfps_interpreter::ComponentDefinition::from_path(
|
||||
path,
|
||||
compiler_config,
|
||||
));
|
||||
let mut compiler = sixtyfps_interpreter::ComponentCompiler::new();
|
||||
compiler.set_include_paths(include_paths);
|
||||
let c = spin_on::spin_on(compiler.build_from_path(path));
|
||||
|
||||
sixtyfps_interpreter::print_diagnostics(&diags);
|
||||
sixtyfps_interpreter::print_diagnostics(&compiler.diagnostics());
|
||||
|
||||
let c = if let Some(c) = c { c } else { return cx.throw_error("Compilation error") };
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ pub async fn compile_from_string(
|
|||
#[cfg(feature = "console_error_panic_hook")]
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
let mut config = sixtyfps_interpreter::CompilerConfiguration::new();
|
||||
let mut compiler = sixtyfps_interpreter::ComponentCompiler::new();
|
||||
|
||||
if let Some(load_callback) = optional_import_callback {
|
||||
let open_import_fallback = move |file_name: &Path| -> core::pin::Pin<
|
||||
|
@ -55,12 +55,10 @@ pub async fn compile_from_string(
|
|||
}
|
||||
})
|
||||
};
|
||||
config = config.with_file_loader(open_import_fallback);
|
||||
compiler.set_file_loader(open_import_fallback);
|
||||
}
|
||||
|
||||
let (c, diags) =
|
||||
sixtyfps_interpreter::ComponentDefinition::from_source(source, base_url.into(), config)
|
||||
.await;
|
||||
let c = compiler.build_from_source(source, base_url.into()).await;
|
||||
|
||||
match c {
|
||||
Some(c) => Ok(WrappedCompiledComp(c)),
|
||||
|
@ -72,7 +70,7 @@ pub async fn compile_from_string(
|
|||
let level_key = JsValue::from_str("level");
|
||||
let mut error_as_string = String::new();
|
||||
let array = js_sys::Array::new();
|
||||
for d in diags.into_iter() {
|
||||
for d in compiler.diagnostics().into_iter() {
|
||||
let filename = d
|
||||
.source_file()
|
||||
.as_ref()
|
||||
|
|
|
@ -66,6 +66,7 @@ mod passes {
|
|||
}
|
||||
|
||||
/// CompilationConfiguration allows configuring different aspects of the compiler.
|
||||
#[derive(Clone)]
|
||||
pub struct CompilerConfiguration {
|
||||
/// Indicate whether to embed resources such as images in the generated output or whether
|
||||
/// to retain references to the resources on the file system.
|
||||
|
@ -80,7 +81,7 @@ pub struct CompilerConfiguration {
|
|||
/// The callback should open the file specified by the given file name and
|
||||
/// return an future that provides the text content of the file as output.
|
||||
pub open_import_fallback:
|
||||
Option<Box<dyn Fn(String) -> Pin<Box<dyn Future<Output = std::io::Result<String>>>>>>,
|
||||
Option<Rc<dyn Fn(String) -> Pin<Box<dyn Future<Output = std::io::Result<String>>>>>>,
|
||||
}
|
||||
|
||||
impl CompilerConfiguration {
|
||||
|
|
|
@ -481,7 +481,7 @@ fn test_load_from_callback_ok() {
|
|||
let mut compiler_config =
|
||||
CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
|
||||
compiler_config.style = Some("ugly".into());
|
||||
compiler_config.open_import_fallback = Some(Box::new(move |path| {
|
||||
compiler_config.open_import_fallback = Some(Rc::new(move |path| {
|
||||
let ok_ = ok_.clone();
|
||||
Box::pin(async move {
|
||||
assert_eq!(path, "../FooBar.60");
|
||||
|
|
|
@ -334,7 +334,124 @@ impl TryInto<Vec<Value>> for Value {
|
|||
}
|
||||
}
|
||||
|
||||
/// ComponentDescription is a representation of a compiled component from .60
|
||||
/// ComponentCompiler is the entry point to the SixtyFPS interpreter that can be used
|
||||
/// to load .60 files or compile them on-the-fly from a string.
|
||||
pub struct ComponentCompiler {
|
||||
config: sixtyfps_compilerlib::CompilerConfiguration,
|
||||
diagnostics: Vec<Diagnostic>,
|
||||
}
|
||||
|
||||
impl ComponentCompiler {
|
||||
/// Returns a new ComponentCompiler
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
config: sixtyfps_compilerlib::CompilerConfiguration::new(
|
||||
sixtyfps_compilerlib::generator::OutputFormat::Interpreter,
|
||||
),
|
||||
diagnostics: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new configuration that includes sets the include paths used for looking up
|
||||
/// `.60` imports to the specified vector of paths.
|
||||
pub fn set_include_paths(&mut self, include_paths: Vec<std::path::PathBuf>) {
|
||||
self.config.include_paths = include_paths;
|
||||
}
|
||||
|
||||
/// Create a new configuration that selects the style to be used for widgets.
|
||||
pub fn set_style(&mut self, style: String) {
|
||||
self.config.style = Some(style);
|
||||
}
|
||||
|
||||
/// Create a new configuration that will use the provided callback for loading.
|
||||
pub fn set_file_loader(
|
||||
&mut self,
|
||||
file_loader_fallback: impl Fn(
|
||||
&Path,
|
||||
)
|
||||
-> core::pin::Pin<Box<dyn core::future::Future<Output = std::io::Result<String>>>>
|
||||
+ 'static,
|
||||
) {
|
||||
self.config.open_import_fallback =
|
||||
Some(Rc::new(move |path| file_loader_fallback(Path::new(path.as_str()))));
|
||||
}
|
||||
|
||||
/// Returns the diagnostics that were produced in the last call to [[build_from_path]] or [[build_from_source]].
|
||||
pub fn diagnostics(&self) -> &Vec<Diagnostic> {
|
||||
&self.diagnostics
|
||||
}
|
||||
|
||||
/// Compile a .60 file into a ComponentDefinition
|
||||
///
|
||||
/// Returns the compiled `ComponentDefinition` if there was no errors.
|
||||
///
|
||||
/// Any diagnostics produced during the compilation, such as warnigns or errors, are collected
|
||||
/// in this ComponentCompiler and can be retrieved after the call using the [[diagnostics()]]
|
||||
/// function. The [`print_diagnostics`] function can be used to display the diagnostics
|
||||
/// to the users.
|
||||
///
|
||||
/// Diagnostics from previous calls are cleared when calling this function.
|
||||
///
|
||||
/// This function is `async` but in practice, this is only asynchronious if
|
||||
/// [`set_file_loader`] was called and its future is actually asynchronious.
|
||||
/// If that is not used, then it is fine to use a very simple executor, such as the one
|
||||
/// provided by the `spin_on` crate
|
||||
pub async fn build_from_path<P: AsRef<Path>>(
|
||||
&mut self,
|
||||
path: P,
|
||||
) -> Option<ComponentDefinition> {
|
||||
let path = path.as_ref();
|
||||
let source = match sixtyfps_compilerlib::diagnostics::load_from_path(path) {
|
||||
Ok(s) => s,
|
||||
Err(d) => {
|
||||
self.diagnostics = vec![d];
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
// We create here a 'static guard. That's alright because we make sure
|
||||
// in this module that we only use erased component
|
||||
let guard = unsafe { generativity::Guard::new(generativity::Id::new()) };
|
||||
|
||||
let (c, diag) =
|
||||
crate::dynamic_component::load(source, path.into(), self.config.clone(), guard).await;
|
||||
self.diagnostics = diag.into_iter().collect();
|
||||
c.ok().map(|inner| ComponentDefinition { inner })
|
||||
}
|
||||
|
||||
/// Compile some .60 code into a ComponentDefinition
|
||||
///
|
||||
/// The `path` argument will be used for diagnostics and to compute relative
|
||||
/// path while importing
|
||||
///
|
||||
/// Any diagnostics produced during the compilation, such as warnigns or errors, are collected
|
||||
/// in this ComponentCompiler and can be retrieved after the call using the [[diagnostics()]]
|
||||
/// function. The [`print_diagnostics`] function can be used to display the diagnostics
|
||||
/// to the users.
|
||||
///
|
||||
/// Diagnostics from previous calls are cleared when calling this function.
|
||||
///
|
||||
/// This function is `async` but in practice, this is only asynchronious if
|
||||
/// [`set_file_loader`] is set and its future is actually asynchronious.
|
||||
/// If that is not used, then it is fine to use a very simple executor, such as the one
|
||||
/// provided by the `spin_on` crate
|
||||
pub async fn build_from_source(
|
||||
&mut self,
|
||||
source_code: String,
|
||||
path: PathBuf,
|
||||
) -> Option<ComponentDefinition> {
|
||||
// We create here a 'static guard. That's alright because we make sure
|
||||
// in this module that we only use erased component
|
||||
let guard = unsafe { generativity::Guard::new(generativity::Id::new()) };
|
||||
|
||||
let (c, diag) =
|
||||
crate::dynamic_component::load(source_code, path, self.config.clone(), guard).await;
|
||||
self.diagnostics = diag.into_iter().collect();
|
||||
c.ok().map(|inner| ComponentDefinition { inner })
|
||||
}
|
||||
}
|
||||
|
||||
/// ComponentDefinition is a representation of a compiled component from .60
|
||||
///
|
||||
/// It can be constructed from a .60 file using the [`Self::from_path`] or [`Self::from_source`] functions.
|
||||
/// And then it can be instentiated with the [`Self::create`] function
|
||||
|
@ -344,66 +461,6 @@ pub struct ComponentDefinition {
|
|||
}
|
||||
|
||||
impl ComponentDefinition {
|
||||
/// Compile a .60 file into a ComponentDefinition
|
||||
///
|
||||
/// The first element of the returned tuple is going to be the compiled
|
||||
/// `ComponentDefinition` if there was no errors. This function also return
|
||||
/// a vector if diagnostics with errors and/or warnings.
|
||||
/// The [`print_diagnostics`] function can be used to display the diagnostics
|
||||
/// to the users.
|
||||
///
|
||||
/// This function is `async` but in practice, this is only asynchronious if
|
||||
/// [`CompilerConfiguration::with_file_loader`] is set and its future is actually asynchronious.
|
||||
/// If that is not used, then it is fine to use a very simple executor, such as the one
|
||||
/// provided by the `spin_on` crate
|
||||
pub async fn from_path<P: AsRef<Path>>(
|
||||
path: P,
|
||||
config: CompilerConfiguration,
|
||||
) -> (Option<Self>, Vec<Diagnostic>) {
|
||||
let path = path.as_ref();
|
||||
let source = match sixtyfps_compilerlib::diagnostics::load_from_path(path) {
|
||||
Ok(s) => s,
|
||||
Err(d) => return (None, vec![d]),
|
||||
};
|
||||
|
||||
// We create here a 'static guard. That's alright because we make sure
|
||||
// in this module that we only use erased component
|
||||
let guard = unsafe { generativity::Guard::new(generativity::Id::new()) };
|
||||
|
||||
let (c, diag) =
|
||||
crate::dynamic_component::load(source, path.into(), config.config, guard).await;
|
||||
(c.ok().map(|inner| Self { inner }), diag.into_iter().collect())
|
||||
}
|
||||
|
||||
/// Compile some .60 code into a ComponentDefinition
|
||||
///
|
||||
/// The `path` argument will be used for diagnostics and to compute relative
|
||||
/// path while importing
|
||||
///
|
||||
/// The first element of the returned tuple is going to be the compiled
|
||||
/// `ComponentDefinition` if there was no errors. This function also return
|
||||
/// a vector if diagnostics with errors and/or warnings.
|
||||
/// The [`print_diagnostics`] function can be used to display the diagnostics
|
||||
/// to the users.
|
||||
///
|
||||
/// This function is `async` but in practice, this is only asynchronious if
|
||||
/// [`CompilerConfiguration::with_file_loader`] is set and its future is actually asynchronious.
|
||||
/// If that is not used, then it is fine to use a very simple executor, such as the one
|
||||
/// provided by the `spin_on` crate
|
||||
pub async fn from_source(
|
||||
source_code: String,
|
||||
path: PathBuf,
|
||||
config: CompilerConfiguration,
|
||||
) -> (Option<Self>, Vec<Diagnostic>) {
|
||||
// We create here a 'static guard. That's alright because we make sure
|
||||
// in this module that we only use erased component
|
||||
let guard = unsafe { generativity::Guard::new(generativity::Id::new()) };
|
||||
|
||||
let (c, diag) =
|
||||
crate::dynamic_component::load(source_code, path, config.config, guard).await;
|
||||
(c.ok().map(|inner| Self { inner }), diag.into_iter().collect())
|
||||
}
|
||||
|
||||
/// Instantiate the component
|
||||
pub fn create(&self) -> ComponentInstance {
|
||||
ComponentInstance {
|
||||
|
@ -478,15 +535,16 @@ impl ComponentInstance {
|
|||
/// ## Examples
|
||||
///
|
||||
/// ```
|
||||
/// use sixtyfps_interpreter::{ComponentDefinition, CompilerConfiguration, Value, SharedString};
|
||||
/// use sixtyfps_interpreter::{ComponentDefinition, ComponentCompiler, Value, SharedString};
|
||||
/// let code = r#"
|
||||
/// MyWin := Window {
|
||||
/// property <int> my_property: 42;
|
||||
/// }
|
||||
/// "#;
|
||||
/// let (definition, diagnostics) = spin_on::spin_on(
|
||||
/// ComponentDefinition::from_source(code.into(), Default::default(), CompilerConfiguration::new()));
|
||||
/// assert!(diagnostics.is_empty(), "{:?}", diagnostics);
|
||||
/// let mut compiler = ComponentCompiler::new();
|
||||
/// let definition = spin_on::spin_on(
|
||||
/// compiler.build_from_source(code.into(), Default::default()));
|
||||
/// assert!(compiler.diagnostics().is_empty(), "{:?}", compiler.diagnostics());
|
||||
/// let instance = definition.unwrap().create();
|
||||
/// assert_eq!(instance.get_property("my_property").unwrap(), Value::from(42));
|
||||
/// ```
|
||||
|
@ -517,7 +575,7 @@ impl ComponentInstance {
|
|||
/// ## Examples
|
||||
///
|
||||
/// ```
|
||||
/// use sixtyfps_interpreter::{ComponentDefinition, CompilerConfiguration, Value, SharedString};
|
||||
/// use sixtyfps_interpreter::{ComponentDefinition, ComponentCompiler, Value, SharedString};
|
||||
/// use core::convert::TryInto;
|
||||
/// let code = r#"
|
||||
/// MyWin := Window {
|
||||
|
@ -525,8 +583,8 @@ impl ComponentInstance {
|
|||
/// property <int> my_prop: 12;
|
||||
/// }
|
||||
/// "#;
|
||||
/// let (definition, _) = spin_on::spin_on(
|
||||
/// ComponentDefinition::from_source(code.into(), Default::default(), CompilerConfiguration::new()));
|
||||
/// let definition = spin_on::spin_on(
|
||||
/// ComponentCompiler::new().build_from_source(code.into(), Default::default()));
|
||||
/// let instance = definition.unwrap().create();
|
||||
///
|
||||
/// let instance_weak = instance.as_weak();
|
||||
|
@ -704,7 +762,7 @@ impl CompilerConfiguration {
|
|||
) -> Self {
|
||||
let mut config = self.config;
|
||||
config.open_import_fallback =
|
||||
Some(Box::new(move |path| file_loader_fallback(Path::new(path.as_str()))));
|
||||
Some(Rc::new(move |path| file_loader_fallback(Path::new(path.as_str()))));
|
||||
Self { config }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,12 +30,13 @@ executor, such as the one provided by the `spin_on` crate
|
|||
This example load a `.60` dynamically from a path and show error if any
|
||||
|
||||
```rust
|
||||
use sixtyfps_interpreter::{ComponentDefinition, CompilerConfiguration};
|
||||
use sixtyfps_interpreter::{ComponentDefinition, ComponentCompiler, CompilerConfiguration};
|
||||
|
||||
let (definition, diagnostics) =
|
||||
spin_on::spin_on(ComponentDefinition::from_path("hello.60", CompilerConfiguration::new()));
|
||||
let mut compiler = ComponentCompiler::new();
|
||||
let definition =
|
||||
spin_on::spin_on(compiler.build_from_path("hello.60"));
|
||||
# #[cfg(feature="print_diagnostics")]
|
||||
sixtyfps_interpreter::print_diagnostics(&diagnostics);
|
||||
sixtyfps_interpreter::print_diagnostics(&compiler.diagnostics());
|
||||
if let Some(definition) = definition {
|
||||
let instance = definition.create();
|
||||
instance.run();
|
||||
|
@ -45,7 +46,7 @@ if let Some(definition) = definition {
|
|||
This example load a `.60` from a string and set some properties
|
||||
|
||||
```rust
|
||||
use sixtyfps_interpreter::{ComponentDefinition, CompilerConfiguration, Value, SharedString};
|
||||
use sixtyfps_interpreter::{ComponentDefinition, ComponentCompiler, Value, SharedString};
|
||||
|
||||
let code = r#"
|
||||
MyWin := Window {
|
||||
|
@ -56,9 +57,10 @@ let code = r#"
|
|||
}
|
||||
"#;
|
||||
|
||||
let (definition, diagnostics) =
|
||||
spin_on::spin_on(ComponentDefinition::from_source(code.into(), Default::default(), CompilerConfiguration::new()));
|
||||
assert!(diagnostics.is_empty());
|
||||
let mut compiler = ComponentCompiler::new();
|
||||
let definition =
|
||||
spin_on::spin_on(compiler.build_from_source(code.into(), Default::default()));
|
||||
assert!(compiler.diagnostics().is_empty());
|
||||
let instance = definition.unwrap().create();
|
||||
instance.set_property("my_name", Value::from(SharedString::from("World"))).unwrap();
|
||||
# return; // we don't want to call run in the tests
|
||||
|
|
|
@ -10,18 +10,14 @@ LICENSE END */
|
|||
|
||||
#[cfg(test)]
|
||||
fn do_test(snippet: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let config = sixtyfps_interpreter::CompilerConfiguration::new();
|
||||
let (component, diagnostics) =
|
||||
spin_on::spin_on(sixtyfps_interpreter::ComponentDefinition::from_source(
|
||||
snippet.into(),
|
||||
Default::default(),
|
||||
config,
|
||||
));
|
||||
let mut compiler = sixtyfps_interpreter::ComponentCompiler::new();
|
||||
let component =
|
||||
spin_on::spin_on(compiler.build_from_source(snippet.into(), Default::default()));
|
||||
|
||||
#[cfg(feature = "display-diagnostics")]
|
||||
sixtyfps_interpreter::print_diagnostics(&diagnostics);
|
||||
sixtyfps_interpreter::print_diagnostics(&compiler.diagnostics());
|
||||
|
||||
for d in diagnostics {
|
||||
for d in compiler.diagnostics() {
|
||||
if d.level() == sixtyfps_interpreter::DiagnosticLevel::Error {
|
||||
return Err(d.message().to_owned().into());
|
||||
}
|
||||
|
|
|
@ -15,20 +15,21 @@ pub fn test(testcase: &test_driver_lib::TestCase) -> Result<(), Box<dyn Error>>
|
|||
let include_paths = test_driver_lib::extract_include_paths(&source)
|
||||
.map(std::path::PathBuf::from)
|
||||
.collect::<Vec<_>>();
|
||||
let config =
|
||||
sixtyfps_interpreter::CompilerConfiguration::new().with_include_paths(include_paths);
|
||||
let mut compiler = sixtyfps_interpreter::ComponentCompiler::new();
|
||||
compiler.set_include_paths(include_paths);
|
||||
|
||||
let (component, diags) =
|
||||
spin_on::spin_on(sixtyfps_interpreter::ComponentDefinition::from_source(
|
||||
source,
|
||||
testcase.absolute_path.clone(),
|
||||
config,
|
||||
));
|
||||
let component =
|
||||
spin_on::spin_on(compiler.build_from_source(source, testcase.absolute_path.clone()));
|
||||
|
||||
let component = match component {
|
||||
None => {
|
||||
sixtyfps_interpreter::print_diagnostics(&diags);
|
||||
return Err(diags.into_iter().map(|d| d.to_string()).join("\n").into());
|
||||
sixtyfps_interpreter::print_diagnostics(&compiler.diagnostics());
|
||||
return Err(compiler
|
||||
.diagnostics()
|
||||
.into_iter()
|
||||
.map(|d| d.to_string())
|
||||
.join("\n")
|
||||
.into());
|
||||
}
|
||||
Some(c) => c,
|
||||
};
|
||||
|
|
|
@ -37,17 +37,14 @@ fn main() -> std::io::Result<()> {
|
|||
});
|
||||
});
|
||||
|
||||
let mut compiler_config =
|
||||
sixtyfps_interpreter::CompilerConfiguration::new().with_include_paths(args.include_paths);
|
||||
let mut compiler = sixtyfps_interpreter::ComponentCompiler::new();
|
||||
compiler.set_include_paths(args.include_paths);
|
||||
if !args.style.is_empty() {
|
||||
compiler_config = compiler_config.with_style(args.style);
|
||||
compiler.set_style(args.style);
|
||||
}
|
||||
|
||||
let (c, diags) = spin_on::spin_on(sixtyfps_interpreter::ComponentDefinition::from_path(
|
||||
args.path,
|
||||
compiler_config,
|
||||
));
|
||||
sixtyfps_interpreter::print_diagnostics(&diags);
|
||||
let c = spin_on::spin_on(compiler.build_from_path(args.path));
|
||||
sixtyfps_interpreter::print_diagnostics(&compiler.diagnostics());
|
||||
|
||||
let c = match c {
|
||||
Some(c) => c,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue