Prepare the compiler to be async

This will allow the online editor to load imports from URL asynchroniously later

Since currently the compiler is only working on a single thread, and that we
never await on a future that could block, it is allowed to use the spin_on executor
This commit is contained in:
Olivier Goffart 2020-10-30 11:18:27 +01:00
parent a7abfea961
commit 359f42c5f7
22 changed files with 110 additions and 89 deletions

View file

@ -24,6 +24,7 @@ sixtyfps-corelib = { version = "=0.0.2", path="../../../sixtyfps_runtime/corelib
scoped-tls-hkt = "0.1" scoped-tls-hkt = "0.1"
neon = "0.5.0" neon = "0.5.0"
css-color-parser2 = "1.0.1" css-color-parser2 = "1.0.1"
spin_on = "0.1" #FIXME: remove and delegate to async JS instead
[build-dependencies] [build-dependencies]
neon-build = "0.5.0" neon-build = "0.5.0"

View file

@ -63,12 +63,11 @@ fn load(mut cx: FunctionContext) -> JsResult<JsValue> {
} }
None => vec![], None => vec![],
}; };
let compiler_config = sixtyfps_compilerlib::CompilerConfiguration { let compiler_config =
include_paths: &include_paths, sixtyfps_compilerlib::CompilerConfiguration { include_paths, ..Default::default() };
..Default::default()
};
let source = std::fs::read_to_string(&path).or_else(|e| cx.throw_error(e.to_string()))?; let source = std::fs::read_to_string(&path).or_else(|e| cx.throw_error(e.to_string()))?;
let (c, warnings) = match sixtyfps_interpreter::load(source, &path, compiler_config) { let (c, warnings) =
match spin_on::spin_on(sixtyfps_interpreter::load(source, path.into(), compiler_config)) {
(Ok(c), warnings) => (c, warnings), (Ok(c), warnings) => (c, warnings),
(Err(()), errors) => { (Err(()), errors) => {
errors.print(); errors.print();

View file

@ -15,3 +15,4 @@ path = "lib.rs"
[dependencies] [dependencies]
sixtyfps-compilerlib = { version = "=0.0.2", path = "../../../sixtyfps_compiler", features = ["rust", "display-diagnostics"] } sixtyfps-compilerlib = { version = "=0.0.2", path = "../../../sixtyfps_compiler", features = ["rust", "display-diagnostics"] }
thiserror = "1" thiserror = "1"
spin_on = "0.1"

View file

@ -167,9 +167,14 @@ pub fn compile_with_config(
compiler_config.embed_resources = true; compiler_config.embed_resources = true;
} }
}; };
let embed_resources = compiler_config.embed_resources;
let (doc, mut diag) = // 'spin_on' is ok here because the compiler in single threaded and does not block if there is no blocking future
sixtyfps_compilerlib::compile_syntax_node(syntax_node, diag, &compiler_config); let (doc, mut diag) = spin_on::spin_on(sixtyfps_compilerlib::compile_syntax_node(
syntax_node,
diag,
compiler_config,
));
if diag.has_error() { if diag.has_error() {
let vec = diag.to_string_vec(); let vec = diag.to_string_vec();
@ -207,7 +212,7 @@ pub fn compile_with_config(
write!(code_formater, "{}", generated).map_err(CompileError::SaveError)?; write!(code_formater, "{}", generated).map_err(CompileError::SaveError)?;
println!("cargo:rerun-if-changed={}", path.display()); println!("cargo:rerun-if-changed={}", path.display());
if !compiler_config.embed_resources { if embed_resources {
for resource in doc.root_component.referenced_file_resources.borrow().keys() { for resource in doc.root_component.referenced_file_resources.borrow().keys() {
println!("cargo:rerun-if-changed={}", resource); println!("cargo:rerun-if-changed={}", resource);
} }

View file

@ -6,7 +6,7 @@
"use strict"; "use strict";
import * as sixtyfps from 'https://sixtyfps.io/wasm-interpreter/sixtyfps_wasm_interpreter.js'; import * as sixtyfps from 'https://sixtyfps.io/wasm-interpreter/sixtyfps_wasm_interpreter.js';
function render_or_error(source, div) { async function render_or_error(source, div) {
let canvas_id = 'canvas_' + Math.random().toString(36).substr(2, 9); let canvas_id = 'canvas_' + Math.random().toString(36).substr(2, 9);
let canvas = document.createElement("canvas"); let canvas = document.createElement("canvas");
canvas.width = 100; canvas.width = 100;
@ -14,11 +14,8 @@
canvas.id = canvas_id; canvas.id = canvas_id;
div.appendChild(canvas); div.appendChild(canvas);
try { try {
sixtyfps.instantiate_from_string(source, document.location.href, canvas_id); var compiled_component = await sixtyfps.compile_from_string(source, "");
} catch (e) { } catch (e) {
if (e.message === "Using exceptions for control flow, don't mind me. This isn't actually an error!") {
throw e;
}
var text = document.createTextNode(e.message); var text = document.createTextNode(e.message);
var p = document.createElement('pre'); var p = document.createElement('pre');
p.appendChild(text); p.appendChild(text);
@ -26,6 +23,7 @@
throw e; throw e;
} }
compiled_component.run(canvas_id);
} }
async function run() { async function run() {

View file

@ -17,4 +17,5 @@ path = "lib.rs"
quote = "1.0" quote = "1.0"
proc-macro2 = "1.0.17" proc-macro2 = "1.0.17"
sixtyfps-compilerlib = { version = "=0.0.2", path = "../../../sixtyfps_compiler", features = ["proc_macro_span", "rust"] } sixtyfps-compilerlib = { version = "=0.0.2", path = "../../../sixtyfps_compiler", features = ["proc_macro_span", "rust"] }
spin_on = "0.1"

View file

@ -331,9 +331,9 @@ pub fn sixtyfps(stream: TokenStream) -> TokenStream {
let syntax_node = parser::SyntaxNodeWithSourceFile { node: syntax_node, source_file }; let syntax_node = parser::SyntaxNodeWithSourceFile { node: syntax_node, source_file };
//println!("{:#?}", syntax_node); //println!("{:#?}", syntax_node);
let compiler_config = let compiler_config = CompilerConfiguration { include_paths, ..Default::default() };
CompilerConfiguration { include_paths: &include_paths, ..Default::default() }; let (root_component, mut diag) =
let (root_component, mut diag) = compile_syntax_node(syntax_node, diag, &compiler_config); spin_on::spin_on(compile_syntax_node(syntax_node, diag, compiler_config));
//println!("{:#?}", tree); //println!("{:#?}", tree);
if diag.has_error() { if diag.has_error() {
diag.map_offsets_to_span(&tokens); diag.map_offsets_to_span(&tokens);

View file

@ -14,7 +14,8 @@ crate-type = ["cdylib"]
[dependencies] [dependencies]
sixtyfps-interpreter = { path = "../../sixtyfps_runtime/interpreter" } sixtyfps-interpreter = { path = "../../sixtyfps_runtime/interpreter" }
wasm-bindgen = { version = "0.2.67" } wasm-bindgen = { version = "0.2.66" }
wasm-bindgen-futures = { version = "0.4.18" }
js-sys = "0.3.44" js-sys = "0.3.44"
console_error_panic_hook = { version = "0.1.6", optional = true } console_error_panic_hook = { version = "0.1.6", optional = true }
wee_alloc = { version = "0.4.5", optional = true } wee_alloc = { version = "0.4.5", optional = true }

View file

@ -10,29 +10,25 @@ LICENSE END */
//! This wasm library can be loaded from JS to load and display the content of .60 files //! This wasm library can be loaded from JS to load and display the content of .60 files
#![cfg(target_arch = "wasm32")] #![cfg(target_arch = "wasm32")]
use std::rc::Rc;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
#[cfg(feature = "wee_alloc")] #[cfg(feature = "wee_alloc")]
#[global_allocator] #[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
/// Compile and display the content of a string. /// Compile the content of a string.
/// The HTML must contains a <canvas> element with the given `canvas_id` ///
/// where the result is gonna be rendered /// Returns a promise to a compiled component which can be run with ".run()"
#[wasm_bindgen] #[wasm_bindgen]
pub fn instantiate_from_string( pub async fn compile_from_string(
source: &str, source: String,
base_url: &str, base_url: String,
canvas_id: String, ) -> Result<WrappedCompiledComp, JsValue> {
) -> Result<(), JsValue> {
#[cfg(feature = "console_error_panic_hook")] #[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once(); console_error_panic_hook::set_once();
let c = match sixtyfps_interpreter::load( let c = match sixtyfps_interpreter::load(source, base_url.into(), Default::default()).await {
source.to_owned(),
&std::path::Path::new(base_url),
Default::default(),
) {
(Ok(c), ..) => { (Ok(c), ..) => {
//TODO: warnings.print(); //TODO: warnings.print();
c c
@ -84,7 +80,20 @@ pub fn instantiate_from_string(
} }
}; };
let component = c.clone().create(canvas_id); Ok(WrappedCompiledComp(c))
component.window().run(component.borrow(), component.root_item()); }
Ok(())
#[wasm_bindgen]
pub struct WrappedCompiledComp(Rc<sixtyfps_interpreter::ComponentDescription>);
#[wasm_bindgen]
impl WrappedCompiledComp {
/// Run this compiled component in a canvas.
/// The HTML must contains a <canvas> element with the given `canvas_id`
/// where the result is gonna be rendered
#[wasm_bindgen]
pub fn run(&self, canvas_id: String) {
let component = self.0.clone().create(canvas_id);
component.window().run(component.borrow(), component.root_item());
}
} }

View file

@ -43,5 +43,6 @@ once_cell = "1"
[dev-dependencies] [dev-dependencies]
regex = "1.3.7" regex = "1.3.7"
parser_test_macro = { path = "./parser_test_macro" } parser_test_macro = { path = "./parser_test_macro" }
spin_on = "0.1"

View file

@ -55,20 +55,20 @@ mod passes {
#[derive(Default)] #[derive(Default)]
/// CompilationConfiguration allows configuring different aspects of the compiler. /// CompilationConfiguration allows configuring different aspects of the compiler.
pub struct CompilerConfiguration<'a> { pub struct CompilerConfiguration {
/// Indicate whether to embed resources such as images in the generated output or whether /// Indicate whether to embed resources such as images in the generated output or whether
/// to retain references to the resources on the file system. /// to retain references to the resources on the file system.
pub embed_resources: bool, pub embed_resources: bool,
/// The compiler will look in these paths for components used in the file to compile. /// The compiler will look in these paths for components used in the file to compile.
pub include_paths: &'a [std::path::PathBuf], pub include_paths: Vec<std::path::PathBuf>,
/// the name of the style. (eg: "native") /// the name of the style. (eg: "native")
pub style: Option<&'a str>, pub style: Option<String>,
} }
pub fn compile_syntax_node( pub async fn compile_syntax_node(
doc_node: parser::SyntaxNodeWithSourceFile, doc_node: parser::SyntaxNodeWithSourceFile,
mut diagnostics: diagnostics::FileDiagnostics, mut diagnostics: diagnostics::FileDiagnostics,
compiler_config: &CompilerConfiguration, compiler_config: CompilerConfiguration,
) -> (object_tree::Document, diagnostics::BuildDiagnostics) { ) -> (object_tree::Document, diagnostics::BuildDiagnostics) {
let mut build_diagnostics = diagnostics::BuildDiagnostics::default(); let mut build_diagnostics = diagnostics::BuildDiagnostics::default();
@ -80,6 +80,7 @@ pub fn compile_syntax_node(
let style = compiler_config let style = compiler_config
.style .style
.as_ref()
.map(Cow::from) .map(Cow::from)
.or_else(|| std::env::var("SIXTYFPS_STYLE").map(Cow::from).ok()) .or_else(|| std::env::var("SIXTYFPS_STYLE").map(Cow::from).ok())
.unwrap_or_else(|| { .unwrap_or_else(|| {
@ -97,13 +98,14 @@ pub fn compile_syntax_node(
let mut all_docs = typeloader::LoadedDocuments::default(); let mut all_docs = typeloader::LoadedDocuments::default();
if doc_node.source_file.is_some() { if doc_node.source_file.is_some() {
let builtin_lib = library::widget_library().iter().find(|x| x.0 == style).map(|x| x.1);
typeloader::load_dependencies_recursively( typeloader::load_dependencies_recursively(
&doc_node, &doc_node,
&mut diagnostics, &mut diagnostics,
&type_registry, &type_registry,
&global_type_registry, &global_type_registry,
&compiler_config, &compiler_config,
library::widget_library().iter().find(|x| x.0 == style).map(|x| x.1), builtin_lib,
&mut all_docs, &mut all_docs,
&mut build_diagnostics, &mut build_diagnostics,
); );
@ -115,7 +117,7 @@ pub fn compile_syntax_node(
if !build_diagnostics.has_error() { if !build_diagnostics.has_error() {
// FIXME: ideally we would be able to run more passes, but currently we panic because invariant are not met. // FIXME: ideally we would be able to run more passes, but currently we panic because invariant are not met.
run_passes(&doc, &mut build_diagnostics, compiler_config); run_passes(&doc, &mut build_diagnostics, &compiler_config);
} }
(doc, build_diagnostics) (doc, build_diagnostics)

View file

@ -140,14 +140,14 @@ fn process_file_source(
sixtyfps_compilerlib::parser::parse(source.clone(), Some(path)); sixtyfps_compilerlib::parser::parse(source.clone(), Some(path));
let compile_diagnostics = if !parse_diagnostics.has_error() { let compile_diagnostics = if !parse_diagnostics.has_error() {
let compiler_config = sixtyfps_compilerlib::CompilerConfiguration { let compiler_config = sixtyfps_compilerlib::CompilerConfiguration {
style: "ugly".into(), style: Some("ugly".into()),
..Default::default() ..Default::default()
}; };
let (_, build_diags) = sixtyfps_compilerlib::compile_syntax_node( let (_, build_diags) = spin_on::spin_on(sixtyfps_compilerlib::compile_syntax_node(
syntax_node, syntax_node,
parse_diagnostics, parse_diagnostics,
&compiler_config, compiler_config,
); ));
build_diags.into_iter().collect() build_diags.into_iter().collect()
} else { } else {
vec![parse_diagnostics] vec![parse_diagnostics]

View file

@ -433,20 +433,20 @@ fn rtti_for_flickable() -> (&'static str, Rc<ItemRTTI>) {
/// Create a ComponentDescription from a source. /// Create a ComponentDescription from a source.
/// The path corresponding to the source need to be passed as well (path is used for diagnostics /// The path corresponding to the source need to be passed as well (path is used for diagnostics
/// and loading relative assets) /// and loading relative assets)
pub fn load<'id>( pub async fn load<'id>(
source: String, source: String,
path: &std::path::Path, path: std::path::PathBuf,
compiler_config: &CompilerConfiguration, compiler_config: CompilerConfiguration,
guard: generativity::Guard<'id>, guard: generativity::Guard<'id>,
) -> (Result<Rc<ComponentDescription<'id>>, ()>, sixtyfps_compilerlib::diagnostics::BuildDiagnostics) ) -> (Result<Rc<ComponentDescription<'id>>, ()>, sixtyfps_compilerlib::diagnostics::BuildDiagnostics)
{ {
let (syntax_node, diag) = parser::parse(source, Some(path)); let (syntax_node, diag) = parser::parse(source, Some(path.as_path()));
if diag.has_error() { if diag.has_error() {
let mut d = sixtyfps_compilerlib::diagnostics::BuildDiagnostics::default(); let mut d = sixtyfps_compilerlib::diagnostics::BuildDiagnostics::default();
d.add(diag); d.add(diag);
return (Err(()), d); return (Err(()), d);
} }
let (doc, diag) = compile_syntax_node(syntax_node, diag, &compiler_config); let (doc, diag) = compile_syntax_node(syntax_node, diag, compiler_config).await;
if diag.has_error() { if diag.has_error() {
return (Err(()), diag); return (Err(()), diag);
} }

View file

@ -175,20 +175,21 @@ impl<'id> dynamic_component::ComponentDescription<'id> {
pub type ComponentDescription = dynamic_component::ComponentDescription<'static>; pub type ComponentDescription = dynamic_component::ComponentDescription<'static>;
pub type ComponentBox = dynamic_component::ComponentBox<'static>; pub type ComponentBox = dynamic_component::ComponentBox<'static>;
pub fn load( pub async fn load(
source: String, source: String,
path: &std::path::Path, path: std::path::PathBuf,
mut compiler_config: CompilerConfiguration, mut compiler_config: CompilerConfiguration,
) -> (Result<Rc<ComponentDescription>, ()>, sixtyfps_compilerlib::diagnostics::BuildDiagnostics) { ) -> (Result<Rc<ComponentDescription>, ()>, sixtyfps_compilerlib::diagnostics::BuildDiagnostics) {
if compiler_config.style.is_none() && std::env::var("SIXTYFPS_STYLE").is_err() { if compiler_config.style.is_none() && std::env::var("SIXTYFPS_STYLE").is_err() {
// Defaults to native if it exists: // Defaults to native if it exists:
compiler_config.style = Some(if sixtyfps_rendering_backend_default::HAS_NATIVE_STYLE { compiler_config.style = Some(if sixtyfps_rendering_backend_default::HAS_NATIVE_STYLE {
"native" "native".to_owned()
} else { } else {
"ugly" "ugly".to_owned()
}); });
} }
dynamic_component::load(source, path, &compiler_config, unsafe { dynamic_component::load(source, path, compiler_config, unsafe {
generativity::Guard::new(generativity::Id::new()) generativity::Guard::new(generativity::Id::new())
}) })
.await
} }

View file

@ -19,6 +19,7 @@ scopeguard = "1.1.0"
test_driver_lib = { path = "../driver_lib" } test_driver_lib = { path = "../driver_lib" }
lazy_static = "1.4.0" lazy_static = "1.4.0"
which = "4.0.2" which = "4.0.2"
spin_on = "0.1"
[build-dependencies] [build-dependencies]
test_driver_lib = { path = "../driver_lib" } test_driver_lib = { path = "../driver_lib" }

View file

@ -15,13 +15,14 @@ use std::ops::Deref;
pub fn test(testcase: &test_driver_lib::TestCase) -> Result<(), Box<dyn Error>> { pub fn test(testcase: &test_driver_lib::TestCase) -> Result<(), Box<dyn Error>> {
let source = std::fs::read_to_string(&testcase.absolute_path)?; let source = std::fs::read_to_string(&testcase.absolute_path)?;
let include_paths = &test_driver_lib::extract_include_paths(&source) let include_paths = test_driver_lib::extract_include_paths(&source)
.map(std::path::PathBuf::from) .map(std::path::PathBuf::from)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let (syntax_node, diag) = parser::parse(source.clone(), Some(&testcase.absolute_path)); let (syntax_node, diag) = parser::parse(source.clone(), Some(&testcase.absolute_path));
let compiler_config = CompilerConfiguration { include_paths, ..Default::default() }; let compiler_config = CompilerConfiguration { include_paths, ..Default::default() };
let (root_component, mut diag) = compile_syntax_node(syntax_node, diag, &compiler_config); let (root_component, mut diag) =
spin_on::spin_on(compile_syntax_node(syntax_node, diag, compiler_config));
if diag.has_error() { if diag.has_error() {
let vec = diag.to_string_vec(); let vec = diag.to_string_vec();

View file

@ -12,16 +12,17 @@ use std::error::Error;
pub fn test(testcase: &test_driver_lib::TestCase) -> Result<(), Box<dyn Error>> { pub fn test(testcase: &test_driver_lib::TestCase) -> Result<(), Box<dyn Error>> {
let source = std::fs::read_to_string(&testcase.absolute_path)?; let source = std::fs::read_to_string(&testcase.absolute_path)?;
let include_paths = &test_driver_lib::extract_include_paths(&source) let include_paths = test_driver_lib::extract_include_paths(&source)
.map(std::path::PathBuf::from) .map(std::path::PathBuf::from)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let config = sixtyfps_compilerlib::CompilerConfiguration { let config =
include_paths: &include_paths, sixtyfps_compilerlib::CompilerConfiguration { include_paths, ..Default::default() };
..Default::default()
};
let (component, _warnings) = let (component, _warnings) = match spin_on::spin_on(sixtyfps_interpreter::load(
match sixtyfps_interpreter::load(source, &testcase.absolute_path, config) { source,
testcase.absolute_path.clone(),
config,
)) {
(Ok(c), diagnostics) => (c, diagnostics), (Ok(c), diagnostics) => (c, diagnostics),
(Err(()), errors) => { (Err(()), errors) => {
let vec = errors.to_string_vec(); let vec = errors.to_string_vec();

View file

@ -15,3 +15,4 @@ path = "main.rs"
[dependencies] [dependencies]
sixtyfps-compilerlib = { version = "=0.0.2", path = "../../sixtyfps_compiler", features = ["display-diagnostics", "cpp", "rust"]} sixtyfps-compilerlib = { version = "=0.0.2", path = "../../sixtyfps_compiler", features = ["display-diagnostics", "cpp", "rust"]}
structopt = "0.3.14" structopt = "0.3.14"
spin_on = "0.1"

View file

@ -32,8 +32,8 @@ fn main() -> std::io::Result<()> {
std::process::exit(-1); std::process::exit(-1);
} }
let compiler_config = let compiler_config =
CompilerConfiguration { include_paths: &args.include_paths, ..Default::default() }; CompilerConfiguration { include_paths: args.include_paths, ..Default::default() };
let (doc, diag) = compile_syntax_node(syntax_node, diag, &compiler_config); let (doc, diag) = spin_on::spin_on(compile_syntax_node(syntax_node, diag, compiler_config));
let mut diag = diag.check_and_exit_on_error(); let mut diag = diag.check_and_exit_on_error();

View file

@ -77,7 +77,7 @@ function update() {
} }
function render_or_error(source, base_url, div) { async function render_or_error(source, base_url, div) {
let canvas_id = 'canvas_' + Math.random().toString(36).substr(2, 9); let canvas_id = 'canvas_' + Math.random().toString(36).substr(2, 9);
let canvas = document.createElement("canvas"); let canvas = document.createElement("canvas");
canvas.width = 800; canvas.width = 800;
@ -86,14 +86,10 @@ function render_or_error(source, base_url, div) {
div.innerHTML = ""; div.innerHTML = "";
div.appendChild(canvas); div.appendChild(canvas);
try { try {
sixtyfps.instantiate_from_string(source, base_url, canvas_id); var compiled_component = await sixtyfps.compile_from_string(source, base_url);
} catch (e) { } catch (e) {
if (e.message === "Using exceptions for control flow, don't mind me. This isn't actually an error!") { let text = document.createTextNode(e.message);
monaco.editor.setModelMarkers(editor.getModel(), "sixtyfps", []); let p = document.createElement('pre');
throw e;
}
var text = document.createTextNode(e.message);
var p = document.createElement('pre');
p.appendChild(text); p.appendChild(text);
div.innerHTML = "<pre style='color: red; background-color:#fee; margin:0'>" + p.innerHTML + "</pre>"; div.innerHTML = "<pre style='color: red; background-color:#fee; margin:0'>" + p.innerHTML + "</pre>";
@ -114,6 +110,7 @@ function render_or_error(source, base_url, div) {
throw e; throw e;
} }
compiled_component.run(canvas_id)
} }
let keystorke_timeout_handle; let keystorke_timeout_handle;

View file

@ -16,6 +16,7 @@ vtable = { version = "0.1", path="../../helper_crates/vtable" }
structopt = "0.3.14" structopt = "0.3.14"
codemap-diagnostic = "0.1.1" codemap-diagnostic = "0.1.1"
codemap = "0.1" codemap = "0.1"
spin_on = "0.1"
[[bin]] [[bin]]

View file

@ -27,12 +27,12 @@ fn main() -> std::io::Result<()> {
let source = std::fs::read_to_string(&args.path)?; let source = std::fs::read_to_string(&args.path)?;
let compiler_config = sixtyfps_compilerlib::CompilerConfiguration { let compiler_config = sixtyfps_compilerlib::CompilerConfiguration {
include_paths: &args.include_paths, include_paths: args.include_paths,
style: if args.style.is_empty() { None } else { Some(&args.style) }, style: if args.style.is_empty() { None } else { Some(args.style) },
..Default::default() ..Default::default()
}; };
let c = match sixtyfps_interpreter::load(source, &args.path, compiler_config) { let c = match spin_on::spin_on(sixtyfps_interpreter::load(source, args.path, compiler_config)) {
(Ok(c), warnings) => { (Ok(c), warnings) => {
warnings.print(); warnings.print();
c c