diff --git a/README.md b/README.md index 30563b4..65a6146 100644 --- a/README.md +++ b/README.md @@ -185,13 +185,13 @@ While diverging from some "purity" aspects, Clyx is a decent choice for newcomer ### What do I need to do to create my own Clyx implementation? -Just port the core interpreter along with the 50 primitive words, stubbing out the words that cannot be supported by the target platform if there are any, so that if a program that e.g. uses `nopen` runs on a non-networked host environment, or a program that uses `hseval` runs on a C-based interpreter, then the user would get a clear message that an unsupported core primitive word has been encountered and the program has been terminated because of that. +Just port the core interpreter along with the 50 primitive words, stubbing out the words that cannot be supported by the target platform if there are any, so that if a program that e.g. uses `nopen` runs on a non-networked host environment, then the user would get a clear message that an unsupported core primitive word has been encountered and the program has been terminated because of that. Apart from that, your implementation needs to be able to load `lib.clx` (standard library source) upon initialization, and `clyx_repl.clx` upon passing no parameters (if it is CLI-based). ### Doesn't `hseval` primitive make Clyx programs non-portable across different implementations? -Yes, it does, because it works differently across different host languages (and Clyx implementations like `clyx-go` are allowed to not support it at all), so it must only be used as a last resort when any other attempts to interact with the outside world are unsuccessful. Additionally, using `hseval` introduces extra security risks. Nevertheless, this primitive had been added in order to enable some host-specific interoperability that would otherwise be impossible or very cumbersome to implement, such as browser-based deployments. +Yes, it does, because it works differently across different host environments and languages (e.g. Python expression evaluation vs. Go system shell command execution), so it must only be used as a last resort when any other attempts to interact with the outside world are unsuccessful. Additionally, using `hseval` introduces extra security risks. Nevertheless, this primitive had been added in order to enable some host-specific interoperability that would otherwise be impossible or very cumbersome to implement, such as browser-based deployments. In other words, `hseval` is sometimes very useful in very specific cases, but please avoid using it unless you have a really good reason to use it. diff --git a/clyx-go/clyx.go b/clyx-go/clyx.go index 75d22f5..2dd249f 100644 --- a/clyx-go/clyx.go +++ b/clyx-go/clyx.go @@ -11,6 +11,8 @@ import ( "math/rand" "net" "os" + "os/exec" + "runtime" "strconv" "strings" "unicode/utf8" @@ -474,7 +476,31 @@ func NewClyx(libfname string, libCode string) *Clyx { } c.stack = append(c.stack, sb.String()) } - c.words["hseval"] = func() { fmt.Fprintln(os.Stderr, "Error: unsupported word"); os.Exit(1) } + c.words["hseval"] = func() { + cmdStr := c.pop().(string) + var shell, flag string + if runtime.GOOS == "windows" { + shell = "cmd" + flag = "/c" + } else { + shell = "sh" + flag = "-c" + } + cmd := exec.Command(shell, flag, cmdStr) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Stdin = os.Stdin + err := cmd.Run() + var exitCode int64 = 0 + if err != nil { + if exitError, ok := err.(*exec.ExitError); ok { + exitCode = int64(exitError.ExitCode()) + } else { + exitCode = -1 + } + } + c.stack = append(c.stack, exitCode) + } c.words["chr"] = func() { c.stack = append(c.stack, string(rune(toInt(c.pop())))) } regMath := func(name string, f func(float64) float64) { diff --git a/clyx_manual.md b/clyx_manual.md index 1ee128a..e3d62a2 100644 --- a/clyx_manual.md +++ b/clyx_manual.md @@ -313,11 +313,13 @@ Any line fragment starting with `#` to the end of the physical source line is re **N.B.**: on the platforms that don't support specifying process exit codes, this primitive may either do nothing, exit the program the usual way supported by the platform or trigger a global interpreter restart. -### `hseval` (Host Language Evaluation) +### `hseval` (Host Evaluation / Execution) - **Stack Effect**: `( expr -- val )` -- **Description**: Pops the string `expr` from the stack, evaluates it as an expression in the host language (e.g., Python), and pushes the resulting value back onto the stack. +- **Description**: Pops the string `expr` from the stack and evaluates or executes it in the host environment. + - In the Python implementation, it evaluates `expr` as a Python expression and pushes the resulting value back onto the stack. + - In the Go implementation, it executes `expr` as a system shell command, redirects its stdin/stdout/stderr to the current process, and pushes the command's exit code back onto the stack. -**N.B.**: this primitive may not be supported on all targets. Only use as a last resort in controlled and/or sandboxed environments. Never pass user input to this primitive. +**N.B.**: this primitive may not be supported on all targets, and its behavior varies by implementation. Only use as a last resort in controlled and/or sandboxed environments. Never pass unvalidated user input to this primitive. ---