diff --git a/offchain/Cargo.lock b/offchain/Cargo.lock index cb08dbb..6cdb6aa 100644 --- a/offchain/Cargo.lock +++ b/offchain/Cargo.lock @@ -879,6 +879,7 @@ dependencies = [ name = "offchain" version = "0.1.0" dependencies = [ + "rand", "revm", "serde", "serde_json", diff --git a/offchain/Cargo.toml b/offchain/Cargo.toml index a469b76..d10fc94 100644 --- a/offchain/Cargo.toml +++ b/offchain/Cargo.toml @@ -6,4 +6,5 @@ edition = "2021" [dependencies] serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -revm = "18.0.0" \ No newline at end of file +revm = "18.0.0" +rand = "0.8" \ No newline at end of file diff --git a/offchain/src/compiler/ast.rs b/offchain/src/compiler/ast.rs index 5f2bad2..65f58cb 100644 --- a/offchain/src/compiler/ast.rs +++ b/offchain/src/compiler/ast.rs @@ -1,15 +1,114 @@ +pub trait Push3Ast { + /// Convert this AST into a bytecode array (Push3 tokens). + fn to_bytecode(&self) -> Vec; + + /// Parse an AST from bytecode (the "decompiler"), if possible. + /// Possibly returns a Result in case of partial/invalid parse. + fn from_bytecode(data: &[u8]) -> Self + where + Self: Sized; + + /// Randomly mutate this AST in-place (GP mutation). + fn mutate(&mut self, rng: &mut impl rand::Rng); + + /// Possibly a "crossover" method, or we do it externally. + // fn crossover(&self, other: &Self, rng: &mut impl rand::Rng) -> Self; + + /// Evaluate the AST with some input(s). + /// Optionally do we want this or do we always run in EVM? + /// We can stub it or do an interpret approach for local test. + fn interpret_locally(&self, inputs: &[i32]) -> Vec; +} + + #[derive(Debug, Clone)] -pub enum Expr { +pub enum TypedAst { IntLiteral(i32), - Instruction(InstructionKind, Vec), - // You might have a variant for sublists if needed, - // or keep it all in the same structure. + Instruction(InstructionKind), } #[derive(Debug, Clone)] pub enum InstructionKind { - // e.g. Plus, Minus, Mult, etc. + Plus(Box, Box), + Minus(Box, Box), + Mult(Box, Box), + Dup, // 0 children + Pop, // 0 children +} + +impl Push3Ast for TypedAst { + fn to_bytecode(&self) -> Vec { + // do typed encoding: + // - for Plus(e1, e2): e1 -> bytecode, e2 -> bytecode, then PLUS opcode + unimplemented!() + } + + fn from_bytecode(data: &[u8]) -> Self { + // parse in a strictly typed manner; might fail or panic + // if the code doesn't line up properly + unimplemented!() + } + + fn mutate(&mut self, rng: &mut impl rand::Rng) { + // e.g. randomly pick a node, replace it with something + // that respects the typed structure + unimplemented!() + } + + fn interpret_locally(&self, inputs: &[i32]) -> Vec { + // do a normal expression evaluation. If a node is "Plus(a,b)", + // interpret (a) + interpret(b). + unimplemented!() + } +} + +#[derive(Debug, Clone)] +pub enum UntypedAst { + /// A single token in a list-based approach, e.g. "Integer literal" + IntLiteral(i32), + + /// "Instruction" might be a simple variant with no children, + /// because the stack-based evaluation decides how many arguments it consumes. + Instruction(OpCode), + + /// A sublist of tokens, e.g. "( 2 3 DUP + )" + Sublist(Vec), +} + +/// Possibly an enum for OpCode: +#[derive(Debug, Clone)] +pub enum OpCode { + Noop, Plus, Minus, Mult, + Dup, + Pop, + // maybe Unknown => treat as no-op +} + +impl Push3Ast for UntypedAst { + fn to_bytecode(&self) -> Vec { + // flatten out sublists vs instructions + unimplemented!() + } + + fn from_bytecode(data: &[u8]) -> Self { + // parse "0x02 => INT_LITERAL => read next 4 bytes" + // instructions might have no subchildren + unimplemented!() + } + + fn mutate(&mut self, rng: &mut impl rand::Rng) { + // can randomly insert unknown tokens, reorder sublists, etc. + // possibly produce "invalid ops => noops" + unimplemented!() + } + + fn interpret_locally(&self, inputs: &[i32]) -> Vec { + // push-based interpretation: + // sublist => interpret each item in sequence + // if an instruction sees insufficient args => no-op + unimplemented!() + } } diff --git a/offchain/src/compiler/bytecode_generator.rs b/offchain/src/compiler/bytecode_generator.rs deleted file mode 100644 index 4e09c0e..0000000 --- a/offchain/src/compiler/bytecode_generator.rs +++ /dev/null @@ -1,7 +0,0 @@ -use super::ast::{Expr, InstructionKind}; - -pub fn ast_to_bytecode(expr: &Expr) -> Vec { - // for now, just a stub - // eventually we produce push3 tokens - vec![] -} diff --git a/offchain/src/compiler/bytecode_parser.rs b/offchain/src/compiler/bytecode_parser.rs deleted file mode 100644 index 9ab1e94..0000000 --- a/offchain/src/compiler/bytecode_parser.rs +++ /dev/null @@ -1,7 +0,0 @@ -use super::ast::{Expr}; - -pub fn bytecode_to_ast(data: &[u8]) -> Expr { - // parse tokens, produce expr - // stub for now - Expr::IntLiteral(0) -} diff --git a/offchain/src/compiler/mod.rs b/offchain/src/compiler/mod.rs index 2a0c9c4..851c0bc 100644 --- a/offchain/src/compiler/mod.rs +++ b/offchain/src/compiler/mod.rs @@ -1,5 +1 @@ pub mod ast; -pub mod bytecode_generator; -pub mod bytecode_parser; - -// In the future, we'll define or `pub use` them. diff --git a/offchain/src/gp/genetic_ops.rs b/offchain/src/gp/genetic_ops.rs index c86dbbf..b5fb028 100644 --- a/offchain/src/gp/genetic_ops.rs +++ b/offchain/src/gp/genetic_ops.rs @@ -1,10 +1,10 @@ -use crate::compiler::ast::{Expr}; +// use crate::compiler::ast::{Expr}; -pub fn crossover(a: &Expr, b: &Expr) -> (Expr, Expr) { - // stub - (a.clone(), b.clone()) -} +// pub fn crossover(a: &Expr, b: &Expr) -> (Expr, Expr) { +// // stub +// (a.clone(), b.clone()) +// } -pub fn mutate(expr: &mut Expr) { - // stub -} +// pub fn mutate(expr: &mut Expr) { +// // stub +// }