Переглянути джерело

Shell: Add support for ARGV (and $*, $#)

This patchset also adds the 'shift' builtin, as well as the usual tests.
closes #2948.
AnotherTest 4 роки тому
батько
коміт
12af65c1c9
8 змінених файлів з 79 додано та 1 видалено
  1. 14 0
      Shell/AST.cpp
  2. 1 0
      Shell/AST.h
  3. 34 0
      Shell/Builtin.cpp
  4. 2 0
      Shell/Parser.cpp
  5. 2 0
      Shell/Parser.h
  6. 1 0
      Shell/Shell.h
  7. 15 0
      Shell/Tests/special-vars.sh
  8. 10 1
      Shell/main.cpp

+ 14 - 0
Shell/AST.cpp

@@ -2004,6 +2004,7 @@ RefPtr<Value> SimpleVariableValue::resolve_without_cast(RefPtr<Shell> shell)
 SpecialVariableValue::~SpecialVariableValue()
 SpecialVariableValue::~SpecialVariableValue()
 {
 {
 }
 }
+
 Vector<String> SpecialVariableValue::resolve_as_list(RefPtr<Shell> shell)
 Vector<String> SpecialVariableValue::resolve_as_list(RefPtr<Shell> shell)
 {
 {
     switch (m_name) {
     switch (m_name) {
@@ -2011,6 +2012,19 @@ Vector<String> SpecialVariableValue::resolve_as_list(RefPtr<Shell> shell)
         return { String::number(shell->last_return_code) };
         return { String::number(shell->last_return_code) };
     case '$':
     case '$':
         return { String::number(getpid()) };
         return { String::number(getpid()) };
+    case '*':
+        if (auto argv = shell->lookup_local_variable("ARGV"))
+            return argv->resolve_as_list(shell);
+        return {};
+    case '#':
+        if (auto argv = shell->lookup_local_variable("ARGV")) {
+            if (argv->is_list()) {
+                auto list_argv = static_cast<AST::ListValue*>(argv.ptr());
+                return { String::number(list_argv->values().size()) };
+            }
+            return { "1" };
+        }
+        return { "0" };
     default:
     default:
         return { "" };
         return { "" };
     }
     }

+ 1 - 0
Shell/AST.h

@@ -233,6 +233,7 @@ public:
     }
     }
 
 
     const Vector<RefPtr<Value>>& values() const { return m_contained_values; }
     const Vector<RefPtr<Value>>& values() const { return m_contained_values; }
+    Vector<RefPtr<Value>>& values() { return m_contained_values; }
 
 
 private:
 private:
     Vector<RefPtr<Value>> m_contained_values;
     Vector<RefPtr<Value>> m_contained_values;

+ 34 - 0
Shell/Builtin.cpp

@@ -678,6 +678,40 @@ int Shell::builtin_setopt(int argc, const char** argv)
     return 0;
     return 0;
 }
 }
 
 
+int Shell::builtin_shift(int argc, const char** argv)
+{
+    int count = 1;
+
+    Core::ArgsParser parser;
+    parser.add_positional_argument(count, "Shift count", "count", Core::ArgsParser::Required::No);
+
+    if (!parser.parse(argc, const_cast<char**>(argv), false))
+        return 1;
+
+    if (count < 1)
+        return 0;
+
+    auto argv_ = lookup_local_variable("ARGV");
+    if (!argv_) {
+        fprintf(stderr, "shift: ARGV is unset\n");
+        return 1;
+    }
+
+    if (!argv_->is_list())
+        argv_ = *new AST::ListValue({ argv_ });
+
+    auto& values = static_cast<AST::ListValue*>(argv_.ptr())->values();
+    if ((size_t)count > values.size()) {
+        fprintf(stderr, "shift: shift count must not be greater than %zu\n", values.size());
+        return 1;
+    }
+
+    for (auto i = 0; i < count; ++i)
+        values.take_first();
+
+    return 0;
+}
+
 int Shell::builtin_time(int argc, const char** argv)
 int Shell::builtin_time(int argc, const char** argv)
 {
 {
     Vector<const char*> args;
     Vector<const char*> args;

+ 2 - 0
Shell/Parser.cpp

@@ -772,6 +772,8 @@ RefPtr<AST::Node> Parser::parse_variable()
     switch (peek()) {
     switch (peek()) {
     case '$':
     case '$':
     case '?':
     case '?':
+    case '*':
+    case '#':
         return create<AST::SpecialVariable>(consume()); // Variable Special
         return create<AST::SpecialVariable>(consume()); // Variable Special
     default:
     default:
         break;
         break;

+ 2 - 0
Shell/Parser.h

@@ -162,6 +162,8 @@ dquoted_string_inner :: '\' . dquoted_string_inner?       {concat}
 variable :: '$' identifier
 variable :: '$' identifier
           | '$' '$'
           | '$' '$'
           | '$' '?'
           | '$' '?'
+          | '$' '*'
+          | '$' '#'
           | ...
           | ...
 
 
 comment :: '#' [^\n]*
 comment :: '#' [^\n]*

+ 1 - 0
Shell/Shell.h

@@ -53,6 +53,7 @@
     __ENUMERATE_SHELL_BUILTIN(pushd)   \
     __ENUMERATE_SHELL_BUILTIN(pushd)   \
     __ENUMERATE_SHELL_BUILTIN(popd)    \
     __ENUMERATE_SHELL_BUILTIN(popd)    \
     __ENUMERATE_SHELL_BUILTIN(setopt)  \
     __ENUMERATE_SHELL_BUILTIN(setopt)  \
+    __ENUMERATE_SHELL_BUILTIN(shift)   \
     __ENUMERATE_SHELL_BUILTIN(time)    \
     __ENUMERATE_SHELL_BUILTIN(time)    \
     __ENUMERATE_SHELL_BUILTIN(jobs)    \
     __ENUMERATE_SHELL_BUILTIN(jobs)    \
     __ENUMERATE_SHELL_BUILTIN(disown)  \
     __ENUMERATE_SHELL_BUILTIN(disown)  \

+ 15 - 0
Shell/Tests/special-vars.sh

@@ -0,0 +1,15 @@
+#!/bin/sh
+
+test "$*" = "" || echo "Fail: Argv list not empty" && exit 1
+test "$#" -eq 0 || echo "Fail: Argv list empty but count non-zero" && exit 1
+test "$ARGV" = "$*" || echo "Fail: \$ARGV not equal to \$*" && exit 1
+
+ARGV=(1 2 3)
+test "$#" -eq 3 || echo "Fail: Assignment to ARGV does not affect \$#" && exit 1
+test "$*" = "1 2 3" || echo "Fail: Assignment to ARGV does not affect \$*" && exit 1
+
+shift
+test "$*" = "2 3" || echo "Fail: 'shift' does not work correctly" && exit 1
+
+shift 2
+test "$*" = "" || echo "Fail: 'shift 2' does not work correctly" && exit 1

+ 10 - 1
Shell/main.cpp

@@ -159,12 +159,14 @@ int main(int argc, char** argv)
 
 
     const char* command_to_run = nullptr;
     const char* command_to_run = nullptr;
     const char* file_to_read_from = nullptr;
     const char* file_to_read_from = nullptr;
+    Vector<const char*> script_args;
     bool skip_rc_files = false;
     bool skip_rc_files = false;
 
 
     Core::ArgsParser parser;
     Core::ArgsParser parser;
     parser.add_option(command_to_run, "String to read commands from", "command-string", 'c', "command-string");
     parser.add_option(command_to_run, "String to read commands from", "command-string", 'c', "command-string");
-    parser.add_positional_argument(file_to_read_from, "File to read commands from", "file", Core::ArgsParser::Required::No);
     parser.add_option(skip_rc_files, "Skip running shellrc files", "skip-shellrc", 0);
     parser.add_option(skip_rc_files, "Skip running shellrc files", "skip-shellrc", 0);
+    parser.add_positional_argument(file_to_read_from, "File to read commands from", "file", Core::ArgsParser::Required::No);
+    parser.add_positional_argument(script_args, "Extra argumets to pass to the script (via $* and co)", "argument", Core::ArgsParser::Required::No);
 
 
     parser.parse(argc, argv);
     parser.parse(argc, argv);
 
 
@@ -181,6 +183,13 @@ int main(int argc, char** argv)
         run_rc_file(Shell::local_init_file_path);
         run_rc_file(Shell::local_init_file_path);
     }
     }
 
 
+    {
+        Vector<String> args;
+        for (auto* arg : script_args)
+            args.empend(arg);
+        shell->set_local_variable("ARGV", *new AST::ListValue(move(args)));
+    }
+
     if (command_to_run) {
     if (command_to_run) {
         dbgprintf("sh -c '%s'\n", command_to_run);
         dbgprintf("sh -c '%s'\n", command_to_run);
         shell->run_command(command_to_run);
         shell->run_command(command_to_run);