{
self.0.get(path.path()).map(RPathBuf::path_to_string)
}
/// `LookupPreloadResolver` only suggests including GDLisp source
/// files, i.e. [`ResourceType::GDLispSource`]. All other resource
/// types result in false.
fn include_resource(&self, res: ResourceType) -> bool {
res == ResourceType::GDLispSource
}
}
================================================
FILE: src/compile/resource_type.rs
================================================
// Copyright 2023 Silvio Mayolo
//
// This file is part of GDLisp.
//
// GDLisp is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GDLisp is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GDLisp. If not, see .
//! The various types of resources GDLisp might encounter.
//!
//! [`ResourceType`] enumerates the possible resource types GDLisp
//! might attempt to load.
use crate::ir::import::{ImportDecl, ImportDetails};
use crate::pipeline::Pipeline;
use super::error::{GDError, GDErrorF};
use std::convert::AsRef;
use std::path::Path;
use std::ffi::OsStr;
/// The various resource types a `use` statement might encounter.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ResourceType {
/// A GDLisp source file. Must be compiled to GDScript. GDLisp
/// source files support the full breadth of import declaration
/// types.
GDLispSource,
/// A GDScript source file. The file must exist, and the import must
/// be a simple import or an aliased import. Open or restricted
/// imports are not allowed.
GDScriptSource,
/// A packed scene file. The file must exist, and the import must be
/// a simple import or an aliased import. Open or restricted imports
/// are not allowed.
PackedScene,
/// Any other resource file. The file must exist, and the import
/// must be a simple import or an aliased import. Open or restricted
/// imports are not allowed.
Miscellaneous,
}
impl ResourceType {
/// Constructs a [`ResourceType`] for the type of resource at the
/// given path. The file at `path` will not be read; all resource
/// type inference is done by looking at the file name, specifically
/// its extension.
pub fn from_path + ?Sized>(path: &P) -> ResourceType {
ResourceType::from_file_extension(&path.as_ref().extension().unwrap_or_else(|| OsStr::new("")))
}
/// Returns the appropriate resource type given a file extension.
/// `ext` should be an all-lowercase file extension excluding the
/// initial dot. If the extension is not recognized, then
/// [`ResourceType::Miscellaneous`] is returned.
pub fn from_file_extension + ?Sized>(ext: &S) -> ResourceType {
let ext = ext.as_ref();
if ext == "lisp" {
ResourceType::GDLispSource
} else if ext == "gd" {
ResourceType::GDScriptSource
} else if ext == "tscn" {
ResourceType::PackedScene
} else {
ResourceType::Miscellaneous
}
}
/// Whether or not the resource type can have macros defined in it.
/// Only [`ResourceType::GDLispSource`] can have macros.
pub fn can_have_macros(&self) -> bool {
*self == ResourceType::GDLispSource
}
/// Whether the given import declaration is allowed for this
/// particular resource type.
///
/// GDLisp source files allow all import types, whereas other
/// resource types are restricted to simple or aliased imports.
pub fn is_import_allowed(&self, import: &ImportDecl) -> bool {
if *self == ResourceType::GDLispSource {
true // GDLispSource allows all imports
} else {
// For other resources, it must be ImportDetails::Named
matches!(import.details, ImportDetails::Named(_))
}
}
/// Checks [`ResourceType::is_import_allowed`]. If it is false, this
/// method issues an appropriate error via `Err`. Otherwise, returns
/// `Ok(())`.
pub fn check_import(_pipeline: &Pipeline, import: &ImportDecl) -> Result<(), GDError> {
// if !pipeline.file_exists(import.filename.path()) {
// return Err(GDError::ResourceDoesNotExist(import.filename.to_string()));
// }
let res_type = ResourceType::from_path(import.filename.path());
if !res_type.is_import_allowed(import) {
return Err(GDError::new(GDErrorF::InvalidImportOnResource(import.filename.to_string()), import.pos));
}
Ok(())
}
}
/// Construct a `ResourceType` from a reference to a path. Delegates
/// to [`ResourceType::from_path`].
impl From<&Path> for ResourceType {
fn from(path: &Path) -> ResourceType {
ResourceType::from_path(path)
}
}
/// Construct a `ResourceType` from the path referenced by the import
/// declaration.
impl From<&ImportDecl> for ResourceType {
fn from(imp: &ImportDecl) -> ResourceType {
ResourceType::from_path(imp.filename.path())
}
}
================================================
FILE: src/compile/special_form/closure.rs
================================================
// Copyright 2023 Silvio Mayolo
//
// This file is part of GDLisp.
//
// GDLisp is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GDLisp is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GDLisp. If not, see .
//! Helpers for generating closures, for use in constructs such as
//! lambdas and lambda classes.
use crate::ir::expr::{Locals, Functions, LambdaClass, LocalFnClause};
use crate::compile::symbol_table::function_call::FnCall;
use crate::compile::symbol_table::local_var::VarScope;
use crate::compile::symbol_table::SymbolTable;
use crate::pipeline::source::SourceOffset;
type IRArgList = crate::ir::arglist::ordinary::ArgList;
type IRExpr = crate::ir::expr::Expr;
/// A `ClosureData` contains information about collections of
/// variables and functions to close over.
#[derive(PartialEq, Eq, Debug, Clone)]
pub struct ClosureData {
/// The collection of all local variables in-scope which need to be
/// closed over.
pub closure_vars: Locals,
/// The collection of all local variables introduced in the
/// closure's scope or a strictly larger one. This is similar to
/// `closed_vars` and will always be a superset of that collection,
/// but this collection also includes variables which are introduced
/// as part of the closure's scope, most commonly arguments to the
/// lambda which created the closure. Such variables do not need to
/// be closed over (since they do not exist outside the closure),
/// but it is still meaningful to ask what their `access_type` is
/// and whether they need a GDLisp cell.
pub all_vars: Locals,
/// The collection of all local functions in-scope which need to be
/// closed over.
pub closure_fns: Functions,
}
/// A function consists of an argument list and a body expression.
/// This simple wrapper couples the two, so that we can pass them as a
/// pair to [`ClosureData`] methods.
#[derive(Clone, Debug)]
pub struct Function<'a, 'b> {
pub args: &'a IRArgList,
pub body: &'b IRExpr,
}
/// A simple wrapper around a collection of [`LocalFnClause`]. A
/// `labels` SCC is a collection of interconnected local function
/// clauses, which reference each other in a circular fashion. More
/// generally, this structure can be used for any collection of local
/// function clauses, regardless of referencing requirements.
#[repr(transparent)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct LabelsComponent<'a, 'b>(pub &'a [&'b LocalFnClause]);
impl ClosureData {
/// Purges globals from `self.closure_vars`, as though via the
/// module-level function [`purge_globals`].
///
/// Equivalent to `purge_globals(&mut self.closure_vars, table)`.
pub fn purge_globals(&mut self, table: &SymbolTable) {
purge_globals(&mut self.closure_vars, table)
}
/// Assuming `self` contains all of the relevant closure information
/// on the GDLisp side, this function constructs a list of the
/// necessary variables which need to be looked up on the GDScript
/// side to construct or utilize the closure.
///
/// Every variable in `self.closure_vars` and every function in
/// `self.closure_fns` will be considered for inclusion in the
/// result. Specifically, each variable will be looked up in `table`
/// and, if the result has a
/// [`LocalVar::simple_name`](crate::compile::symbol_table::local_var::LocalVar::simple_name),
/// then it will be included in the returned translation vector.
/// Likewise, each function is looked up in `table` and, if
/// [`closure_fn_to_gd_var`] returns a name, then that name is added
/// to the translation vector.
///
/// `self.all_vars` is not considered during this calculation.
///
/// This function can be used in two similar ways. By passing the
/// lambda symbol table as `table` to this function, the resulting
/// vector will contain the GDScript names used to refer to the
/// closure variables from *within* the closure. This is useful for
/// building the lambda's constructor function. By passing the
/// enclosing outer table as `table`, on the other hand, the
/// resulting vector will contain the names used to refer to the
/// closure variables from the scope *surrounding* the closure. This
/// is useful for building the expression that will *call* the
/// lambda's constructor function.
///
/// # Panics
///
/// It is a precondition of this function that every name in
/// `self.closure_vars` and in `self.closure_fns` appears in `table`
/// under the appropriate namespace. If any names are missing, then
/// this function will panic.
pub fn to_gd_closure_vars(&self, table: &SymbolTable) -> Vec {
let mut gd_closure_vars = Vec::new();
// Get closure variables
for lisp_name in self.closure_vars.names() {
let var = table.get_var(lisp_name).unwrap_or_else(|| {
panic!("Internal error compiling lambda variable {}", lisp_name);
});
if let Some(name) = var.simple_name() {
gd_closure_vars.push(name.to_owned());
}
}
// Get closure functions (functions also get moved to the variable
// namespace when closed around, since GDScript doesn't treat
// function names as first-class objects)
for lisp_name in self.closure_fns.names() {
let (call, _) = table.get_fn(lisp_name).unwrap_or_else(|| {
panic!("Internal error compiling lambda variable {}", lisp_name);
});
if let Some(var) = closure_fn_to_gd_var(call) {
gd_closure_vars.push(var);
}
}
gd_closure_vars
}
}
impl<'a, 'b> Function<'a, 'b> {
/// Convenience function to construct a new `Function`.
pub fn new(args: &'a IRArgList, body: &'b IRExpr) -> Self {
Function { args, body }
}
}
impl<'a, 'b> From> for ClosureData {
/// If we're constructing a simple lambda function, we can convert
/// its [`Function`] value into a [`ClosureData`] in a well-defined
/// way.
fn from(function: Function<'a, 'b>) -> ClosureData {
let (all_vars, closure_fns) = function.body.get_names();
let mut closure_vars = all_vars.clone();
for arg in function.args.iter_vars() {
closure_vars.remove(arg);
}
ClosureData { closure_vars, all_vars, closure_fns }
}
}
impl<'a> From<&'a LambdaClass> for ClosureData {
/// A lambda class involves closing around any variables inside of
/// it, similar to a lambda function. The lambda class case is
/// somewhat more complex, as it consists of multiple functions and,
/// in general, a `self` variable that gets implicitly overwritten
/// by the class' `self`.
fn from(class: &'a LambdaClass) -> ClosureData {
let (mut closure_vars, mut closure_fns) = class.constructor_or_default(SourceOffset::from(0)).get_names();
for d in &class.decls {
let (decl_vars, decl_fns) = d.get_names();
closure_vars.merge_with(decl_vars);
closure_fns.merge_with(decl_fns);
}
closure_vars.remove("self"); // Don't close around self; we get a new self
ClosureData { closure_vars: closure_vars.clone(), all_vars: closure_vars, closure_fns }
}
}
impl<'a, 'b> From> for ClosureData {
/// A strongly-connected component (SCC) of a `labels` clause is a
/// collection of interconnected local function clauses. Those
/// function clauses each have their own closure, and together they
/// have a common closure for the object which encapsulates them.
///
/// Note that all of the function names in the SCC are in scope for
/// the duration of all of the function bodies, so the function
/// names themselves will never appear in the resulting
/// `closure_fns`.
fn from(comp: LabelsComponent<'a, 'b>) -> ClosureData {
let LabelsComponent(clauses) = comp;
let mut closure_vars = Locals::new();
let mut closure_fns = Functions::new();
let mut all_vars = Locals::new();
for clause in clauses {
let (mut inner_vars, inner_fns) = clause.body.get_names();
all_vars.merge_with(inner_vars.clone());
for arg in clause.args.iter_vars() {
inner_vars.remove(arg);
}
closure_vars.merge_with(inner_vars);
closure_fns.merge_with(inner_fns);
}
// Function names are in scope for the duration of their own bodies
for clause in clauses {
closure_fns.remove(&clause.name);
}
ClosureData { closure_vars, all_vars, closure_fns }
}
}
/// Removes all of the variables from `vars` whose scope (according to
/// the corresponding entry in `table`) is
/// [`VarScope::GlobalVar`](crate::compile::symbol_table::local_var::VarScope::GlobalVar).
///
/// Lambdas are lifted to the file-level scope. A variable with scope
/// `VarScope::GlobalVar` is defined either at the file-level scope or
/// as a superglobal. In either case, the lambda will still have a
/// reference to the variable without any help, so we don't need to
/// close around those variables. This function removes from `vars`
/// the variables which it is unnecessary to explicitly create
/// closures around.
pub fn purge_globals(vars: &mut Locals, table: &SymbolTable) {
vars.retain(|var, _| {
table.get_var(var).map_or(true, |v| v.scope != VarScope::GlobalVar)
});
}
/// If the function call comes from a local variable (most commonly,
/// if it was built out of `flet` or `labels`), then this function
/// returns the name of that variable, as a `String`. If the function
/// does *not* come from a local variable, then this function returns
/// `None`.
pub fn closure_fn_to_gd_var(call: &FnCall) -> Option {
call.scope.local_name().map(str::to_owned)
}
================================================
FILE: src/compile/special_form/flet.rs
================================================
// Copyright 2023 Silvio Mayolo
//
// This file is part of GDLisp.
//
// GDLisp is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GDLisp is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GDLisp. If not, see .
use crate::ir;
use crate::ir::expr::LocalFnClause;
use crate::compile::body::builder::StmtBuilder;
use crate::compile::symbol_table::{HasSymbolTable, SymbolTable};
use crate::compile::symbol_table::function_call::{FnCall, FnSpecs, FnScope, FnName};
use crate::compile::symbol_table::local_var::VarName;
use crate::compile::error::GDError;
use crate::compile::stateful::{StExpr, NeedsResult};
use crate::compile::stmt_wrapper;
use crate::compile::factory;
use crate::compile::frame::CompilerFrame;
use crate::compile::names::generator::NameGenerator;
use crate::compile::names::registered::RegisteredNameGenerator;
use crate::gdscript::decl::{self, Decl, DeclF};
use crate::graph::Graph;
use crate::graph::top_sort::top_sort;
use crate::graph::tarjan;
use crate::pipeline::source::SourceOffset;
use super::lambda;
use std::convert::AsRef;
type IRExpr = ir::expr::Expr;
type IRArgList = ir::arglist::ordinary::ArgList;
pub fn compile_flet(frame: &mut CompilerFrame,
clauses: &[LocalFnClause],
body: &IRExpr,
needs_result: NeedsResult,
minimalist: bool,
pos: SourceOffset)
-> Result {
let local_fns = clauses.iter().map(|clause| {
let call = compile_flet_call(frame, clause.args.to_owned(), &clause.body, minimalist, pos)?;
Ok((clause.name.to_owned(), call))
}).collect::, GDError>>()?;
frame.with_local_fns(&mut local_fns.into_iter(), |frame| {
frame.compile_expr(body, needs_result)
})
}
fn compile_flet_call(frame: &mut CompilerFrame,
args: IRArgList,
body: &IRExpr,
minimalist: bool,
pos: SourceOffset)
-> Result {
if is_declaration_semiglobal(&args, body, frame.table) {
// No closure vars and any closure fns (if there are any) are
// free of closures, so we can compile to SemiGlobal.
let gd_name = RegisteredNameGenerator::new_fn(frame.table).generate_with("_flet");
let func = factory::declare_function(frame, gd_name.clone(), args.clone(), body, &stmt_wrapper::Return)?;
frame.builder.add_helper(Decl::new(DeclF::FnDecl(decl::Static::IsStatic, func), pos));
let specs = FnSpecs::from(args);
Ok(FnCall {
scope: FnScope::SemiGlobal,
object: FnName::FileConstant,
function: gd_name,
specs,
is_macro: false,
})
} else {
// Have to make a full closure object.
let stmt = lambda::compile_lambda_stmt(frame, &args, body, minimalist, pos)?.expr;
let local_name = factory::declare_var(&mut RegisteredNameGenerator::new_local_var(frame.table), frame.builder, "_flet", Some(stmt), pos);
let specs = FnSpecs::from(args);
Ok(FnCall {
scope: FnScope::Local(local_name.clone()),
object: FnName::on_local_var(VarName::Local(local_name)),
function: "call_func".to_owned(),
specs,
is_macro: false,
})
}
}
pub fn compile_labels(frame: &mut CompilerFrame,
clauses: &[LocalFnClause],
body: &IRExpr,
needs_result: NeedsResult,
pos: SourceOffset)
-> Result {
// TODO This is rife with string cloning, because of the sloppy way
// Graph is implemented. Once we fix Graph, we can eliminate some
// clones here.
let mut dependencies = Graph::from_nodes(clauses.iter().map(|clause| clause.name.clone()));
for clause in clauses {
for ref_name in clause.body.get_functions().into_names() {
if dependencies.has_node(&ref_name) {
dependencies.add_edge_no_dup(clause.name.clone(), ref_name);
}
}
}
let sccs = tarjan::find_scc(&dependencies);
let collated_graph = tarjan::build_scc_graph(&dependencies, &sccs);
let collated_graph = collated_graph.transpose(); // We need the arrows pointing in load order, not dependency order
let ordering = top_sort(&collated_graph)
.expect("SCC detection failed (cycle in resulting graph)")
.into_iter().copied();
let mut alg = CompileLabelsRecAlgorithm { frame, body, needs_result, pos, clauses, full_graph: &dependencies, sccs: &sccs, ordering: ordering };
alg.compile_labels_rec()
}
struct CompileLabelsRecAlgorithm<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, 'j, 'k, I> {
frame: &'a mut CompilerFrame<'b, 'c, 'd, 'e, 'f, StmtBuilder>,
body: &'g IRExpr,
needs_result: NeedsResult,
pos: SourceOffset,
clauses: &'h [LocalFnClause],
full_graph: &'i Graph,
sccs: &'j tarjan::SCCSummary<'k, String>,
ordering: I,
}
impl<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, 'j, 'k, I : Iterator- > CompileLabelsRecAlgorithm<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, 'j, 'k, I> {
fn compile_labels_rec(&mut self) -> Result {
if let Some(current_scc_idx) = self.ordering.next() {
let tarjan::SCC(current_scc) = self.sccs.get_scc_by_id(current_scc_idx).expect("SCC detection failed (invalid ID)");
if current_scc.is_empty() {
// That's weird. But whatever. No action needed.
self.compile_labels_rec()
} else {
let name = current_scc.iter().next().expect("Internal error in SCC detection (no first element?)");
if current_scc.len() == 1 && !self.full_graph.has_edge(name, name) {
// Simple FLet-like case.
let name = current_scc.iter().next().expect("Internal error in SCC detection (no first element?)");
let clause = self.clauses.iter().find(|clause| clause.name == **name).expect("Internal error in SCC detection (no function found?)");
let call = compile_flet_call(self.frame, clause.args.to_owned(), &clause.body, self.frame.compiler.is_minimalist(), self.pos)?;
self.with_local_fn((*name).to_owned(), call, |alg| {
alg.compile_labels_rec()
})
} else {
// Complicated mutual recursion case.
let mut relevant_clauses = Vec::new();
for name in current_scc {
let clause = self.clauses.iter().find(|clause| clause.name == **name).expect("Internal error in SCC detection (no function found?)");
relevant_clauses.push(clause);
}
// Go ahead and sort them just so we guarantee a consistent order for testing purposes.
relevant_clauses.sort_by_key(|clause| &clause.name);
let calls = lambda::compile_labels_scc(self.frame, &relevant_clauses[..], self.pos)?;
self.with_local_fns(&mut calls.into_iter(), |alg| {
alg.compile_labels_rec()
})
}
}
} else {
self.frame.compile_expr(self.body, self.needs_result)
}
}
}
impl<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, 'j, 'k, I> HasSymbolTable for CompileLabelsRecAlgorithm<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, 'j, 'k, I> {
fn get_symbol_table(&self) -> &SymbolTable {
self.frame.get_symbol_table()
}
fn get_symbol_table_mut(&mut self) -> &mut SymbolTable {
self.frame.get_symbol_table_mut()
}
}
/// A function declaration is eligible to be semiglobal if all of the
/// following are true.
///
/// * All functions referenced in the body of the function are
/// non-local (i.e. [`FnScope::is_local`] returns false on their
/// scope).
///
/// * All variables referenced in the body of the function are
/// arguments to the function.
///
/// Semiglobal functions do not need to have explicit closure objects
/// constructed for them and can instead be hoisted on the GDScript
/// side into top-level global functions.
pub fn is_declaration_semiglobal(args: &IRArgList, body: &IRExpr, table: &SymbolTable) -> bool {
let (closure_vars, closure_fns) = body.get_names();
let arg_var_names: Vec<_> = args.iter_vars().collect();
// All referenced functions should be Global or SemiGlobal and all
// referenced local variables should be found in the argument list.
let mut closure_names = closure_vars.names();
closure_names.all(|x| arg_var_names.contains(&x)) &&
all_names_are_nonlocal(closure_fns.names(), table)
}
fn all_names_are_nonlocal(mut names: I, table: &SymbolTable)
-> bool
where I : Iterator
- ,
T : AsRef {
names.all(|name| {
table.get_fn(name.as_ref()).map_or(false, |(call, _)| !call.scope.is_local())
})
}
================================================
FILE: src/compile/special_form/lambda.rs
================================================
// Copyright 2023 Silvio Mayolo
//
// This file is part of GDLisp.
//
// GDLisp is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GDLisp is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GDLisp. If not, see .
use crate::util::unzip_err;
use crate::ir;
use crate::ir::expr::{Locals, Functions, LocalFnClause};
use crate::ir::access_type::AccessType;
use crate::compile::{Compiler, StExpr};
use crate::compile::frame::CompilerFrame;
use crate::compile::body::builder::StmtBuilder;
use crate::compile::symbol_table::SymbolTable;
use crate::compile::symbol_table::inner::InnerSymbolTable;
use crate::compile::symbol_table::local_var::{LocalVar, VarScope, VarName};
use crate::compile::symbol_table::function_call::{FnCall, FnSpecs, FnScope, FnName, OuterStaticRef};
use crate::compile::symbol_table::call_magic::CallMagic;
use crate::compile::stmt_wrapper;
use crate::compile::error::{GDError, GDErrorF};
use crate::compile::stateful::SideEffects;
use crate::compile::names::{self, NameTrans};
use crate::compile::names::fresh::FreshNameGenerator;
use crate::compile::names::registered::RegisteredNameGenerator;
use crate::compile::names::generator::NameGenerator;
use crate::gdscript::stmt::{Stmt, StmtF};
use crate::gdscript::expr::{Expr, ExprF};
use crate::gdscript::decl::{self, Decl, DeclF, VarDecl};
use crate::gdscript::class_extends::ClassExtends;
use crate::gdscript::arglist::ArgList;
use crate::gdscript::library;
use crate::gdscript::inner_class::{self, NeedsOuterClassRef};
use crate::pipeline::Pipeline;
use crate::pipeline::can_load::CanLoad;
use crate::pipeline::source::SourceOffset;
use super::lambda_vararg::generate_lambda_class;
use super::closure::{ClosureData, Function, LabelsComponent};
use std::borrow::Borrow;
use std::cmp::max;
type IRExpr = ir::expr::Expr;
type IRArgList = ir::arglist::ordinary::ArgList;
pub fn compile_labels_scc(frame: &mut CompilerFrame,
clauses: &[&LocalFnClause],
pos: SourceOffset)
-> Result, GDError> {
// In the perfect world, we would do all of our operations *on*
// frame rather than destructuring that variable here. But, the way
// this function is currently written, we need access to both
// frame.table and lambda_table throughout most of the computation.
// Perhaps there's a way to refactor it, but it won't be easy.
let CompilerFrame { compiler, pipeline, table, builder, class_scope } = frame;
let mut class_scope = class_scope.closure_mut();
let closure = {
let mut closure = ClosureData::from(LabelsComponent(clauses));
// No need to close around global variables, as they're available everywhere
closure.purge_globals(table);
closure
};
// Generate an outer class ref if we need access to the scope from
// within the lambda class.
let mut outer_ref_name = String::new();
let needs_outer_ref = closure.closure_fns.needs_outer_class_ref(table);
if needs_outer_ref {
outer_ref_name = compiler.name_generator().generate_with(inner_class::OUTER_REFERENCE_NAME);
}
// Determine a name for the global class to represent the labels.
let class_name = RegisteredNameGenerator::new_global_var(table).generate_with("_Labels");
// Bind all of the closure variables, closure functions, and global
// variables inside.
let mut lambda_table = SymbolTable::with_synthetics_from(table);
locally_bind_vars(compiler, table, &mut lambda_table, &closure.closure_vars, &[], pos)?;
locally_bind_fns(compiler, *pipeline, table, &mut lambda_table, &closure.closure_fns, pos, &OuterStaticRef::InnerInstanceVar(&outer_ref_name))?;
copy_global_vars(table, &mut lambda_table);
// Convert the closures to GDScript names.
let gd_closure_vars = closure.to_gd_closure_vars(&lambda_table);
let gd_src_closure_vars = closure.to_gd_closure_vars(table);
let local_var_name = RegisteredNameGenerator::new_local_var(table).generate_with("_locals");
let mut lambda_table = InnerSymbolTable::new(lambda_table, table);
// Bind the functions themselves
let named_clauses = generate_names_for_scc_clauses(clauses.iter().copied(), compiler.name_generator());
for (func_name, clause) in &named_clauses {
let specs = FnSpecs::from(clause.args.to_owned());
let fn_call = special_local_fn_call(local_var_name.clone(), func_name.clone(), specs);
lambda_table.set_fn(clause.name.to_owned(), fn_call, CallMagic::DefaultCall);
}
let (bound_calls, functions) = unzip_err::, Vec<_>, _, _, _>(named_clauses.iter().map(|(func_name, clause)| {
let mut lambda_table = InnerSymbolTable::cloned_from(&mut lambda_table); // New table for this particular function
let mut lambda_builder = StmtBuilder::new();
let (arglist, gd_args) = clause.args.clone().into_gd_arglist(&mut RegisteredNameGenerator::new_local_var(&mut lambda_table));
// Bind the function arguments
for NameTrans { lisp_name: arg, gd_name: gd_arg } in &gd_args {
let access_type = *closure.all_vars.get(arg).unwrap_or(&AccessType::None);
lambda_table.set_var(arg.to_owned(), LocalVar::local(gd_arg.to_owned(), access_type));
wrap_in_cell_if_needed(arg, gd_arg, &closure.all_vars, &mut lambda_builder, pos);
}
compiler.frame(pipeline, &mut lambda_builder, &mut lambda_table, &mut *class_scope).compile_stmt(&stmt_wrapper::Return, &clause.body)?;
let lambda_body = lambda_builder.build_into(*builder);
let func_name = func_name.to_owned();
let func = decl::FnDecl {
name: func_name.clone(),
args: arglist,
body: lambda_body,
};
let call = FnCall {
scope: FnScope::SpecialLocal(local_var_name.clone()),
object: FnName::on_local_var(VarName::local(&local_var_name)),
function: func_name,
specs: FnSpecs::from(clause.args.to_owned()),
is_macro: false,
};
Ok(((clause.name.to_owned(), call), func))
}))?;
let mut constructor_body = Vec::new();
for var in &gd_closure_vars {
constructor_body.push(super::assign_to_compiler(var.to_string(), var.to_string(), pos));
}
let constructor = decl::FnDecl {
name: String::from(library::CONSTRUCTOR_NAME),
args: ArgList::required(gd_closure_vars.iter().map(|x| x.to_owned()).collect()),
body: constructor_body,
};
let mut class_body = vec!();
for var in &gd_closure_vars {
class_body.push(Decl::new(DeclF::VarDecl(VarDecl::simple(var.clone())), pos));
}
class_body.push(Decl::new(DeclF::FnDecl(decl::Static::NonStatic, constructor), pos));
for func in functions {
class_body.push(Decl::new(DeclF::FnDecl(decl::Static::NonStatic, func), pos));
}
let mut class = decl::ClassDecl {
name: class_name.clone(),
extends: ClassExtends::SimpleIdentifier(String::from("Reference")),
body: class_body,
};
if needs_outer_ref {
inner_class::add_outer_class_ref_named(&mut class, compiler.preload_resolver(), *pipeline, outer_ref_name, pos);
}
builder.add_helper(Decl::new(DeclF::ClassDecl(class), pos));
let constructor_args: Vec<_> = gd_src_closure_vars.into_iter().map(|x| Expr::new(ExprF::Var(x), pos)).collect();
let expr = Expr::call(Some(Expr::new(ExprF::Var(class_name), pos)), "new", constructor_args, pos);
builder.append(Stmt::new(StmtF::VarDecl(local_var_name, expr), pos));
Ok(bound_calls)
}
/// Generate an appropriate name for an SCC function generated from a
/// GDLisp function with the given name.
fn generate_scc_name(original_name: &str, gen: &mut FreshNameGenerator) -> String {
let name_prefix = format!("_fn_{}", names::lisp_to_gd(original_name));
gen.generate_with(&name_prefix)
}
fn generate_names_for_scc_clauses<'a>(clauses: impl Iterator
- , gen: &mut FreshNameGenerator)
-> Vec<(String, &'a LocalFnClause)> {
clauses.map(|clause| {
let func_name = generate_scc_name(&clause.name, gen);
(func_name, clause)
}).collect()
}
/// Compiles an [`FnCall`] for a `labels`-style function with the
/// given name and specs, on the given labels object.
pub fn special_local_fn_call(labels_var: String, function: String, specs: FnSpecs) -> FnCall {
FnCall {
scope: FnScope::SpecialLocal(labels_var),
object: FnName::OnLocalScope,
function,
specs,
is_macro: false,
}
}
pub fn locally_bind_vars(compiler: &mut Compiler,
table: &SymbolTable,
lambda_table: &mut SymbolTable,
closure_vars: &Locals,
forbidden_names: &[&str],
_pos: SourceOffset) // _pos unused right now, might need it later :)
-> Result<(), GDError> {
for (var, _access_type, var_pos) in closure_vars.iter_with_offset() {
// Ensure the variable actually exists
match table.get_var(var.borrow()) {
None => return Err(GDError::new(GDErrorF::NoSuchVar(var.borrow().to_owned()), var_pos)),
Some(gdvar) => {
let mut new_var = gdvar.to_owned();
// Ad-hoc rule for closing around self (TODO Generalize?)
if new_var == LocalVar::self_var() { // TODO Special case over in VarName?
new_var = LocalVar::local(compiler.name_generator().generate_with("_self"), AccessType::ClosedRead);
}
let mut name_generator = RegisteredNameGenerator::new_local_var(lambda_table);
protect_closure_var_name(&mut name_generator, &mut new_var, forbidden_names);
lambda_table.set_var(var.borrow().to_owned(), new_var);
}
};
}
Ok(())
}
fn protect_closure_var_name(gen: &mut impl NameGenerator, var: &mut LocalVar, forbidden_names: &[&str]) {
if let Some(original_name) = var.simple_name() {
let mut final_name = original_name.to_owned();
while forbidden_names.contains(&&*final_name) {
final_name = gen.generate_with(original_name);
}
var.set_simple_name(final_name);
}
}
pub fn locally_bind_fns(compiler: &mut Compiler,
pipeline: &L,
table: &SymbolTable,
lambda_table: &mut SymbolTable,
closure_fns: &Functions,
_pos: SourceOffset, // Unused right now, might need it later :)
outer_static_ref: &OuterStaticRef<'_>)
-> Result<(), GDError>
where L : CanLoad {
for (func, (), func_pos) in closure_fns.iter_with_offset() {
// Ensure the function actually exists
match table.get_fn(func.borrow()) {
None => { return Err(GDError::new(GDErrorF::NoSuchFn(func.borrow().to_owned()), func_pos)) }
Some((call, magic)) => {
let mut call = call.clone();
call.object.update_for_inner_scope(outer_static_ref, compiler.preload_resolver(), pipeline);
lambda_table.set_fn(func.borrow().to_owned(), call, magic.clone());
}
};
}
Ok(())
}
/// Copies all of the variables from `src_table` to `dest_table` whose
/// [`VarScope`] is [`VarScope::GlobalVar`]. This function does not
/// modify the function namespace in either table.
pub fn copy_global_vars(src_table: &SymbolTable, dest_table: &mut SymbolTable) {
for (name, var) in src_table.vars() {
if var.scope == VarScope::GlobalVar {
dest_table.set_var(name.to_owned(), var.clone());
}
}
}
/// Compiles a call to a lambda class constructor, where the lambda
/// class has name `class_name`. The closure variables are given by
/// `gd_src_closure_vars`, and the resulting expression will be given
/// source offset `pos`.
///
/// Any expression in `suffix_args` will be suffixed onto the end of
/// the closure variable constructor arguments verbatim and can be
/// used to pass custom arguments to the constructor.
///
/// Despite technically being a method call, lambda constructors are
/// never stateful, so `side_effects` on the result will always be
/// [`SideEffects::None`].
pub fn make_constructor_call(class_name: String,
gd_src_closure_vars: impl IntoIterator
- ,
suffix_args: Vec,
pos: SourceOffset)
-> StExpr {
let side_effects = suffix_args.iter().map(|x| x.side_effects).fold(SideEffects::None, max);
let mut constructor_args: Vec<_> = gd_src_closure_vars.into_iter().map(|x| Expr::new(ExprF::Var(x), pos)).collect();
constructor_args.extend(suffix_args.into_iter().map(|x| x.expr));
let expr = Expr::call(Some(Expr::new(ExprF::Var(class_name), pos)), "new", constructor_args, pos);
StExpr { expr, side_effects }
}
fn wrap_in_cell_if_needed(name: &str, gd_name: &str, all_vars: &Locals, lambda_builder: &mut StmtBuilder, pos: SourceOffset) {
if all_vars.get(name).unwrap_or(&AccessType::None).requires_cell() {
lambda_builder.append(Stmt::simple_assign(Expr::var(gd_name, pos),
library::cell::construct_cell(Expr::var(gd_name, pos)),
pos));
}
}
pub fn compile_lambda_stmt(frame: &mut CompilerFrame,
args: &IRArgList,
body: &IRExpr,
minimalist: bool,
pos: SourceOffset)
-> Result {
// In the perfect world, we would do all of our operations *on*
// frame rather than destructuring that variable here. But, the way
// this function is currently written, we need access to both
// frame.table and lambda_table throughout most of the computation.
// Perhaps there's a way to refactor it, but it won't be easy.
let CompilerFrame { compiler, pipeline, table, builder, class_scope } = frame;
let mut class_scope = class_scope.closure_mut();
let closure = {
let mut closure = ClosureData::from(Function::new(args, body));
// No need to close around global variables, as they're available
// everywhere.
closure.purge_globals(table);
closure
};
// Determine the eventual class name.
let class_name = RegisteredNameGenerator::new_global_var(table).generate_with("_LambdaBlock");
let mut lambda_table = SymbolTable::with_synthetics_from(table);
let (arglist, gd_args) = args.clone().into_gd_arglist(&mut RegisteredNameGenerator::new_local_var(&mut lambda_table));
// Bind the arguments to the lambda in the new lambda table.
for arg in &gd_args {
let access_type = *closure.all_vars.get(&arg.lisp_name).unwrap_or(&AccessType::None);
lambda_table.set_var(arg.lisp_name.to_owned(), LocalVar::local(arg.gd_name.to_owned(), access_type));
}
// Generate an outer class ref if we need access to the scope from
// within the lambda.
let mut outer_ref_name = String::new();
let needs_outer_ref = closure.closure_fns.needs_outer_class_ref(table);
if needs_outer_ref {
outer_ref_name = compiler.name_generator().generate_with(inner_class::OUTER_REFERENCE_NAME);
}
// Bind all of the closure variables, closure functions, and global
// variables inside.
locally_bind_vars(compiler, table, &mut lambda_table, &closure.closure_vars, &[], pos)?;
locally_bind_fns(compiler, *pipeline, table, &mut lambda_table, &closure.closure_fns, pos, &OuterStaticRef::InnerInstanceVar(&outer_ref_name))?;
copy_global_vars(table, &mut lambda_table);
// Convert the closures to GDScript names.
let gd_closure_vars = closure.to_gd_closure_vars(&lambda_table);
let gd_src_closure_vars = closure.to_gd_closure_vars(table);
let mut lambda_table = InnerSymbolTable::new(lambda_table, table);
let lambda_body = {
let mut lambda_builder = StmtBuilder::new();
// Wrap arguments in cells, as needed.
for NameTrans { lisp_name: arg, gd_name: gd_arg } in &gd_args {
wrap_in_cell_if_needed(arg, gd_arg, &closure.all_vars, &mut lambda_builder, pos);
}
// Compile the lambda body.
compiler.frame(pipeline, &mut lambda_builder, &mut lambda_table, &mut *class_scope).compile_stmt(&stmt_wrapper::Return, body)?;
lambda_builder.build_into(*builder)
};
// Generate the enclosing class.
let mut class = generate_lambda_class(class_name.clone(), args.clone().into(), arglist, &gd_closure_vars, lambda_body, minimalist, pos);
// Add outer class reference.
if needs_outer_ref {
inner_class::add_outer_class_ref_named(&mut class, compiler.preload_resolver(), *pipeline, outer_ref_name, pos);
}
// Place the resulting values in the builder.
builder.add_helper(Decl::new(DeclF::ClassDecl(class), pos));
Ok(make_constructor_call(class_name, gd_src_closure_vars, vec!(), pos))
}
/// This function compiles a GDLisp function reference, as constructed
/// using the `(function ...)` special form. GDLisp function
/// references compile to instances of private helper classes, similar
/// to lambda expressions.
///
/// If the function has scope [`FnScope::Local`], then it is already a
/// local variable, and the name of that local variable will be
/// returned without constructing an unnecessary second helper class.
/// If the function has scope [`FnScope::SpecialLocal`], then the
/// resulting helper class will have a single constructor argument:
/// the special local function object. Otherwise, the constructor
/// function will have no constructor arguments.
pub fn compile_function_ref(compiler: &mut Compiler,
pipeline: &mut Pipeline,
builder: &mut StmtBuilder,
table: &mut SymbolTable,
func: FnCall,
minimalist: bool,
pos: SourceOffset)
-> Result {
if let FnScope::Local(name) = func.scope {
// If the function is already bound to a local variable, we can
// happily reuse that variable. This is most likely to come up if
// we take a function ref of an flet function with a nontrivial
// closure.
Ok(StExpr { expr: Expr::new(ExprF::Var(name), pos), side_effects: SideEffects::None })
} else {
let arglist = simple_arg_names(func.specs.runtime_arity());
// Normally, function references are either to local variables (in
// which case, we have a reference already) or functions (in which
// case, there is no closure so a simple top-level class will do).
// However, if the referent is from a nontrivial SCC of a labels
// block, then we have to paradoxically close around it, since it
// *is* local but doesn't satisfy the funcref interface.
let gd_src_closure_vars =
if let FnScope::SpecialLocal(name) = func.scope {
vec!(name)
} else {
vec!()
};
let object = func.object.clone().into_inner_scope(&OuterStaticRef::InnerStatic, compiler.preload_resolver(), pipeline).into_expr(pos);
let body = Stmt::new(
StmtF::ReturnStmt(
Expr::call(object, &func.function, arglist.all_args_iter().map(|x| Expr::var(x, pos)).collect(), pos)
),
pos,
);
// Generate the class and the constructor call.
let class_name = RegisteredNameGenerator::new_global_var(table).generate_with("_FunctionRefBlock");
let class = generate_lambda_class(class_name.clone(), func.specs, arglist, &gd_src_closure_vars, vec!(body), minimalist, pos);
builder.add_helper(Decl::new(DeclF::ClassDecl(class), pos));
Ok(make_constructor_call(class_name, gd_src_closure_vars, vec!(), pos))
}
}
/// Simple helper function to generate basic function arguments with a
/// regular naming scheme. This function makes no effort to avoid
/// conflicts with other variables and should only be used in scopes
/// where such conflicts are impossible.
///
/// The names generated by this function begin with "arg" and are
/// followed by a numerical index from 0 up to (exclusive) `count`.
///
/// # Examples
///
/// ```
/// # use gdlisp::compile::special_form::lambda::simple_arg_names;
/// # use gdlisp::gdscript::arglist::ArgList;
/// assert_eq!(simple_arg_names(0), ArgList::required(vec!()));
/// assert_eq!(simple_arg_names(4), ArgList::required(vec!("arg0".to_string(), "arg1".to_string(), "arg2".to_string(), "arg3".to_string())));
/// ```
pub fn simple_arg_names(count: usize) -> ArgList {
let arg_names = (0..count).map(|i| format!("arg{}", i)).collect();
ArgList::required(arg_names)
}
================================================
FILE: src/compile/special_form/lambda_class.rs
================================================
// Copyright 2023 Silvio Mayolo
//
// This file is part of GDLisp.
//
// GDLisp is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GDLisp is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GDLisp. If not, see .
use crate::ir;
use crate::ir::expr::LambdaClass;
use crate::compile::error::{GDError, GDErrorF};
use crate::compile::factory;
use crate::compile::CompilerFrame;
use crate::compile::Compiler;
use crate::compile::stateful::{StExpr, NeedsResult};
use crate::compile::body::builder::{StmtBuilder, HasDecls};
use crate::compile::body::class_initializer::ClassBuilder;
use crate::compile::body::class_scope::DirectClassScope;
use crate::compile::symbol_table::{SymbolTable, ClassTablePair};
use crate::compile::symbol_table::inner::InnerSymbolTable;
use crate::compile::symbol_table::local_var::LocalVar;
use crate::compile::symbol_table::function_call::OuterStaticRef;
use crate::compile::names::registered::RegisteredNameGenerator;
use crate::compile::names::generator::NameGenerator;
use crate::gdscript::decl::{self, Decl, DeclF, VarDecl};
use crate::gdscript::inner_class::{self, NeedsOuterClassRef};
use crate::pipeline::source::SourceOffset;
use super::lambda;
use super::closure::ClosureData;
pub fn compile_lambda_class(frame: &mut CompilerFrame,
class: &LambdaClass,
pos: SourceOffset)
-> Result {
// In the perfect world, we would do all of our operations *on*
// frame rather than destructuring that variable here. But, the way
// this function is currently written, we need access to both
// frame.table and lambda_table throughout most of the computation.
// Perhaps there's a way to refactor it, but it won't be easy.
let CompilerFrame { compiler, pipeline, table, builder, class_scope: original_class_scope } = frame;
let LambdaClass { extends, args: constructor_args, constructor, decls } = class;
// Note: We construct a new class scope here, since we're now inside
// of a newly-declared class. We use the original_class_scope to
// compile the constructor arguments, since super calls to the scope
// enclosing the class declaration are still valid in that syntactic
// position.
let mut class_scope = DirectClassScope::new();
// Validate the extends declaration (must be a global variable)
let extends = Compiler::resolve_extends(table, extends, pos)?;
// New GD name
let gd_class_name = RegisteredNameGenerator::new_global_var(table).generate_with("_AnonymousClass");
let closure = {
let mut closure = ClosureData::from(class);
// No need to close around global variables, as they're available
// everywhere.
closure.purge_globals(table);
closure
};
// Generate an outer class ref if we need access to the scope from
// within the lambda class.
let mut outer_ref_name = String::new();
let needs_outer_ref = closure.closure_fns.needs_outer_class_ref(table);
if needs_outer_ref {
outer_ref_name = compiler.name_generator().generate_with(inner_class::OUTER_REFERENCE_NAME);
}
let mut lambda_table = SymbolTable::with_synthetics_from(table);
// Bind self into the lambda table.
lambda_table.set_var(String::from("self"), LocalVar::self_var());
// Bind all of the closure variables, closure functions, and global
// variables inside.
let forbidden_names = get_all_instance_scoped_vars(decls);
lambda::locally_bind_vars(compiler, table, &mut lambda_table, &closure.closure_vars, &forbidden_names, pos)?;
lambda::locally_bind_fns(compiler, *pipeline, table, &mut lambda_table, &closure.closure_fns, pos, &OuterStaticRef::InnerInstanceVar(&outer_ref_name))?;
lambda::copy_global_vars(table, &mut lambda_table);
// Convert the closures to GDScript names.
let gd_closure_vars = closure.to_gd_closure_vars(&lambda_table);
let gd_src_closure_vars = closure.to_gd_closure_vars(table);
let mut lambda_table = InnerSymbolTable::new(lambda_table, table);
// Build the constructor for the lambda class.
let default_constructor: ir::decl::ConstructorDecl;
let constructor = match constructor {
None => {
default_constructor = ir::decl::ConstructorDecl::empty(pos);
&default_constructor
}
Some(c) => {
c
}
};
let (constructor, constructor_helpers) = compile_lambda_class_constructor(&mut compiler.frame(pipeline, *builder, &mut lambda_table, &mut class_scope), constructor, &gd_closure_vars, pos)?;
// Build the class body for the lambda class.
let mut class_init_builder = ClassBuilder::new();
#[allow(clippy::vec_init_then_push)] // For style consistency
let class_body = {
let mut class_body = vec!();
class_body.push(Decl::new(DeclF::InitFnDecl(constructor), pos));
for helper in constructor_helpers {
class_body.push(Decl::new(DeclF::FnDecl(decl::Static::NonStatic, helper), pos));
}
for name in gd_closure_vars.iter() {
class_body.push(Decl::new(DeclF::VarDecl(VarDecl::simple(name.clone())), pos));
}
for d in decls {
if d.is_static() {
// Static methods / constants are not allowed on lambda classes
return Err(GDError::new(GDErrorF::StaticOnLambdaClass(d.name().into_owned()), d.pos));
}
// Nothing static is allowed in lambda classes (static methods
// or constants). The ClassTablePair simply gets a dummy static
// symbol table that will never be used, since we just checked
// in the above code that the declaration is non-static.
let mut dummy_table = SymbolTable::new();
let tables = ClassTablePair { instance_table: &mut lambda_table, static_table: &mut dummy_table };
class_body.push(compiler.compile_class_inner_decl(pipeline, &mut class_init_builder, tables, &mut class_scope, d)?);
}
class_body
};
drop(lambda_table);
let mut class = decl::ClassDecl {
name: gd_class_name.clone(),
extends: extends,
body: class_body,
};
if needs_outer_ref {
inner_class::add_outer_class_ref_named(&mut class, compiler.preload_resolver(), *pipeline, outer_ref_name, pos);
}
class_init_builder.declare_proxies_from_scope(class_scope);
let class_init = class_init_builder.build_into(*builder);
class_init.apply(&mut class, pos)?;
builder.add_helper(Decl::new(DeclF::ClassDecl(class), pos));
let constructor_args = constructor_args.iter().map(|expr| compiler.frame(pipeline, *builder, table, *original_class_scope).compile_expr(expr, NeedsResult::Yes)).collect::, _>>()?;
let expr = lambda::make_constructor_call(gd_class_name, gd_src_closure_vars, constructor_args, pos);
Ok(expr)
}
fn get_all_instance_scoped_vars(decls: &[ir::decl::ClassInnerDecl]) -> Vec<&str> {
// TODO This is NOT a complete solution. See Issue #82
// (https://github.com/Mercerenies/gdlisp/issues/82) for the
// problems with this implementation.
let mut result: Vec<&str> = vec![];
for decl in decls {
match &decl.value {
ir::decl::ClassInnerDeclF::ClassVarDecl(var) => {
result.push(&var.name);
}
ir::decl::ClassInnerDeclF::ClassConstDecl(cdecl) => {
result.push(&cdecl.name);
}
ir::decl::ClassInnerDeclF::ClassSignalDecl(_) => {}
ir::decl::ClassInnerDeclF::ClassFnDecl(_) => {}
}
}
result
}
fn compile_lambda_class_constructor(frame: &mut CompilerFrame,
constructor: &ir::decl::ConstructorDecl,
gd_closure_vars: &[String],
pos: SourceOffset)
-> Result<(decl::InitFnDecl, Vec), GDError> {
let (mut constructor, constructor_helpers) = factory::declare_constructor(frame, constructor)?;
constructor.args.prepend_required(gd_closure_vars.iter().cloned());
for name in gd_closure_vars.iter().rev() {
constructor.body.insert(0, super::assign_to_compiler(name.to_string(), name.to_string(), pos));
}
Ok((constructor, constructor_helpers))
}
================================================
FILE: src/compile/special_form/lambda_vararg/builder.rs
================================================
// Copyright 2023 Silvio Mayolo
//
// This file is part of GDLisp.
//
// GDLisp is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GDLisp is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GDLisp. If not, see .
use crate::gdscript::stmt::{self, Stmt};
use crate::gdscript::expr::{Expr, ExprF};
use crate::gdscript::op;
use crate::pipeline::source::SourceOffset;
use std::iter;
/// A `LambdaVarargBuilder` builds up what will eventually be a
/// `Vec`. In the course of doing so, we assume that a variable
/// with the name given by `args_variable` (in the constructor) is in
/// scope, and we accumulate values from that variable into several
/// others, eventually culminating in a final function call which uses
/// the accumulated arguments.
#[derive(Debug, Clone)]
pub struct LambdaVarargBuilder {
args_variable: String,
stmts: Vec,
args: Vec,
pos: SourceOffset,
}
impl LambdaVarargBuilder {
/// Construct a new builder with the given arguments variable, an
/// empty list of accumulated arguments, and the given source
/// position. The source position is used for GDLisp compiler error
/// messages.
pub fn new(args_variable: String, pos: SourceOffset) -> LambdaVarargBuilder {
LambdaVarargBuilder::with_existing_args(args_variable, iter::empty(), pos)
}
/// Construct a new builder with the given arguments variable, an
/// inherited list of accumulated arguments, and the given source
/// position. The source position is used for GDLisp compiler error
/// messages.
pub fn with_existing_args(args_variable: String, args: impl Iterator
- , pos: SourceOffset) -> LambdaVarargBuilder {
LambdaVarargBuilder {
args_variable: args_variable,
stmts: Vec::new(),
args: args.collect(),
pos: pos,
}
}
/// Runs an internal block with a builder inherited from `self`,
/// returning the statements produced by the inner builder.
///
/// The new builder will be created with the same arguments variable
/// and `SourceOffset`, and it will be created with the current
/// accumulated argument list of the outer builder. Changes to the
/// inner builder's argument list will not propagate to the outer.
/// The given `block` shall be called with the inner builder, and
/// then `inner_builder.build()` will be returned.
pub fn with_inner_builder(&self, block: impl FnOnce(&mut LambdaVarargBuilder)) -> Vec {
let mut inner_builder = LambdaVarargBuilder::with_existing_args(
self.args_variable.to_owned(),
self.args.iter().cloned(),
self.pos,
);
block(&mut inner_builder);
inner_builder.build()
}
/// Declares a variable (whose initial value is [`Expr::null`]), and
/// adds it to the accumulated arguments list.
pub fn declare_argument_var(&mut self, name: String) {
self.stmts.push(Stmt::var_decl(name.clone(), Expr::null(self.pos), self.pos));
self.args.push(Expr::new(ExprF::Var(name), self.pos));
}
/// Pushes a call to the GDScript built-in function `push_error`,
/// with the given error message.
pub fn push_error(&mut self, message: &str) {
self.stmts.push(Stmt::expr(
Expr::simple_call("push_error", vec!(Expr::str_lit(message, self.pos)), self.pos),
));
}
/// This method generates code to take the first element (i.e. the
/// `car`) of the arguments variable and assign it to the variable
/// indicated by `variable_name`. Then the arguments variable is
/// reassigned to its own `cdr`. Note that this will fail if the
/// arguments variable contains `nil`, so it is often better to run
/// this inside of a
/// [`if_args_is_empty`](LambdaVarargBuilder::if_args_is_empty)
/// block, to be sure.
pub fn pop_argument(&mut self, variable_name: &str) {
self.stmts.push(Stmt::simple_assign(
Expr::var(variable_name, self.pos),
Expr::var(&self.args_variable, self.pos).attribute("car", self.pos),
self.pos,
));
self.stmts.push(Stmt::simple_assign(
Expr::var(&self.args_variable, self.pos),
Expr::var(&self.args_variable, self.pos).attribute("cdr", self.pos),
self.pos,
));
}
/// Assigns an arbitrary value to the given variable.
pub fn assign_to_var(&mut self, variable_name: &str, value: Expr) {
self.stmts.push(Stmt::simple_assign(Expr::var(variable_name, self.pos), value, self.pos));
}
/// Takes the arguments variable, indicating all of the arguments
/// that have not been processed yet, and adds it, as a single
/// scalar unit, to the accumulated arguments list.
///
/// The `function` argument indicates a transformative function to
/// be applied to the arguments variable before using it as an
/// accumulated argument. If the transformative function is not
/// necessary, then [`LambdaVarargBuilder::pop_rest_of_arguments`]
/// can be used instead.
pub fn pop_rest_of_arguments_with(&mut self, function: F)
where F : FnOnce(Expr) -> Expr {
let args_var = Expr::new(ExprF::Var(self.args_variable.to_owned()), self.pos);
self.args.push(function(args_var));
}
/// Takes the arguments variable, indicating all of the arguments
/// that have not been processed yet, and adds it, as a single
/// scalar unit, to the accumulated arguments list.
///
/// Equivalent to `self.pop_rest_of_arguments_with(|x| x)`.
pub fn pop_rest_of_arguments(&mut self) {
self.pop_rest_of_arguments_with(|x| x);
}
/// Generates code to call the function given by `function_name`,
/// passing all of the accumulated arguments in order. The code is
/// generated as part of a `return` statement, so generally speaking
/// no more code should be executed after this point.
pub fn call_function_with_arguments(&mut self, function_name: &str) {
let all_args = self.args.clone();
self.stmts.push(Stmt::return_stmt(Expr::simple_call(function_name, all_args, self.pos), self.pos));
}
/// Generates code for an `if` statement, where the first branch is
/// followed if the arguments variable has the value `nil` and the
/// second is followed otherwise.
///
/// The two branches are created by passing `empty_block` and
/// `nonempty_block`, respectively, to
/// [`LambdaVarargBuilder::with_inner_builder`].
pub fn if_args_is_empty(&mut self, empty_block: F1, nonempty_block: F2)
where F1 : FnOnce(&mut LambdaVarargBuilder),
F2 : FnOnce(&mut LambdaVarargBuilder) {
let empty_case: Vec = self.with_inner_builder(empty_block);
let nonempty_case: Vec = self.with_inner_builder(nonempty_block);
self.stmts.push(stmt::if_else(
Expr::binary(Expr::var(&self.args_variable, self.pos), op::BinaryOp::Eq, Expr::null(self.pos), self.pos),
empty_case,
nonempty_case,
self.pos,
));
}
/// Consumes the builder and returns the sequence of statements
/// generated.
pub fn build(self) -> Vec