Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement command line parser builtin #663

Open
wants to merge 11 commits into
base: staging
Choose a base branch
from
18 changes: 10 additions & 8 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ glob = "0.3"
heraclitus-compiler = "1.8.1"
include_dir = "0.7.4"
itertools = "0.13.0"
once_cell = "1.20.2"
regex = "1.11.1"
similar-string = "1.4.2"
test-generator = "0.3.1"
wildmatch = "2.4.0"
Expand Down
15 changes: 10 additions & 5 deletions src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ use heraclitus_compiler::prelude::*;
use itertools::Itertools;
use wildmatch::WildMatchPattern;
use std::env;
use std::ffi::OsStr;
use std::fs;
use std::fs::File;
use std::io::{ErrorKind, Write};
use std::iter::once;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::process::{Command, ExitStatus};
use std::time::Instant;

Expand All @@ -29,19 +30,23 @@ const AMBER_DEBUG_TIME: &str = "AMBER_DEBUG_TIME";
pub struct CompilerOptions {
pub no_proc: Vec<String>,
pub minify: bool,
pub run_name: Option<String>,
}

impl Default for CompilerOptions {
fn default() -> Self {
let no_proc = vec![String::from("*")];
Self { no_proc, minify: false }
Self { no_proc, minify: false, run_name: None }
}
}

impl CompilerOptions {
pub fn from_args(no_proc: &[String], minify: bool) -> Self {
pub fn from_args(no_proc: &[String], minify: bool, input: Option<&Path>) -> Self {
let no_proc = no_proc.to_owned();
Self { no_proc, minify }
let run_name = input.and_then(Path::file_name)
.and_then(OsStr::to_str)
.map(String::from);
Self { no_proc, minify, run_name }
}
}

Expand Down Expand Up @@ -138,7 +143,7 @@ impl AmberCompiler {
}
}

pub fn get_sorted_ast_forest(
fn get_sorted_ast_forest(
&self,
block: Block,
meta: &ParserMetadata,
Expand Down
10 changes: 6 additions & 4 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ struct Cli {

/// Arguments passed to Amber script
#[arg(trailing_var_arg = true)]
#[arg(allow_hyphen_values = true)]
args: Vec<String>,

/// Disable a postprocessor
Expand Down Expand Up @@ -70,6 +71,7 @@ struct RunCommand {

/// Arguments passed to Amber script
#[arg(trailing_var_arg = true)]
#[arg(allow_hyphen_values = true)]
Copy link
Contributor Author

@hdwalters hdwalters Jan 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This allows long options --xyz and short options -x to be passed to the Amber script, without being rejected as invalid by the clap library; but clap still consumes the --help option with amber script.ab --help or ./script.ab --help. I would like to make it pass all options following a positional argument to the Amber script, but I'm not sure how. All suggestions appreciated!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is of course possible to force options to be passed to the script with amber script.ab -- --help. I've tried moving this Clap argument to the end of the struct and adding #[arg(last = true)], but that makes it necessary to call amber script.ab -- arg1 arg2 for all arguments, not just --help.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hdwalters does allow_external_subcommands solve this issue?

Copy link
Contributor Author

@hdwalters hdwalters Jan 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure it would. According to the docs, it handles "unexpected positional arguments" (such as foo) not optional ones (such as --help).

I will try it, however.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may have to put up with ./script.ab -- --help.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah... I think that this solution of adding the -- is good enough. It adds the separation between the Amber's arguments and the script args.

args: Vec<String>,

/// Disable a postprocessor
Expand Down Expand Up @@ -134,17 +136,17 @@ fn main() -> Result<(), Box<dyn Error>> {
handle_eval(command)?;
}
CommandKind::Run(command) => {
let options = CompilerOptions::from_args(&command.no_proc, false);
let options = CompilerOptions::from_args(&command.no_proc, false, Some(&command.input));
let (code, messages) = compile_input(command.input, options);
execute_output(code, command.args, messages)?;
}
CommandKind::Check(command) => {
let options = CompilerOptions::from_args(&command.no_proc, false);
let options = CompilerOptions::from_args(&command.no_proc, false, None);
compile_input(command.input, options);
}
CommandKind::Build(command) => {
let output = create_output(&command);
let options = CompilerOptions::from_args(&command.no_proc, command.minify);
let options = CompilerOptions::from_args(&command.no_proc, command.minify, None);
let (code, _) = compile_input(command.input, options);
write_output(output, code);
}
Expand All @@ -156,7 +158,7 @@ fn main() -> Result<(), Box<dyn Error>> {
}
}
} else if let Some(input) = cli.input {
let options = CompilerOptions::from_args(&cli.no_proc, false);
let options = CompilerOptions::from_args(&cli.no_proc, false, Some(&input));
let (code, messages) = compile_input(input, options);
execute_output(code, cli.args, messages)?;
}
Expand Down
63 changes: 63 additions & 0 deletions src/modules/builtin/cli/getopt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use crate::docs::module::DocumentationModule;
use crate::modules::builtin::cli::param::ParamImpl;
use crate::modules::builtin::cli::parser::ParserImpl;
use crate::modules::expression::expr::Expr;
use crate::modules::types::{Type, Typed};
use crate::translate::module::TranslateModule;
use crate::utils::metadata::{ParserMetadata, TranslateMetadata};
use heraclitus_compiler::prelude::*;
use std::cell::RefCell;
use std::rc::Rc;

#[derive(Debug, Clone)]
pub struct GetoptCli {
parser: Option<Rc<RefCell<ParserImpl>>>,
args: Box<Expr>,
}

impl Typed for GetoptCli {
fn get_type(&self) -> Type {
Type::Null
}
}

impl SyntaxModule<ParserMetadata> for GetoptCli {
syntax_name!("Getopt Invocation");

fn new() -> Self {
let args = Box::new(Expr::new());
Self { parser: None, args }
}

fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult {
token(meta, "getopt")?;
let mut parser = Expr::new();
token(meta, "(")?;
let parser_tok = meta.get_current_token();
syntax(meta, &mut parser)?;
token(meta, ",")?;
syntax(meta, &mut *self.args)?;
token(meta, ")")?;
let parser = match ParserImpl::find_parser(meta, &parser) {
Some(parser) => parser,
None => return error!(meta, parser_tok, "Expected parser object"),
};
parser.borrow_mut().add_param(ParamImpl::help());
self.parser = Some(parser);
Ok(())
}
}

impl TranslateModule for GetoptCli {
fn translate(&self, meta: &mut TranslateMetadata) -> String {
self.parser.as_ref()
.map(|parser| parser.borrow().translate(meta, &self.args))
.unwrap_or_default()
}
}

impl DocumentationModule for GetoptCli {
fn document(&self, _meta: &ParserMetadata) -> String {
String::new()
}
}
3 changes: 3 additions & 0 deletions src/modules/builtin/cli/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod getopt;
pub mod param;
pub mod parser;
Loading
Loading