From ff26db224573d0a400adec35a3b1a1b751298e5e Mon Sep 17 00:00:00 2001 From: rvcas Date: Wed, 5 Oct 2022 18:57:45 -0400 Subject: [PATCH] feat: start project building --- Cargo.lock | 117 ++++++++++++++++++++++++ crates/cli/Cargo.toml | 4 + crates/cli/src/args.rs | 7 +- crates/cli/src/config.rs | 21 +++++ crates/cli/src/error.rs | 3 + crates/cli/src/main.rs | 30 ++++--- crates/cli/src/project.rs | 134 ++++++++++++++++++++++++++++ crates/lang/src/ast.rs | 1 - crates/lang/src/tipo.rs | 6 +- examples/sample/aiken.toml | 2 + examples/{ => sample/lib}/syntax.ak | 0 11 files changed, 305 insertions(+), 20 deletions(-) create mode 100644 crates/cli/src/config.rs create mode 100644 crates/cli/src/error.rs create mode 100644 crates/cli/src/project.rs create mode 100644 examples/sample/aiken.toml rename examples/{ => sample/lib}/syntax.ak (100%) diff --git a/Cargo.lock b/Cargo.lock index cf0bbb82..14cc4d7a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -22,6 +22,15 @@ dependencies = [ "version_check", ] +[[package]] +name = "aho-corasick" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +dependencies = [ + "memchr", +] + [[package]] name = "aiken" version = "0.0.19" @@ -30,14 +39,18 @@ dependencies = [ "anyhow", "clap", "hex", + "ignore", "pallas-addresses", "pallas-codec", "pallas-crypto", "pallas-primitives", "pallas-traverse", + "regex", "serde", "serde_json", + "toml", "uplc", + "walkdir", ] [[package]] @@ -113,6 +126,15 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "memchr", +] + [[package]] name = "byteorder" version = "1.4.3" @@ -195,6 +217,15 @@ dependencies = [ "tiny-keccak", ] +[[package]] +name = "crossbeam-utils" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" +dependencies = [ + "cfg-if", +] + [[package]] name = "crunchy" version = "0.2.2" @@ -258,6 +289,19 @@ dependencies = [ "wasi", ] +[[package]] +name = "globset" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a" +dependencies = [ + "aho-corasick", + "bstr", + "fnv", + "log", + "regex", +] + [[package]] name = "half" version = "1.8.2" @@ -294,6 +338,24 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "ignore" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d" +dependencies = [ + "crossbeam-utils", + "globset", + "lazy_static", + "log", + "memchr", + "regex", + "same-file", + "thread_local", + "walkdir", + "winapi-util", +] + [[package]] name = "indexmap" version = "1.9.1" @@ -360,6 +422,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + [[package]] name = "miette" version = "5.3.0" @@ -711,6 +779,17 @@ dependencies = [ "bitflags", ] +[[package]] +name = "regex" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + [[package]] name = "regex-syntax" version = "0.6.27" @@ -744,6 +823,15 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scopeguard" version = "1.1.0" @@ -853,6 +941,15 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + [[package]] name = "tiny-keccak" version = "2.0.2" @@ -862,6 +959,15 @@ dependencies = [ "crunchy", ] +[[package]] +name = "toml" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +dependencies = [ + "serde", +] + [[package]] name = "typed-arena" version = "2.0.1" @@ -928,6 +1034,17 @@ dependencies = [ "libc", ] +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index abef133b..9d84dddc 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -21,3 +21,7 @@ serde = { version = "1.0.144", features = ["derive"] } serde_json = "1.0.85" uplc = { path = '../uplc', version = "0.0.18" } aiken-lang = { path = "../lang", version = "0.0.19" } +toml = "0.5.9" +walkdir = "2.3.2" +ignore = "0.4.18" +regex = "1.6.0" diff --git a/crates/cli/src/args.rs b/crates/cli/src/args.rs index 79952fc5..3ed2cfe7 100644 --- a/crates/cli/src/args.rs +++ b/crates/cli/src/args.rs @@ -9,10 +9,11 @@ use clap::{Parser, Subcommand}; pub enum Args { /// Build an aiken project Build, - /// Check a file or project + /// Typecheck a project project Check { - /// Specific aiken file to check - input: Option, + /// Path to project + #[clap(short, long)] + directory: Option, }, /// Start a development server Dev, diff --git a/crates/cli/src/config.rs b/crates/cli/src/config.rs new file mode 100644 index 00000000..4acc18db --- /dev/null +++ b/crates/cli/src/config.rs @@ -0,0 +1,21 @@ +use std::{fs, io, path::PathBuf}; + +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct Config { + pub name: String, + pub version: String, + #[serde(default)] + pub description: String, +} + +impl Config { + pub fn load(dir: PathBuf) -> io::Result { + let raw_config = fs::read_to_string(dir.join("aiken.toml"))?; + + let config = toml::from_str(&raw_config).unwrap(); + + Ok(config) + } +} diff --git a/crates/cli/src/error.rs b/crates/cli/src/error.rs new file mode 100644 index 00000000..33e6df9e --- /dev/null +++ b/crates/cli/src/error.rs @@ -0,0 +1,3 @@ +pub enum Error { + Io {}, +} diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 0047af42..84c04760 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -1,10 +1,12 @@ -use std::{fmt::Write as _, fs}; +use std::{env, fmt::Write as _, fs}; +use config::Config; use pallas_primitives::{ babbage::{TransactionInput, TransactionOutput}, Fragment, }; use pallas_traverse::{Era, MultiEraTx}; +use project::Project; use uplc::{ ast::{DeBruijn, FakeNamedDeBruijn, Name, NamedDeBruijn, Program, Term}, machine::cost_model::ExBudget, @@ -16,6 +18,9 @@ use uplc::{ }; mod args; +mod config; +mod error; +mod project; use args::{Args, TxCommand, UplcCommand}; @@ -33,19 +38,18 @@ fn main() -> anyhow::Result<()> { todo!() } - Args::Check { input } => { - if let Some(input) = input { - let src = fs::read_to_string(&input)?; + Args::Check { directory } => { + let project_path = if let Some(d) = directory { + d + } else { + env::current_dir()? + }; - match aiken_lang::parser::script(&src) { - Ok(_) => (), - Err(errs) => { - for err in errs { - eprintln!("{:#?}", err); - } - } - } - } + let config = Config::load(project_path.clone())?; + + let mut project = Project::new(config, project_path); + + project.build()?; } Args::Dev => { diff --git a/crates/cli/src/project.rs b/crates/cli/src/project.rs new file mode 100644 index 00000000..d552759e --- /dev/null +++ b/crates/cli/src/project.rs @@ -0,0 +1,134 @@ +use std::{ + fs, io, + path::{Path, PathBuf}, +}; + +use aiken_lang::ast::ModuleKind; + +use crate::config::Config; + +#[derive(Debug)] +pub struct Source { + pub path: PathBuf, + pub name: String, + pub code: String, + pub kind: ModuleKind, +} + +pub struct Project { + config: Config, + root: PathBuf, + sources: Vec, +} + +impl Project { + pub fn new(config: Config, root: PathBuf) -> Project { + Project { + config, + root, + sources: vec![], + } + } + + pub fn build(&mut self) -> io::Result<()> { + self.read_source_files()?; + + for source in &self.sources { + println!("{:#?}", source); + + match aiken_lang::parser::script(&source.code) { + Ok(_) => (), + Err(errs) => { + for err in errs { + eprintln!("{:#?}", err); + } + } + } + } + + Ok(()) + } + + fn read_source_files(&mut self) -> io::Result<()> { + let lib = self.root.join("lib"); + let scripts = self.root.join("scripts"); + + self.aiken_files(&scripts, ModuleKind::Script)?; + self.aiken_files(&lib, ModuleKind::Lib)?; + + Ok(()) + } + + fn aiken_files(&mut self, dir: &Path, kind: ModuleKind) -> io::Result<()> { + let paths = walkdir::WalkDir::new(dir) + .follow_links(true) + .into_iter() + .filter_map(Result::ok) + .filter(|e| e.file_type().is_file()) + .map(|d| d.into_path()) + .filter(move |d| is_aiken_path(d, dir)); + + for path in paths { + self.add_module(path, dir, kind)?; + } + + Ok(()) + } + + fn add_module(&mut self, path: PathBuf, dir: &Path, kind: ModuleKind) -> io::Result<()> { + let name = self.module_name(dir, &path); + let code = fs::read_to_string(&path)?; + + self.sources.push(Source { + name, + code, + kind, + path, + }); + + Ok(()) + } + + fn module_name(&self, package_path: &Path, full_module_path: &Path) -> String { + // ../../lib/module.ak + + // module.ak + let mut module_path = full_module_path + .strip_prefix(package_path) + .expect("Stripping package prefix from module path") + .to_path_buf(); + + // module + let _ = module_path.set_extension(""); + + // Stringify + let name = module_path + .to_str() + .expect("Module name path to str") + .to_string(); + + // normalise windows paths + let name = name.replace('\\', "/"); + + // project_name/module + format!("{}/{}", self.config.name, name) + } +} + +fn is_aiken_path(path: &Path, dir: impl AsRef) -> bool { + use regex::Regex; + + let re = Regex::new(&format!( + "^({module}{slash})*{module}\\.ak$", + module = "[a-z][_a-z0-9]*", + slash = "(/|\\\\)", + )) + .expect("is_aiken_path() RE regex"); + + re.is_match( + path.strip_prefix(dir) + .expect("is_gleam_path(): strip_prefix") + .to_str() + .expect("is_gleam_path(): to_str"), + ) +} diff --git a/crates/lang/src/ast.rs b/crates/lang/src/ast.rs index ca591e26..07b03898 100644 --- a/crates/lang/src/ast.rs +++ b/crates/lang/src/ast.rs @@ -14,7 +14,6 @@ pub type UntypedModule = Module<(), UntypedDefinition>; #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum ModuleKind { - Contract, Lib, Script, } diff --git a/crates/lang/src/tipo.rs b/crates/lang/src/tipo.rs index 265c415c..e0ffd953 100644 --- a/crates/lang/src/tipo.rs +++ b/crates/lang/src/tipo.rs @@ -11,7 +11,7 @@ pub enum Type { /// custom type such as `Person`. The type can take other types as /// arguments (aka "generics" or "parametric polymorphism"). /// - /// If the type is defined in the Gleam prelude the `module` field will be + /// If the type is defined in the Aiken prelude the `module` field will be /// empty, otherwise it will contain the name of the module that /// defines the type. /// @@ -44,7 +44,7 @@ pub enum Type { pub enum TypeVar { /// Unbound is an unbound variable. It is one specific type but we don't /// know what yet in the inference process. It has a unique id which can be used to - /// identify if two unbound variable Rust values are the same Gleam type variable + /// identify if two unbound variable Rust values are the same Aiken type variable /// instance or not. /// Unbound { id: u64 }, @@ -57,7 +57,7 @@ pub enum TypeVar { /// /// # Example /// - /// ```gleam + /// ```aiken /// type Cat(a) { /// Cat(name: a) /// } diff --git a/examples/sample/aiken.toml b/examples/sample/aiken.toml new file mode 100644 index 00000000..289acfc2 --- /dev/null +++ b/examples/sample/aiken.toml @@ -0,0 +1,2 @@ +name = "sample" +version = "0.0.1" diff --git a/examples/syntax.ak b/examples/sample/lib/syntax.ak similarity index 100% rename from examples/syntax.ak rename to examples/sample/lib/syntax.ak