diff --git a/run.sh b/run.sh index 4e84afa16..16d1eaf67 100755 --- a/run.sh +++ b/run.sh @@ -32,4 +32,6 @@ if [ "$SUM256" != "$NEW_SUM256" ]; then build fi -./joker "$@" +if [ "$1" != "--build-only" ]; then + ./joker "$@" +fi diff --git a/std/generate-std.joke b/std/generate-std.joke index 3866536fb..9d8b613aa 100644 --- a/std/generate-std.joke +++ b/std/generate-std.joke @@ -10,6 +10,9 @@ (require '[joker.string :as s] '[joker.os :as os]) +(def JOKER_STD_OS (or (keyword (joker.os/get-env "JOKER_STD_OS")) + :darwin)) + (def rpl s/replace) (defn debug @@ -60,8 +63,7 @@ (defn raw-quoted-string "Returns a Go-style backtick-quoted string with backticks handled by appending double-quoted backticks" [s] - (str "`" (rpl s "`" "` + \"`\" + `") "`") - ) + (str "`" (rpl s "`" "` + \"`\" + `") "`")) (defn go-name "Convert Clojure-style function name to unique Go-style name suitable as its internal implementation." @@ -149,12 +151,14 @@ [m] (let [m (dissoc m :doc :added :arglists :ns :name :file :line :column :go)] (s/join "" (map #(-> addmeta-template - (rpl "{key}" (s/replace-first (str (key %)) ":" "")) - (rpl "{value}" (make-value (val %)))) m)))) + (rpl "{key}" (s/replace-first (str (key %)) ":" "")) + (rpl "{value}" (make-value (val %)))) m)))) (defn generate-fn-decl [ns-name ns-name-final k v] (let [m (meta v) + _ (when-not (:doc m) + (throw (ex-info (str "Missing docstring for function declaration: " k) {}))) arglists (:arglists m) go-fn-name (go-name (str k)) arities (s/join "\n\t" (map #(generate-arity % (:go m) (:tag m)) arglists)) @@ -194,43 +198,53 @@ [name m] (let [type (type-name (:tag m))] (if (= type "Var") - (format "var %s *GoVar" name) ; Not yet supported by this version of Joker (see https://github.com/jcburley/joker/) + (format "var %s *GoVar" name) ; Not yet supported by this version of Joker (see https://github.com/jcburley/joker/) (format "var %s %s" name (go-return-type type))))) (defn generate-const-or-var-init [name m] - (let [type (type-name (:tag m))] - (if (= type "Var") - (format "\t%s = &GoVar{Value: &%s}" ; Get pointer to the actual var, not a copy of the var - name - (:go m)) - (format "\t%s = Make%s(%s)" - name - type - (:go m))))) + (let [type (type-name (:tag m)) + go (:go m) + v (if (map? go) + (or (get go JOKER_STD_OS) + (:default go)) + go)] + (when v + (if (= type "Var") + (format "\t%s = &GoVar{Value: &%s}" ; Get pointer to the actual var, not a copy of the var + name + v) + (format "\t%s = Make%s(%s)" + name + type + v))))) (defn generate-non-fn-decl [ns-name ns-name-final k v] (let [m (meta v) - go-non-fn-name (go-name (str k)) - non-fn-str (generate-const-or-var-decl go-non-fn-name m) - intern-str (-> intern-template - (rpl "{nsFullName}" ns-name) - (rpl "{nsName}" ns-name-final) - (rpl "{fnName}" (str k)) - (rpl "{goName}" go-non-fn-name) - (rpl "{fnDocstring}" (raw-quoted-string (:doc m))) - (rpl "{added}" (:added m)) - (rpl "{moreMeta}" (add-other-meta m)) - (rpl "{args}" "nil"))] - [non-fn-str intern-str])) - -(defn generate-non-fn-init + go (:go m)] + (if (and (map? go) + (not (contains? go :default)) + (not (contains? go JOKER_STD_OS))) + nil + (let [go-non-fn-name (go-name (str k)) + non-fn-str (generate-const-or-var-decl go-non-fn-name m) + intern-str (-> intern-template + (rpl "{nsFullName}" ns-name) + (rpl "{nsName}" ns-name-final) + (rpl "{fnName}" (str k)) + (rpl "{goName}" go-non-fn-name) + (rpl "{fnDocstring}" (raw-quoted-string (:doc m))) + (rpl "{added}" (:added m)) + (rpl "{moreMeta}" (add-other-meta m)) + (rpl "{args}" "nil"))] + [non-fn-str intern-str])))) + +(defn- generate-non-fn-init [ns-name-final k v] (let [m (meta v) - go-non-fn-name (go-name (str k)) - non-fn-str (generate-const-or-var-init go-non-fn-name m)] - non-fn-str)) + go-non-fn-name (go-name (str k))] + (generate-const-or-var-init go-non-fn-name m))) (defn comment-out [s] @@ -282,11 +296,13 @@ go-non-fns (sort-by first (ns-public-go-non-fns ns)) go-fns (sort-by first (ns-public-go-fns ns)) fn-decls (for [[k v] go-fns] - (generate-fn-decl ns-name ns-name-final k v)) - non-fn-decls (for [[k v] go-non-fns] - (generate-non-fn-decl ns-name ns-name-final k v)) - non-fn-inits (for [[k v] go-non-fns] - (generate-non-fn-init ns-name-final k v)) + (generate-fn-decl ns-name ns-name-final k v)) + non-fn-decls (remove nil? + (for [[k v] go-non-fns] + (generate-non-fn-decl ns-name ns-name-final k v))) + non-fn-inits (remove nil? + (for [[k v] go-non-fns] + (generate-non-fn-init ns-name-final k v))) res (-> package-template (rpl "{nsFullName}" ns-name) (rpl "{nsName}" ns-name-final) @@ -312,11 +328,10 @@ go-non-fns (sort-by first (ns-public-go-non-fns ns)) go-fns (sort-by first (ns-public-go-fns ns)) fn-decls (for [[k v] go-fns] - (generate-fn-decl ns-name ns-name-final k v)) - non-fn-decls (for [[k v] go-non-fns] - (generate-non-fn-decl ns-name ns-name-final k v)) - non-fn-inits (for [[k v] go-non-fns] - (generate-non-fn-init ns-name-final k v)) + (generate-fn-decl ns-name ns-name-final k v)) + non-fn-decls (remove nil? + (for [[k v] go-non-fns] + (generate-non-fn-decl ns-name ns-name-final k v))) res (-> package-slow-template (rpl "{maybeSlowOnly}" (if pre "//go:build gen_code\n// +build gen_code\n" "")) (rpl "{nsFullName}" ns-name) @@ -383,4 +398,4 @@ (remove-blanky-lines (generate-ns-slow-init ns-sym ns-name ns-name-final pre))) (when pre (spit (ns-file-name-fast-init dir ns-name-final) - (remove-blanky-lines (generate-ns-fast-init ns-sym ns-name ns-name-final)))))) + (remove-blanky-lines (generate-ns-fast-init ns-sym ns-name ns-name-final)))))) diff --git a/std/os.joke b/std/os.joke index 237e905f4..bc0c5c27c 100644 --- a/std/os.joke +++ b/std/os.joke @@ -2,6 +2,90 @@ :doc "Provides a platform-independent interface to operating system functionality."} os) +(def ^{:doc "SIGABRT" + :added "1.0.1" + :tag Int + :const true + :go "0x6"} + SIGABRT) + +(def ^{:doc "SIGALRM" + :added "1.0.1" + :tag Int + :const true + :go "0xe"} + SIGALRM) + +(def ^{:doc "SIGFPE" + :added "1.0.1" + :tag Int + :const true + :go "0x8"} + SIGFPE) + +(def ^{:doc "SIGHUP" + :added "1.0.1" + :tag Int + :const true + :go "0x1"} + SIGHUP) + +(def ^{:doc "SIGILL" + :added "1.0.1" + :tag Int + :const true + :go "0x4"} + SIGILL) + +(def ^{:doc "SIGINT" + :added "1.0.1" + :tag Int + :const true + :go "0x2"} + SIGINT) + +(def ^{:doc "SIGKILL" + :added "1.0.1" + :tag Int + :const true + :go "0x9"} + SIGKILL) + +(def ^{:doc "SIGPIPE" + :added "1.0.1" + :tag Int + :const true + :go "0xd"} + SIGPIPE) + +(def ^{:doc "SIGQUIT" + :added "1.0.1" + :tag Int + :const true + :go "0x3"} + SIGQUIT) + +(def ^{:doc "SIGSEGV" + :added "1.0.1" + :tag Int + :const true + :go "0xb"} + SIGSEGV) + +(def ^{:doc "SIGTERM" + :added "1.0.1" + :tag Int + :const true + :go "0xf"} + SIGTERM) + +(def ^{:doc "SIGTRAP" + :added "1.0.1" + :tag Int + :const true + :go "0x5"} + SIGTRAP) + (defn chmod "Changes the mode of the named file to mode. If the file is a symbolic link, it changes the mode of the link's target." {:added "1.0" @@ -194,6 +278,28 @@ :go "execute(name, opts)"} [^String name ^Map opts]) +(defn ^Int start + "Starts a new process with the program specified by name. + opts is a map with the same keys as in exec. + Doesn't wait for the process to finish. + Returns the process's PID." + {:added "1.0.1" + :go "startProcess(name, opts)"} + [^String name ^Map opts]) + +(defn kill + "Causes the process with the given PID to exit immediately. + Only kills the process itself, not any other processes it may have started." + {:added "1.0.1" + :go "killProcess(pid)"} + [^Int pid]) + +(defn signal + "Sends signal to the process with the given PID." + {:added "1.0.1" + :go "sendSignal(pid, signal)"} + [^Int pid ^Int signal]) + (defn mkdir "Creates a new directory with the specified name and permission bits." {:added "1.0" diff --git a/std/os/a_os.go b/std/os/a_os.go index dce13689a..ff29f426a 100644 --- a/std/os/a_os.go +++ b/std/os/a_os.go @@ -8,6 +8,18 @@ import ( "os" ) +var SIGABRT_ Int +var SIGALRM_ Int +var SIGFPE_ Int +var SIGHUP_ Int +var SIGILL_ Int +var SIGINT_ Int +var SIGKILL_ Int +var SIGPIPE_ Int +var SIGQUIT_ Int +var SIGSEGV_ Int +var SIGTERM_ Int +var SIGTRAP_ Int var __args__P ProcFn = __args_ var args_ Proc = Proc{Fn: __args__P, Name: "args_", Package: "std/os"} @@ -402,6 +414,23 @@ func __hostname_(_args []Object) Object { return NIL } +var __kill__P ProcFn = __kill_ +var kill_ Proc = Proc{Fn: __kill__P, Name: "kill_", Package: "std/os"} + +func __kill_(_args []Object) Object { + _c := len(_args) + switch { + case _c == 1: + pid := ExtractInt(_args, 0) + _res := killProcess(pid) + return _res + + default: + PanicArity(_c) + } + return NIL +} + var __lchown__P ProcFn = __lchown_ var lchown_ Proc = Proc{Fn: __lchown__P, Name: "lchown_", Package: "std/os"} @@ -756,6 +785,42 @@ func __sh_from_(_args []Object) Object { return NIL } +var __signal__P ProcFn = __signal_ +var signal_ Proc = Proc{Fn: __signal__P, Name: "signal_", Package: "std/os"} + +func __signal_(_args []Object) Object { + _c := len(_args) + switch { + case _c == 2: + pid := ExtractInt(_args, 0) + signal := ExtractInt(_args, 1) + _res := sendSignal(pid, signal) + return _res + + default: + PanicArity(_c) + } + return NIL +} + +var __start__P ProcFn = __start_ +var start_ Proc = Proc{Fn: __start__P, Name: "start_", Package: "std/os"} + +func __start_(_args []Object) Object { + _c := len(_args) + switch { + case _c == 2: + name := ExtractString(_args, 0) + opts := ExtractMap(_args, 1) + _res := startProcess(name, opts) + return MakeInt(_res) + + default: + PanicArity(_c) + } + return NIL +} + var __stat__P ProcFn = __stat_ var stat_ Proc = Proc{Fn: __stat__P, Name: "stat_", Package: "std/os"} @@ -918,7 +983,18 @@ func __user_home_dir_(_args []Object) Object { } func Init() { - + SIGABRT_ = MakeInt(0x6) + SIGALRM_ = MakeInt(0xe) + SIGFPE_ = MakeInt(0x8) + SIGHUP_ = MakeInt(0x1) + SIGILL_ = MakeInt(0x4) + SIGINT_ = MakeInt(0x2) + SIGKILL_ = MakeInt(0x9) + SIGPIPE_ = MakeInt(0xd) + SIGQUIT_ = MakeInt(0x3) + SIGSEGV_ = MakeInt(0xb) + SIGTERM_ = MakeInt(0xf) + SIGTRAP_ = MakeInt(0x5) InternsOrThunks() } diff --git a/std/os/a_os_slow_init.go b/std/os/a_os_slow_init.go index 25e953119..897a05b11 100644 --- a/std/os/a_os_slow_init.go +++ b/std/os/a_os_slow_init.go @@ -14,6 +14,66 @@ func InternsOrThunks() { } osNamespace.ResetMeta(MakeMeta(nil, `Provides a platform-independent interface to operating system functionality.`, "1.0")) + osNamespace.InternVar("SIGABRT", SIGABRT_, + MakeMeta( + nil, + `SIGABRT`, "1.0.1").Plus(MakeKeyword("const"), String{S: "true"}).Plus(MakeKeyword("tag"), String{S: "Int"})) + + osNamespace.InternVar("SIGALRM", SIGALRM_, + MakeMeta( + nil, + `SIGALRM`, "1.0.1").Plus(MakeKeyword("const"), String{S: "true"}).Plus(MakeKeyword("tag"), String{S: "Int"})) + + osNamespace.InternVar("SIGFPE", SIGFPE_, + MakeMeta( + nil, + `SIGFPE`, "1.0.1").Plus(MakeKeyword("const"), String{S: "true"}).Plus(MakeKeyword("tag"), String{S: "Int"})) + + osNamespace.InternVar("SIGHUP", SIGHUP_, + MakeMeta( + nil, + `SIGHUP`, "1.0.1").Plus(MakeKeyword("const"), String{S: "true"}).Plus(MakeKeyword("tag"), String{S: "Int"})) + + osNamespace.InternVar("SIGILL", SIGILL_, + MakeMeta( + nil, + `SIGILL`, "1.0.1").Plus(MakeKeyword("const"), String{S: "true"}).Plus(MakeKeyword("tag"), String{S: "Int"})) + + osNamespace.InternVar("SIGINT", SIGINT_, + MakeMeta( + nil, + `SIGINT`, "1.0.1").Plus(MakeKeyword("const"), String{S: "true"}).Plus(MakeKeyword("tag"), String{S: "Int"})) + + osNamespace.InternVar("SIGKILL", SIGKILL_, + MakeMeta( + nil, + `SIGKILL`, "1.0.1").Plus(MakeKeyword("const"), String{S: "true"}).Plus(MakeKeyword("tag"), String{S: "Int"})) + + osNamespace.InternVar("SIGPIPE", SIGPIPE_, + MakeMeta( + nil, + `SIGPIPE`, "1.0.1").Plus(MakeKeyword("const"), String{S: "true"}).Plus(MakeKeyword("tag"), String{S: "Int"})) + + osNamespace.InternVar("SIGQUIT", SIGQUIT_, + MakeMeta( + nil, + `SIGQUIT`, "1.0.1").Plus(MakeKeyword("const"), String{S: "true"}).Plus(MakeKeyword("tag"), String{S: "Int"})) + + osNamespace.InternVar("SIGSEGV", SIGSEGV_, + MakeMeta( + nil, + `SIGSEGV`, "1.0.1").Plus(MakeKeyword("const"), String{S: "true"}).Plus(MakeKeyword("tag"), String{S: "Int"})) + + osNamespace.InternVar("SIGTERM", SIGTERM_, + MakeMeta( + nil, + `SIGTERM`, "1.0.1").Plus(MakeKeyword("const"), String{S: "true"}).Plus(MakeKeyword("tag"), String{S: "Int"})) + + osNamespace.InternVar("SIGTRAP", SIGTRAP_, + MakeMeta( + nil, + `SIGTRAP`, "1.0.1").Plus(MakeKeyword("const"), String{S: "true"}).Plus(MakeKeyword("tag"), String{S: "Int"})) + osNamespace.InternVar("args", args_, MakeMeta( NewListFrom(NewVectorFrom()), @@ -147,6 +207,12 @@ func InternsOrThunks() { NewListFrom(NewVectorFrom()), `Returns the host name reported by the kernel.`, "1.0").Plus(MakeKeyword("tag"), String{S: "String"})) + osNamespace.InternVar("kill", kill_, + MakeMeta( + NewListFrom(NewVectorFrom(MakeSymbol("pid"))), + `Causes the process with the given PID to exit immediately. + Only kills the process itself, not any other processes it may have started.`, "1.0.1")) + osNamespace.InternVar("lchown", lchown_, MakeMeta( NewListFrom(NewVectorFrom(MakeSymbol("name"), MakeSymbol("uid"), MakeSymbol("gid"))), @@ -272,6 +338,19 @@ func InternsOrThunks() { :out - string capturing stdout of the program, :err - string capturing stderr of the program.`, "1.0")) + osNamespace.InternVar("signal", signal_, + MakeMeta( + NewListFrom(NewVectorFrom(MakeSymbol("pid"), MakeSymbol("signal"))), + `Sends signal to the process with the given PID.`, "1.0.1")) + + osNamespace.InternVar("start", start_, + MakeMeta( + NewListFrom(NewVectorFrom(MakeSymbol("name"), MakeSymbol("opts"))), + `Starts a new process with the program specified by name. + opts is a map with the same keys as in exec. + Doesn't wait for the process to finish. + Returns the process's PID.`, "1.0.1").Plus(MakeKeyword("tag"), String{S: "Int"})) + osNamespace.InternVar("stat", stat_, MakeMeta( NewListFrom(NewVectorFrom(MakeSymbol("filename"))), diff --git a/std/os/os_native.go b/std/os/os_native.go index e8bd8381e..a06ce9379 100644 --- a/std/os/os_native.go +++ b/std/os/os_native.go @@ -1,10 +1,13 @@ package os import ( + "bytes" "io" "io/ioutil" "os" + "os/exec" "strings" + "syscall" . "github.com/candid82/joker/core" ) @@ -35,11 +38,51 @@ func commandArgs() Object { const defaultFailedCode = 127 // seen from 'sh no-such-file' on OS X and Ubuntu -func execute(name string, opts Map) Object { - var dir string - var args []string - var stdin io.Reader - var stdout, stderr io.Writer +func startProcess(name string, opts Map) int { + dir, args, stdin, stdout, stderr := parseExecOpts(opts) + + cmd := exec.Command(name, args...) + cmd.Dir = dir + cmd.Stdin = stdin + + var stdoutBuffer, stderrBuffer bytes.Buffer + if stdout != nil { + cmd.Stdout = stdout + } else { + cmd.Stdout = &stdoutBuffer + } + if stderr != nil { + cmd.Stderr = stderr + } else { + cmd.Stderr = &stderrBuffer + } + + err := cmd.Start() + PanicOnErr(err) + + return cmd.Process.Pid +} + +func sendSignal(pid, signal int) Object { + p, err := os.FindProcess(pid) + PanicOnErr(err) + err = p.Signal(syscall.Signal(signal)) + PanicOnErr(err) + return NIL +} + +func killProcess(pid int) Object { + p, err := os.FindProcess(pid) + PanicOnErr(err) + err = p.Kill() + PanicOnErr(err) + // Wait to avoid zombie child processes. + // Ignore result and error (which may occur if p is not a child process) + p.Wait() + return NIL +} + +func parseExecOpts(opts Map) (dir string, args []string, stdin io.Reader, stdout, stderr io.Writer) { if ok, dirObj := opts.Get(MakeKeyword("dir")); ok && !dirObj.Equals(NIL) { dir = EnsureObjectIsString(dirObj, "dir: %s").S } @@ -92,6 +135,11 @@ func execute(name string, opts Map) Object { panic(RT.NewError("stderr option must be an IOWriter, got " + stderrObj.GetType().ToString(false))) } } + return +} + +func execute(name string, opts Map) Object { + dir, args, stdin, stdout, stderr := parseExecOpts(opts) return sh(dir, stdin, stdout, stderr, name, args) }