feat: start project building
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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<PathBuf>,
|
||||
/// Path to project
|
||||
#[clap(short, long)]
|
||||
directory: Option<PathBuf>,
|
||||
},
|
||||
/// Start a development server
|
||||
Dev,
|
||||
|
||||
21
crates/cli/src/config.rs
Normal file
21
crates/cli/src/config.rs
Normal file
@@ -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<Config> {
|
||||
let raw_config = fs::read_to_string(dir.join("aiken.toml"))?;
|
||||
|
||||
let config = toml::from_str(&raw_config).unwrap();
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
}
|
||||
3
crates/cli/src/error.rs
Normal file
3
crates/cli/src/error.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub enum Error {
|
||||
Io {},
|
||||
}
|
||||
@@ -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 => {
|
||||
|
||||
134
crates/cli/src/project.rs
Normal file
134
crates/cli/src/project.rs
Normal file
@@ -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<Source>,
|
||||
}
|
||||
|
||||
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<Path>) -> 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"),
|
||||
)
|
||||
}
|
||||
@@ -14,7 +14,6 @@ pub type UntypedModule = Module<(), UntypedDefinition>;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum ModuleKind {
|
||||
Contract,
|
||||
Lib,
|
||||
Script,
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
/// }
|
||||
|
||||
Reference in New Issue
Block a user