Add ability to write prelude in KCL
This commit is contained in:
		@ -8,6 +8,7 @@ description = "A new executor for KCL which compiles to Execution Plans"
 | 
			
		||||
[dependencies]
 | 
			
		||||
image = { version = "0.24.7", default-features = false, features = ["png"] }
 | 
			
		||||
kcl-lib = { path = "../kcl" }
 | 
			
		||||
kcl-macros = { path = "../kcl-macros/" }
 | 
			
		||||
kittycad = { workspace = true }
 | 
			
		||||
kittycad-execution-plan = { workspace = true }
 | 
			
		||||
kittycad-execution-plan-traits = { workspace = true }
 | 
			
		||||
 | 
			
		||||
@ -11,6 +11,8 @@ use kcl_lib::{
 | 
			
		||||
    ast,
 | 
			
		||||
    ast::types::{BodyItem, FunctionExpressionParts, KclNone, LiteralValue, Program},
 | 
			
		||||
};
 | 
			
		||||
extern crate alloc;
 | 
			
		||||
use kcl_macros::parse_file;
 | 
			
		||||
use kcl_value_group::into_single_value;
 | 
			
		||||
use kittycad_execution_plan::{self as ep, Destination, Instruction};
 | 
			
		||||
use kittycad_execution_plan_traits as ept;
 | 
			
		||||
@ -24,9 +26,11 @@ use self::{
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// Execute a KCL program by compiling into an execution plan, then running that.
 | 
			
		||||
pub async fn execute(ast: Program, session: &mut Option<Session>) -> Result<ep::Memory, Error> {
 | 
			
		||||
/// Include a `prelude.kcl` inlined as a `Program` struct thanks to a proc_macro `parse!`.
 | 
			
		||||
/// This makes additional functions available to the user.
 | 
			
		||||
pub async fn execute(ast_user: Program, session: &mut Option<Session>) -> Result<ep::Memory, Error> {
 | 
			
		||||
    let mut planner = Planner::new();
 | 
			
		||||
    let (plan, _retval) = planner.build_plan(ast)?;
 | 
			
		||||
    let (plan, _retval) = planner.build_plan(ast_user)?;
 | 
			
		||||
    let mut mem = ep::Memory::default();
 | 
			
		||||
    ep::execute(&mut mem, plan, session).await?;
 | 
			
		||||
    Ok(mem)
 | 
			
		||||
@ -54,7 +58,9 @@ impl Planner {
 | 
			
		||||
 | 
			
		||||
    /// If successful, return the KCEP instructions for executing the given program.
 | 
			
		||||
    /// If the program is a function with a return, then it also returns the KCL function's return value.
 | 
			
		||||
    fn build_plan(&mut self, program: Program) -> Result<(Vec<Instruction>, Option<EpBinding>), CompileError> {
 | 
			
		||||
    fn build_plan(&mut self, ast_user: Program) -> Result<(Vec<Instruction>, Option<EpBinding>), CompileError> {
 | 
			
		||||
        let ast_prelude = parse_file!("./prelude.kcl");
 | 
			
		||||
        let program = ast_prelude.merge(ast_user);
 | 
			
		||||
        program
 | 
			
		||||
            .body
 | 
			
		||||
            .into_iter()
 | 
			
		||||
 | 
			
		||||
@ -32,23 +32,23 @@ impl<'a> Context<'a> {
 | 
			
		||||
/// Unary operator macro to quickly create new bindings.
 | 
			
		||||
macro_rules! define_unary {
 | 
			
		||||
  () => {};
 | 
			
		||||
  ($h:ident$( $r:ident)*) => {
 | 
			
		||||
  ($fn_name:ident$( $rest:ident)*) => {
 | 
			
		||||
    #[derive(Debug, Clone)]
 | 
			
		||||
    #[cfg_attr(test, derive(Eq, PartialEq))]
 | 
			
		||||
    pub struct $h;
 | 
			
		||||
    pub struct $fn_name;
 | 
			
		||||
 | 
			
		||||
    impl Callable for $h {
 | 
			
		||||
    impl Callable for $fn_name {
 | 
			
		||||
        fn call(&self, ctx: &mut Context<'_>, mut args: Vec<EpBinding>) -> Result<EvalPlan, CompileError> {
 | 
			
		||||
            if args.len() > 1 {
 | 
			
		||||
                return Err(CompileError::TooManyArgs {
 | 
			
		||||
                    fn_name: "$h".into(),
 | 
			
		||||
                    fn_name: "$fn_name".into(),
 | 
			
		||||
                    maximum: 1,
 | 
			
		||||
                    actual: args.len(),
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            let not_enough_args = CompileError::NotEnoughArgs {
 | 
			
		||||
                fn_name: "$h".into(),
 | 
			
		||||
                fn_name: "$fn_name".into(),
 | 
			
		||||
                required: 1,
 | 
			
		||||
                actual: args.len(),
 | 
			
		||||
            };
 | 
			
		||||
@ -61,7 +61,7 @@ macro_rules! define_unary {
 | 
			
		||||
            let instructions = vec![
 | 
			
		||||
              Instruction::UnaryArithmetic {
 | 
			
		||||
                arithmetic: UnaryArithmetic {
 | 
			
		||||
                  operation: UnaryOperation::$h,
 | 
			
		||||
                  operation: UnaryOperation::$fn_name,
 | 
			
		||||
                  operand: Operand::Reference(arg0)
 | 
			
		||||
                },
 | 
			
		||||
                destination: Destination::Address(destination)
 | 
			
		||||
@ -75,7 +75,7 @@ macro_rules! define_unary {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    define_unary!($($r)*);
 | 
			
		||||
    define_unary!($($rest)*);
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -115,27 +115,27 @@ impl Callable for Id {
 | 
			
		||||
/// Binary operator macro to quickly create new bindings.
 | 
			
		||||
macro_rules! define_binary {
 | 
			
		||||
  () => {};
 | 
			
		||||
  ($h:ident$( $r:ident)*) => {
 | 
			
		||||
  ($fn_name:ident$( $rest:ident)*) => {
 | 
			
		||||
    #[derive(Debug, Clone)]
 | 
			
		||||
    #[cfg_attr(test, derive(Eq, PartialEq))]
 | 
			
		||||
    pub struct $h;
 | 
			
		||||
    pub struct $fn_name;
 | 
			
		||||
 | 
			
		||||
    impl Callable for $h {
 | 
			
		||||
    impl Callable for $fn_name {
 | 
			
		||||
        fn call(&self, ctx: &mut Context<'_>, mut args: Vec<EpBinding>) -> Result<EvalPlan, CompileError> {
 | 
			
		||||
          let len = args.len();
 | 
			
		||||
          if len > 2 {
 | 
			
		||||
              return Err(CompileError::TooManyArgs {
 | 
			
		||||
                  fn_name: "$h".into(),
 | 
			
		||||
                  fn_name: "$fn_name".into(),
 | 
			
		||||
                  maximum: 2,
 | 
			
		||||
                  actual: len,
 | 
			
		||||
              });
 | 
			
		||||
          }
 | 
			
		||||
          let not_enough_args = CompileError::NotEnoughArgs {
 | 
			
		||||
              fn_name: "$h".into(),
 | 
			
		||||
              fn_name: "$fn_name".into(),
 | 
			
		||||
              required: 2,
 | 
			
		||||
              actual: len,
 | 
			
		||||
          };
 | 
			
		||||
          const ERR: &str = "cannot use composite values (e.g. array) as arguments to $h";
 | 
			
		||||
          const ERR: &str = "cannot use composite values (e.g. array) as arguments to $fn_name";
 | 
			
		||||
          let EpBinding::Single(arg1) = args.pop().ok_or(not_enough_args.clone())? else {
 | 
			
		||||
              return Err(CompileError::InvalidOperand(ERR));
 | 
			
		||||
          };
 | 
			
		||||
@ -146,7 +146,7 @@ macro_rules! define_binary {
 | 
			
		||||
          Ok(EvalPlan {
 | 
			
		||||
              instructions: vec![Instruction::BinaryArithmetic {
 | 
			
		||||
                  arithmetic: BinaryArithmetic {
 | 
			
		||||
                      operation: BinaryOperation::$h,
 | 
			
		||||
                      operation: BinaryOperation::$fn_name,
 | 
			
		||||
                      operand0: Operand::Reference(arg0),
 | 
			
		||||
                      operand1: Operand::Reference(arg1),
 | 
			
		||||
                  },
 | 
			
		||||
@ -157,7 +157,7 @@ macro_rules! define_binary {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    define_binary!($($r)*);
 | 
			
		||||
    define_binary!($($rest)*);
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1442,3 +1442,23 @@ async fn cos_sin_pi() {
 | 
			
		||||
    // Constants don't live in memory.
 | 
			
		||||
    assert_eq!(*z, constants::PI);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[tokio::test]
 | 
			
		||||
async fn kcl_prelude() {
 | 
			
		||||
    let program = "
 | 
			
		||||
        let the_answer_to_the_universe_is = hey_from_one_of_the_devs_of_the_past_i_wish_you_a_great_day_and_maybe_gave_you_a_little_smile_as_youve_found_this()
 | 
			
		||||
    ";
 | 
			
		||||
    let (_plan, scope, _) = must_plan(program);
 | 
			
		||||
    let Some(EpBinding::Single(the_answer_to_the_universe_is)) = scope.get("the_answer_to_the_universe_is") else {
 | 
			
		||||
        panic!(
 | 
			
		||||
            "Unexpected binding for variable 'the_answer_to_the_universe_is': {:?}",
 | 
			
		||||
            scope.get("the_answer_to_the_universe_is")
 | 
			
		||||
        );
 | 
			
		||||
    };
 | 
			
		||||
    let ast = kcl_lib::parser::Parser::new(kcl_lib::token::lexer(program))
 | 
			
		||||
        .ast()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
    let mem = crate::execute(ast, &mut None).await.unwrap();
 | 
			
		||||
    use ept::ReadMemory;
 | 
			
		||||
    assert_eq!(*mem.get(the_answer_to_the_universe_is).unwrap(), Primitive::from(42i64));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -8,7 +8,6 @@ use syn::{parse_macro_input, LitStr};
 | 
			
		||||
/// This macro takes exactly one argument: A string literal containing KCL.
 | 
			
		||||
/// # Examples
 | 
			
		||||
/// ```
 | 
			
		||||
/// extern crate alloc;
 | 
			
		||||
/// use kcl_compile_macro::parse_kcl;
 | 
			
		||||
/// let ast: kcl_lib::ast::types::Program = parse_kcl!("const y = 4");
 | 
			
		||||
/// ```
 | 
			
		||||
@ -21,3 +20,14 @@ pub fn parse(input: TokenStream) -> TokenStream {
 | 
			
		||||
    let ast_struct = ast.bake(&Default::default());
 | 
			
		||||
    quote!(#ast_struct).into()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Same as `parse!` but read in a file.
 | 
			
		||||
#[proc_macro]
 | 
			
		||||
pub fn parse_file(input: TokenStream) -> TokenStream {
 | 
			
		||||
    let file_name = parse_macro_input!(input as LitStr);
 | 
			
		||||
    let kcl_src = std::fs::read_to_string(file_name.value()).unwrap();
 | 
			
		||||
    let tokens = kcl_lib::token::lexer(&kcl_src);
 | 
			
		||||
    let ast = kcl_lib::parser::Parser::new(tokens).ast().unwrap();
 | 
			
		||||
    let ast_struct = ast.bake(&Default::default());
 | 
			
		||||
    quote!(#ast_struct).into()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -351,6 +351,31 @@ impl Program {
 | 
			
		||||
 | 
			
		||||
        None
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Merge two `Program`s together.
 | 
			
		||||
    pub fn merge(self, ast_other: Program) -> Program {
 | 
			
		||||
      let mut body_new: Vec<BodyItem> = vec![];
 | 
			
		||||
      body_new.extend(self.body);
 | 
			
		||||
      body_new.extend(ast_other.body);
 | 
			
		||||
 | 
			
		||||
      let mut non_code_nodes_new: HashMap<usize, Vec<NonCodeNode>> = HashMap::new();
 | 
			
		||||
      non_code_nodes_new.extend(self.non_code_meta.non_code_nodes);
 | 
			
		||||
      non_code_nodes_new.extend(ast_other.non_code_meta.non_code_nodes);
 | 
			
		||||
 | 
			
		||||
      let mut start_new: Vec<NonCodeNode> = vec![];
 | 
			
		||||
      start_new.extend(self.non_code_meta.start);
 | 
			
		||||
      start_new.extend(ast_other.non_code_meta.start);
 | 
			
		||||
 | 
			
		||||
      Program {
 | 
			
		||||
        start: self.start,
 | 
			
		||||
        end: self.end + (ast_other.end - ast_other.start),
 | 
			
		||||
        body: body_new,
 | 
			
		||||
        non_code_meta: NonCodeMeta {
 | 
			
		||||
          non_code_nodes: non_code_nodes_new,
 | 
			
		||||
          start: start_new,
 | 
			
		||||
        },
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub trait ValueMeta {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										3
									
								
								src/wasm-lib/prelude.kcl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/wasm-lib/prelude.kcl
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
			
		||||
fn hey_from_one_of_the_devs_of_the_past_i_wish_you_a_great_day_and_maybe_gave_you_a_little_smile_as_youve_found_this = () => {
 | 
			
		||||
  return 42
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user