Skip to content

Conversation

@kryske
Copy link
Contributor

@kryske kryske commented Jan 4, 2026

I added a bunch of Builtins + Tests, as described in #908

  • rm
  • rmdir
  • sleep -> current test seems unstable with the use of SECONDS
  • ls
  • pwd
  • clear -> I don't know how to test this
  • wait -> current test seems unstable with the use of SECONDS

I also added

  • cp
  • pid -> returns pid of last executed process

Builtins described in #908 but not added:

  • yes
    • I don't get why this would be needed and therefor how this should be implemented.
  • lsof
    • Cloud be implemented to return [Text], but I also don't really get why? Especially considering the rather large amount of optional arguments needed to get an output one would actually want/need.
  • watch, timeout, trap
    • All of these expected a command as a parameter, which in my opinion without the use of Higher Order Functions isn't appropriate.
    • It could be done when the builtin expects the command as an String and the use of the nameof builtin but this seems rather hacky.

I also fixed some variable names in the stdlib because they where using reserved keywords.

Ref: #908

kryske added 9 commits January 4, 2026 19:55
* added builtins: clear, ls, pwd, rm, rmdir and sleep
* added tests for builtins ls, rm, rmdir and pwd
* added builtin function for wait command
* added new builtin for cp
* added test for builtin cp
* added builtin to get pid of last command
* updated kill test to use pid builtin
* don't use reserved keyword pid in stdlib
* added grammar for builtins
* removed const with name lines from pgrep and pgrep_exact as it is a reserved keyword
* added test for sleep builtin
* silenced Terminate message, so it doesn't interfere with the expected output
@codecov
Copy link

codecov bot commented Jan 4, 2026

kryske added 2 commits January 4, 2026 22:22
* added test for wait builtin
* removed deprecated calling method for new builtin cp
@kryske kryske marked this pull request as draft January 4, 2026 21:44
@Mte90 Mte90 requested review from Mte90, Ph0enixKM and lens0021 and removed request for Mte90 January 5, 2026 12:55
Copy link
Member

@Mte90 Mte90 left a comment

Choose a reason for hiding this comment

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

There are some changes to do but others are internal discussion for Amber.


impl TranslateModule for Rm {
fn translate(&self, meta: &mut TranslateMetadata) -> FragmentKind {
fragments!("rm -f ", self.value.translate(meta))
Copy link
Member

Choose a reason for hiding this comment

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

I think that we should have parameters in this builtin, like for the f and r parameter.


impl TranslateModule for RmDir {
fn translate(&self, meta: &mut TranslateMetadata) -> FragmentKind {
fragments!("rm -rf ", self.value.translate(meta))
Copy link
Member

Choose a reason for hiding this comment

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

As I said in the ticket maybe we can use the rmdir command instead of rm.


impl TranslateModule for Wait {
fn translate(&self, meta: &mut TranslateMetadata) -> FragmentKind {
fragments!("wait ", self.pids.translate(meta))
Copy link
Member

Choose a reason for hiding this comment

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

About this maybe it is better to implement it as block? #950 @Ph0enixKM

Copy link
Member

Choose a reason for hiding this comment

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

@Mte90 I'd rename this builtin to be await and let it accept single parameter or a list of parameter:

/// WARNING: This is future Amber code

let pid = async foo()
// Waits until foo finishes
await(pid)

let pids = [async foo(), async bar()]
// Runs in parallel foo and bar and waits for them to finish
await(pids)

// Runs in parallel foo and bar and waits for them to finish
await([async foo(), async bar()])

Copy link
Member

Choose a reason for hiding this comment

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

seems a good idea

@Mte90
Copy link
Member

Mte90 commented Jan 5, 2026

First of all, Thanks!

There are some fixes in tests to do, I suggest to move the discussion about the missing builtin in the ticket itself.
About clear you are right because it is a bash builtin, I think that we should add that in the docs or comments about it.

@Mte90 Mte90 linked an issue Jan 5, 2026 that may be closed by this pull request
kryske added 5 commits January 5, 2026 19:28
* fixed typo
* use touch builtin when possible
* make tests that use SECONDS less volatile
* added force and recursive flag to rm builtin
* added test for rm recursive
* use rmdir when translating
* added argument to ignore errors for non empty dirs
* updated testcase
* removed argument for ignore errors for non empty dirs since it doesn't exist on MacOS
* added optional options to ls builtin
let force_expr = meta.push_ephemeral_variable(force_var_stmt);
meta.stmt_queue.extend([
fragments!(
raw_fragment!("read -rd '' -a {} < <([[ ", force_expr.get_name()),
Copy link
Member

Choose a reason for hiding this comment

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

what is the purpose of this code?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The rm builtin now expects an optional 2nd and 3rd param for the -f and -r flag, that are an expr of type Bool. The builtin probably is most often called like rm("file.txt", true, false) where this translation would be somewhat obsolete. But since the params are an expr it could also be called like rm("file.txt", isF(), isR()) where you'd need to evaluate if the flag should be set during runtime, which the translation above does.
This could benefit from Constant folding since in the 1st case, we can tell during compile time that -f has to be set.

Alternatively I could also rewrite the builtin to just expect a [Text] for the params, so it's called like rm("file.txt", ["-f", "-r"]). This of course leaves more room for unexpected behavior during runtime.

Copy link
Member

Choose a reason for hiding this comment

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

I think that we should accept bool just like we do it right now. If anyone wants to use some less common option parameter, they can just run a regular command.

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 a boolean is the best, until we don't have dictionaries is the right way

@Mte90
Copy link
Member

Mte90 commented Jan 7, 2026

I think that we need to update the stdlib to use where needed those new builtins.
Apart that we need a review from the others ;-)

Copy link
Member

@Ph0enixKM Ph0enixKM left a comment

Choose a reason for hiding this comment

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

@kryske you did a really good job. I'm really impressed. There are some things that have to be addressed though. Nevertheless I like how cleanly you wrote the rust side of code. Good job!

options_frag.with_quotes(false),
path_fragment
),
BlockFragment::new(vec![handler], true).to_frag(),
Copy link
Member

Choose a reason for hiding this comment

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

Consider unwrapping the handler from within BlockFragment unless it's here on purpose.

Suggested change
BlockFragment::new(vec![handler], true).to_frag(),
handler,

Comment on lines +25 to +27
token(meta, "pid")?;
token(meta, "(")?;
token(meta, ")")?;
Copy link
Member

Choose a reason for hiding this comment

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

We sould unify this with status syntax. In this case I think that we should update status to also require parentheses:

// Before
status

// After
status()

options_frag.with_quotes(false),
path_fragment
),
BlockFragment::new(vec![handler], true).to_frag(),
Copy link
Member

Choose a reason for hiding this comment

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

Consider unwrapping handler from within BlockFragment unless this was made on purpose.

Suggested change
BlockFragment::new(vec![handler], true).to_frag(),
handler,

fn translate(&self, meta: &mut TranslateMetadata) -> FragmentKind {
let id = meta.gen_value_id();
let var_stmt =
VarStmtFragment::new("__int", Type::Int, fragments!("$!")).with_global_id(id);
Copy link
Member

Choose a reason for hiding this comment

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

Consider calling it __pid instead.

Suggested change
VarStmtFragment::new("__int", Type::Int, fragments!("$!")).with_global_id(id);
VarStmtFragment::new("__pid", Type::Int, fragments!("$!")).with_global_id(id);

};
let id = meta.gen_value_id();
let var_stmt =
VarStmtFragment::new("__array", Type::array_of(Type::Text), FragmentKind::Empty)
Copy link
Member

Choose a reason for hiding this comment

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

Consider calling this variable __ls instead.

Suggested change
VarStmtFragment::new("__array", Type::array_of(Type::Text), FragmentKind::Empty)
VarStmtFragment::new("__ls", Type::array_of(Type::Text), FragmentKind::Empty)

let force_expr = meta.push_ephemeral_variable(force_var_stmt);
meta.stmt_queue.extend([
fragments!(
raw_fragment!("read -rd '' -a {} < <([[ ", force_expr.get_name()),
Copy link
Member

Choose a reason for hiding this comment

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

I think that we should accept bool just like we do it right now. If anyone wants to use some less common option parameter, they can just run a regular command.

fn new() -> Self {
Ls {
value: Box::new(None),
options: Box::new(None),
Copy link
Member

Choose a reason for hiding this comment

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

Let's add only the most important and common flags. I propose to add support for -a (all) and -R (recursive) in the same format as rm does. This makes it easy to switch on/off certain flags with boolean variables. If someone wants to use some less common flag, they can always run a command.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think -A would be better for the all flag in this case. I don't see the value in returning a "." and ".." entry.

Copy link
Member

Choose a reason for hiding this comment

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

You're absolutely right, @kryske. Let's do -A instead of -a.

let recursive_var_stmt = VarStmtFragment::new("__recursive", Type::Bool, FragmentKind::Empty).with_global_id(recursive_id);
let recursive_expr = meta.push_ephemeral_variable(recursive_var_stmt);
meta.stmt_queue.extend([
fragments!(raw_fragment!("read -rd '' -a {} < <([[ ", recursive_expr.get_name()),
Copy link
Member

Choose a reason for hiding this comment

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

This command reads array from recursive_expr which is of type Bool. Consider rendering the following logic gate:

[[ ${recursive_expr} -neq 0 ]] && __recursive="-r" || __recursive=""

Or even cleaner version which uses bash arithmetic to determine the result

(( recursive_expr )) && __recursive="-r" || __recursive=""

Consider doing the same for force_expr too in this function.

Copy link
Member

Choose a reason for hiding this comment

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

I don't see a point of introducing this builtin since we can already rm("directory", true). I think that we can scrap this module entirely (remember to update grammar.ebnf)

Copy link
Member

Choose a reason for hiding this comment

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

or maybe do an alias to that builtin internally?
rmdir is a command already available/

Copy link
Member

@Ph0enixKM Ph0enixKM Jan 8, 2026

Choose a reason for hiding this comment

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

I don't like the current approach where one builtin can do other builtin's behavior. Let's simplify them to make them all self contained.

Idea 1

Let's either unify them, making the rm(val, true) run rmdir which is a safer option for shell scripting

Idea 2

Let's separate these two builtins so that only one can do its own thing:

  • rm / rmfile - removes only files with rf and fails to remove directories
  • rmdir - removes only directories

Idea 3

Let's just use rm for both files and directories. This could render to the following condition:

[[ -d $target ]] && rmdir "$target" || rm "$target"


impl TranslateModule for Wait {
fn translate(&self, meta: &mut TranslateMetadata) -> FragmentKind {
fragments!("wait ", self.pids.translate(meta))
Copy link
Member

Choose a reason for hiding this comment

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

@Mte90 I'd rename this builtin to be await and let it accept single parameter or a list of parameter:

/// WARNING: This is future Amber code

let pid = async foo()
// Waits until foo finishes
await(pid)

let pids = [async foo(), async bar()]
// Runs in parallel foo and bar and waits for them to finish
await(pids)

// Runs in parallel foo and bar and waits for them to finish
await([async foo(), async bar()])

kryske added 8 commits January 7, 2026 22:01
* switch recursive and force param
* made force param optional and default to false
* simplified translation
* updated tests
* refactored naming in pid builtin
* added parentheses to status to unify syntax
* added deprecation warning for use with parentheses
* updated use of status throughout the project
* simplified pwd builtin translation
* updated prefix of var
* removed rmdir builtin
* removed grammer for rmdir builtin
* renamed builtin wait to await
* updated grammar
* updated params of builtin ls to only provider optional bool params to provide the -A and -R flag
* updated tests
* fixed test because ls -R returns something different on mac
* some code cleanup

main {
trust $ SECONDS=0 $
trust $ sleep 2 & $
Copy link
Member

Choose a reason for hiding this comment

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

we should use the new builtin here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] New builtins proposals

4 participants