From 69cbdf9fdc70c15d85eb4cbc641a77df52195d88 Mon Sep 17 00:00:00 2001 From: playX Date: Fri, 23 Jul 2021 18:04:36 +0300 Subject: [PATCH] v.gen.js, os_js: port the OS module to JS (#10872) --- vlib/builtin/js/array.js.v | 7 ++ vlib/os_js/environment.js.v | 31 +++++++ vlib/os_js/file.js.v | 130 +++++++++++++++++++++++++++ vlib/os_js/os.js.v | 95 ++++++++++++++++++++ vlib/os_js/os.v | 13 +++ vlib/os_js/process.js.v | 173 ++++++++++++++++++++++++++++++++++++ vlib/v/checker/checker.v | 2 +- vlib/v/gen/js/js.v | 7 +- 8 files changed, 454 insertions(+), 4 deletions(-) create mode 100644 vlib/os_js/environment.js.v create mode 100644 vlib/os_js/file.js.v create mode 100644 vlib/os_js/os.js.v create mode 100644 vlib/os_js/os.v create mode 100644 vlib/os_js/process.js.v diff --git a/vlib/builtin/js/array.js.v b/vlib/builtin/js/array.js.v index 8c34829ddd..20f21ff4bd 100644 --- a/vlib/builtin/js/array.js.v +++ b/vlib/builtin/js/array.js.v @@ -104,6 +104,13 @@ pub fn (mut a array) insert(i int, val voidptr) { #a.arr.splice(i,0,val) } +pub fn (mut a array) join(separator string) string { + mut res := '' + #res = new builtin.string(a.arr.join(separator +'')); + + return res +} + fn (a array) push(val voidptr) { #a.arr.push(val) } diff --git a/vlib/os_js/environment.js.v b/vlib/os_js/environment.js.v new file mode 100644 index 0000000000..0f69f094c5 --- /dev/null +++ b/vlib/os_js/environment.js.v @@ -0,0 +1,31 @@ +module os_js + +// setenv sets the value of an environment variable with `name` to `value`. +pub fn setenv(key string, val string, overwrite bool) { + #if ($process.env[key] && !(overwrite.valueOf())) return; + #$process.env[key] = val + ''; +} + +// `getenv` returns the value of the environment variable named by the key. +pub fn getenv(key string) string { + mut res := '' + #if ($process.env[key]) res = new builtin.string($process.env[key]) + + return res +} + +// unsetenv clears an environment variable with `name`. +pub fn unsetenv(name string) int { + #$process.env[name] = "" + + return 1 +} + +pub fn environ() map[string]string { + mut res := map[string]string{} + #for (const key in $process.env) { + #res.map.set(key,$process.env[key]) + #} + + return res +} diff --git a/vlib/os_js/file.js.v b/vlib/os_js/file.js.v new file mode 100644 index 0000000000..a7541c52d3 --- /dev/null +++ b/vlib/os_js/file.js.v @@ -0,0 +1,130 @@ +module os_js + +pub struct File { +pub: + fd int +pub mut: + is_opened bool +} + +#const $buffer = require('buffer'); + +// todo(playX): __as_cast is broken here +/* +pub struct ErrFileNotOpened { + msg string = 'os: file not opened' + code int +} +pub struct ErrSizeOfTypeIs0 { + msg string = 'os: size of type is 0' + code int +} +fn error_file_not_opened() IError { + return IError(&ErrFileNotOpened{}) +} +fn error_size_of_type_0() IError { + return IError(&ErrSizeOfTypeIs0{}) +} +*/ +pub fn open_file(path string, mode string, options ...int) ?File { + mut res := File{} + $if js_node { + #if (!options) { options = new array([]); } + #let permissions = 0o666 + #if (options.arr.length > 0) { permissions = options.arr[0]; } + #try { + #res.fd = new int($fs.openSync(''+path,''+mode,permissions)) + #} catch (e) { + #return builtin.error('' + e); + #} + + res.is_opened = true + } $else { + error('cannot open file on non NodeJS runtime') + } + return res +} + +// open tries to open a file for reading and returns back a read-only `File` object. +pub fn open(path string) ?File { + f := open_file(path, 'r') ? + return f +} + +pub fn create(path string) ?File { + f := open_file(path, 'w') ? + return f +} + +pub fn stdin() File { + return File{ + fd: 0 + is_opened: true + } +} + +pub fn stdout() File { + return File{ + fd: 1 + is_opened: true + } +} + +pub fn stderr() File { + return File{ + fd: 2 + is_opened: true + } +} + +pub fn (f &File) read(mut buf []byte) ?int { + if buf.len == 0 { + return 0 + } + mut nbytes := 0 + #try { + #let buffer = $fs.readFileSync(f.fd.valueOf()); + # + #for (const val of buffer.values()) { buf.arr[nbytes++] = val; } + #} + #catch (e) { return builtin.error('' + e); } + + return nbytes +} + +pub fn (mut f File) write(buf []byte) ?int { + if !f.is_opened { + return error('file is not opened') + } + mut nbytes := 0 + #const b = $buffer.Buffer.from(buf.arr.map((x) => x.valueOf())) + #try { $fs.writeSync(f.fd.valueOf(),b,0,buf.len.valueOf(),0); } catch (e) { return builtin.error('' + e); } + + return nbytes +} + +// writeln writes the string `s` into the file, and appends a \n character. +// It returns how many bytes were written, including the \n character. +pub fn (mut f File) writeln(s string) ?int { + mut nbytes := f.write(s.bytes()) ? + nbytes += f.write('\n'.bytes()) ? + return nbytes +} + +pub fn (mut f File) write_to(pos u64, buf []byte) ?int { + if !f.is_opened { + return error('file is not opened') + } + mut nbytes := 0 + #const b = $buffer.Buffer.from(buf.arr.map((x) => x.valueOf())) + #try { $fs.writeSync(f.fd.valueOf(),b,0,buf.len.valueOf(),pos.valueOf()); } catch (e) { return builtin.error('' + e); } + + return nbytes +} + +// write_string writes the string `s` into the file +// It returns how many bytes were actually written. +pub fn (mut f File) write_string(s string) ?int { + nbytes := f.write(s.bytes()) ? + return nbytes +} diff --git a/vlib/os_js/os.js.v b/vlib/os_js/os.js.v new file mode 100644 index 0000000000..2fb088a865 --- /dev/null +++ b/vlib/os_js/os.js.v @@ -0,0 +1,95 @@ +module os_js + +#const $fs = require('fs'); +#const $path = require('path'); + +pub const ( + args = []string{} +) + +$if js_node { + #$process.argv.forEach(function(val,index) { args.arr[index] = new string(val); }) +} + +// real_path returns the full absolute path for fpath, with all relative ../../, symlinks and so on resolved. +// See http://pubs.opengroup.org/onlinepubs/9699919799/functions/realpath.html +// Also https://insanecoding.blogspot.com/2007/11/pathmax-simply-isnt.html +// and https://insanecoding.blogspot.com/2007/11/implementing-realpath-in-c.html +// NB: this particular rabbit hole is *deep* ... +pub fn real_path(fpath string) string { + $if js_node { + mut res := '' + #res = new string( $fs.realpathSync(fpath)) + + return res + } $else { + return fpath + } +} + +// flush will flush the stdout buffer. +pub fn flush() { + $if js_node { + #$process.stdout.write('') + } +} + +// chmod change file access attributes of `path` to `mode`. +// Octals like `0o600` can be used. +pub fn chmod(path string, mode int) { + $if js_node { + #$fs.chmodSync(''+path,mode.valueOf()) + } +} + +// chown changes the owner and group attributes of `path` to `owner` and `group`. +// Octals like `0o600` can be used. +pub fn chown(path string, owner int, group int) { + $if js_node { + #$fs.chownSync(''+path,owner.valueOf(),group.valueOf()) + } +} + +pub fn temp_dir() string { + mut res := '' + $if js_node { + #res = new builtin.string($os.tmpdir()) + } + return res +} + +pub fn home_dir() string { + mut res := '' + $if js_node { + #res = new builtin.string($os.homedir()) + } + return res +} + +// join_path returns a path as string from input string parameter(s). +pub fn join_path(base string, dirs ...string) string { + mut result := []string{} + result << base.trim_right('\\/') + for d in dirs { + result << d + } + mut path_sep := '' + #path_sep = $path.sep; + + res := result.join(path_sep) + return res +} + +pub fn execute(cmd string) Result { + mut exit_code := 0 + mut stdout := '' + #let commands = cmd.str.split(' '); + #let output = $child_process.spawnSync(commands[0],commands.slice(1,commands.length)); + #exit_code = new builtin.int(output.status) + #stdout = new builtin.string(output.stdout + '') + + return Result{ + exit_code: exit_code + output: stdout + } +} diff --git a/vlib/os_js/os.v b/vlib/os_js/os.v new file mode 100644 index 0000000000..8b4e160ea4 --- /dev/null +++ b/vlib/os_js/os.v @@ -0,0 +1,13 @@ +module os_js + +pub const ( + // todo(playX): NodeJS does not seem to have any path limit? + max_path_len = 4096 +) + +pub struct Result { +pub: + exit_code int + output string + // stderr string // TODO +} diff --git a/vlib/os_js/process.js.v b/vlib/os_js/process.js.v new file mode 100644 index 0000000000..392c4a797a --- /dev/null +++ b/vlib/os_js/process.js.v @@ -0,0 +1,173 @@ +module os_js + +#const $child_process = require('child_process') + +// ProcessState.not_started - the process has not yet started +// ProcessState.running - the process is currently running +// ProcessState.stopped - the process was running, but was stopped temporarily +// ProcessState.exited - the process has finished/exited +// ProcessState.aborted - the process was terminated by a signal +// ProcessState.closed - the process resources like opened file descriptors were freed/discarded, final state. +pub enum ProcessState { + not_started + running + stopped + exited + aborted + closed +} + +// todo(playX): fix reference member access in JS backend +// [heap] +pub struct Process { +pub: + filename string +pub mut: + pid voidptr + code int = -1 + status ProcessState = .not_started + // the current status of the process + err string // if the process fails, contains the reason why + args []string // the arguments that the command takes + env_is_custom bool // true, when the environment was customized with .set_environment + env []string // the environment with which the process was started (list of 'var=val') + use_stdio_ctl bool // when true, then you can use p.stdin_write(), p.stdout_slurp() and p.stderr_slurp() + use_pgroup bool // when true, the process will create a new process group, enabling .signal_pgkill() + stdio_fd [3]int // the stdio file descriptors for the child process, used only by the nix implementation +} + +// new_process - create a new process descriptor +// NB: new does NOT start the new process. +// That is done because you may want to customize it first, +// by calling different set_ methods on it. +// In order to start it, call p.run() or p.wait() +pub fn new_process(filename string) Process { + return Process{ + filename: filename + stdio_fd: [-1, -1, -1]! + } +} + +// set_args - set the arguments for the new process +pub fn (mut p Process) set_args(pargs []string) { + if p.status != .not_started { + return + } + p.args = pargs + return +} + +// set_environment - set a custom environment variable mapping for the new process +pub fn (mut p Process) set_environment(envs map[string]string) { + if p.status != .not_started { + return + } + p.env_is_custom = true + p.env = []string{} + for k, v in envs { + p.env << '$k=$v' + } + return +} + +fn (mut p Process) spawn_internal() { + #p.pid = $child_process.spawn( + #p.filename+'', + #p.args.arr.map((x) => x.valueOf() + ''), + #{ + #env: (p.env_is_custom ? p.env : $process.env), + #}) + #p.pid.on('error', function (err) { builtin.panic('Failed to start subprocess') }) + + p.status = .running + // todo(playX): stderr,stdin + if p.use_stdio_ctl { + #p.pid.stdout.pipe(process.stdout) + #p.pid.stdin.pipe(process.stdin) + #p.pid.stderr.pipe(process.stderr) + } +} + +pub fn (mut p Process) run() { + if p.status != .not_started { + return + } + p.spawn_internal() + return +} + +pub fn (mut p Process) signal_kill() { + if p.status !in [.running, .stopped] { + return + } + #p.pid.kill('SIGKILL'); + + p.status = .aborted +} + +pub fn (mut p Process) signal_stop() { + if p.status !in [.running, .stopped] { + return + } + #p.pid.kill('SIGSTOP'); + + p.status = .aborted +} + +pub fn (mut p Process) signal_continue() { + if p.status != .stopped { + return + } + #p.pid.kill('SIGCONT'); + + p.status = .running + return +} + +pub fn (mut p Process) wait() { + if p.status == .not_started { + p.spawn_internal() + } + if p.status !in [.running, .stopped] { + return + } + + p.wait_internal() + return +} + +fn (mut p Process) wait_internal() { + #p.pid.on('exit', function (code) { console.log(code) }) +} + +pub fn (mut p Process) set_redirect_stdio() { + p.use_stdio_ctl = true + return +} + +pub fn (mut p Process) stdin_write(s string) { + p.check_redirection_call('stdin_write') + #p.pid.stdin.write(s) +} + +// todo(playX): probably does not work + +// will read from stdout pipe, will only return when EOF (end of file) or data +// means this will block unless there is data +pub fn (mut p Process) stdout_slurp() string { + p.check_redirection_call('stdout_slurp') + mut res := '' + #p.pid.stdout.on('data', function (data) { res = new builtin.string(data) }) + + return res +} + +// _check_redirection_call - should be called just by stdxxx methods +fn (mut p Process) check_redirection_call(fn_name string) { + if !p.use_stdio_ctl { + panic('Call p.set_redirect_stdio() before calling p.$fn_name') + } + if p.status == .not_started { + panic('Call p.${fn_name}() after you have called p.run()') + } +} diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 4811c4a030..0640e64367 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -26,7 +26,7 @@ const ( valid_comp_if_platforms = ['amd64', 'i386', 'aarch64', 'arm64', 'arm32', 'rv64', 'rv32'] valid_comp_if_cpu_features = ['x64', 'x32', 'little_endian', 'big_endian'] valid_comp_if_other = ['js', 'debug', 'prod', 'test', 'glibc', 'prealloc', - 'no_bounds_checking', 'freestanding', 'threads', 'js_browser', 'js_freestanding'] + 'no_bounds_checking', 'freestanding', 'threads', 'js_node', 'js_browser', 'js_freestanding'] valid_comp_not_user_defined = all_valid_comptime_idents() array_builtin_methods = ['filter', 'clone', 'repeat', 'reverse', 'map', 'slice', 'sort', 'contains', 'index', 'wait', 'any', 'all', 'first', 'last', 'pop'] diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v index a81313f52e..2e8d230e7f 100644 --- a/vlib/v/gen/js/js.v +++ b/vlib/v/gen/js/js.v @@ -126,9 +126,10 @@ pub fn gen(files []&ast.File, table &ast.Table, pref &pref.Preferences) string { // builtin types if g.file.mod.name == 'builtin' && !g.generated_builtin { g.gen_builtin_type_defs() - g.writeln('Object.defineProperty(array.prototype,"len", { get: function() {return this.arr.length;}, set: function(l) { this.arr.length = l; } }); ') - g.writeln('Object.defineProperty(map.prototype,"len", { get: function() {return this.map.length;}, set: function(l) { this.map.length = l; } }); ') - g.writeln('Object.defineProperty(array.prototype,"length", { get: function() {return this.arr.length;}, set: function(l) { this.arr.length = l; } }); ') + g.writeln('Object.defineProperty(array.prototype,"len", { get: function() {return new builtin.int(this.arr.length);}, set: function(l) { this.arr.length = l.valueOf(); } }); ') + g.writeln('Object.defineProperty(string.prototype,"len", { get: function() {return new builtin.int(this.str.length);}, set: function(l) {/* ignore */ } }); ') + g.writeln('Object.defineProperty(map.prototype,"len", { get: function() {return new builtin.int(this.map.length);}, set: function(l) { this.map.length = l.valueOf(); } }); ') + g.writeln('Object.defineProperty(array.prototype,"length", { get: function() {return new builtin.int(this.arr.length);}, set: function(l) { this.arr.length = l.valueOf(); } }); ') g.generated_builtin = true }