From 109d5d584747fb473cec28f7b22dad1dbbc0f125 Mon Sep 17 00:00:00 2001 From: playX Date: Wed, 25 Aug 2021 14:40:53 +0300 Subject: [PATCH] js: `os` now compiles to the JS backend, more builtins & minor codegen fixes (#11302) --- vlib/builtin/js/builtin.v | 59 +++++++++--- vlib/builtin/js/byte.js.v | 10 +- vlib/builtin/js/string.js.v | 165 ++++++++++++++++++++++++++++++++ vlib/builtin/string.v | 2 + vlib/{os_js => os}/file.js.v | 8 +- vlib/os/os.c.v | 21 +++++ vlib/os/os.js.v | 2 +- vlib/os/os.v | 21 +---- vlib/os/os_js.js.v | 98 +++++++++++++++++-- vlib/os_js/environment.js.v | 31 ------ vlib/os_js/os.js.v | 95 ------------------- vlib/os_js/os.v | 13 --- vlib/os_js/process.js.v | 173 ---------------------------------- vlib/v/gen/js/builtin_types.v | 2 +- vlib/v/gen/js/js.v | 148 ++++++++++++++++++++++++++--- 15 files changed, 479 insertions(+), 369 deletions(-) rename vlib/{os_js => os}/file.js.v (95%) create mode 100644 vlib/os/os.c.v delete mode 100644 vlib/os_js/environment.js.v delete mode 100644 vlib/os_js/os.js.v delete mode 100644 vlib/os_js/os.v delete mode 100644 vlib/os_js/process.js.v diff --git a/vlib/builtin/js/builtin.v b/vlib/builtin/js/builtin.v index ce9093af98..5f97a29226 100644 --- a/vlib/builtin/js/builtin.v +++ b/vlib/builtin/js/builtin.v @@ -32,12 +32,38 @@ struct Option { err Error } +// IError holds information about an error instance +pub interface IError { + msg string + code int +} + +// Error is the default implementation of IError, that is returned by e.g. `error()` pub struct Error { pub: msg string code int } +const none__ = IError(&None__{}) + +struct None__ { + msg string + code int +} + +fn (_ None__) str() string { + return 'none' +} + +pub fn (err IError) str() string { + return match err { + None__ { 'none' } + Error { err.msg } + else { '$err.type_name(): $err.msg' } + } +} + pub fn (o Option) str() string { if o.state == 0 { return 'Option{ ok }' @@ -48,21 +74,28 @@ pub fn (o Option) str() string { return 'Option{ error: "$o.err" }' } -pub fn error(s string) Option { - return Option{ - state: 2 - err: Error{ - msg: s - } +[if trace_error ?] +fn trace_error(x string) { + eprintln('> ${@FN} | $x') +} + +// error returns a default error instance containing the error given in `message`. +// Example: `if ouch { return error('an error occurred') }` +[inline] +pub fn error(message string) IError { + trace_error(message) + return &Error{ + msg: message } } -pub fn error_with_code(s string, code int) Option { - return Option{ - state: 2 - err: Error{ - msg: s - code: code - } +// error_with_code returns a default error instance containing the given `message` and error `code`. +// `if ouch { return error_with_code('an error occurred', 1) }` +[inline] +pub fn error_with_code(message string, code int) IError { + // trace_error('$message | code: $code') + return &Error{ + msg: message + code: code } } diff --git a/vlib/builtin/js/byte.js.v b/vlib/builtin/js/byte.js.v index a2508db399..af1803b29d 100644 --- a/vlib/builtin/js/byte.js.v +++ b/vlib/builtin/js/byte.js.v @@ -2,7 +2,15 @@ module builtin pub fn (b byte) is_space() bool { mut result := false - #result = /^\s*$/.test(String.fromCharCode(b)) + #result.val = /^\s*$/.test(String.fromCharCode(b)) + + return result +} + +pub fn (c byte) is_letter() bool { + result := false + + #result.val = (c.val >= `a`.charCodeAt() && c.val <= `z`.charCodeAt()) || (c.val >= `A`.charCodeAt() && c.val <= `Z`.charCodeAt()) return result } diff --git a/vlib/builtin/js/string.js.v b/vlib/builtin/js/string.js.v index f209fb8d6d..ec95bd6bb6 100644 --- a/vlib/builtin/js/string.js.v +++ b/vlib/builtin/js/string.js.v @@ -541,3 +541,168 @@ pub fn (s string) split_nth(delim string, nth int) []string { } } } + +struct RepIndex { + idx int + val_idx int +} + +// replace_each replaces all occurences of the string pairs given in `vals`. +// Example: assert 'ABCD'.replace_each(['B','C/','C','D','D','C']) == 'AC/DC' +[direct_array_access] +pub fn (s string) replace_each(vals []string) string { + if s.len == 0 || vals.len == 0 { + return s.clone() + } + + if vals.len % 2 != 0 { + eprintln('string.replace_each(): odd number of strings') + return s.clone() + } + + // `rep` - string to replace + // `with_` - string to replace with_ + // Remember positions of all rep strings, and calculate the length + // of the new string to do just one allocation. + + mut idxs := []RepIndex{} + mut idx := 0 + s_ := s.clone() + #function setCharAt(str,index,chr) { + #if(index > str.length-1) return str; + #return str.substring(0,index) + chr + str.substring(index+1); + #} + + for rep_i := 0; rep_i < vals.len; rep_i += 2 { + rep := vals[rep_i] + mut with_ := vals[rep_i + 1] + with_ = with_ + for { + idx = s_.index_after(rep, idx) + if idx == -1 { + break + } + + for i in 0 .. rep.len { + mut j_ := i + j_ = j_ + #s_.str = setCharAt(s_.str,idx + i, String.fromCharCode(127)) + } + + idxs << RepIndex{ + idx: idx + val_idx: rep_i + } + idx += rep.len + } + } + + if idxs.len == 0 { + return s.clone() + } + + idxs.sort(a.idx < b.idx) + + mut b := '' + mut idx_pos := 0 + mut cur_idx := idxs[idx_pos] + mut b_i := 0 + for i := 0; i < s.len; i++ { + if i == cur_idx.idx { + rep := vals[cur_idx.val_idx] + with_ := vals[cur_idx.val_idx + 1] + for j in 0 .. with_.len { + mut j_ := j + j_ = j_ + #b.str = setCharAt(b.str,b_i, with_.str[j]) + //#b.str[b_i] = with_.str[j] + b_i++ + } + i += rep.len - 1 + idx_pos++ + if idx_pos < idxs.len { + cur_idx = idxs[idx_pos] + } + } else { + #b.str = setCharAt(b.str,b_i,s.str[i]) //b.str[b_i] = s.str[i] + b_i++ + } + } + + return b +} + +// last_index returns the position of the last occurence of the input string. +fn (s string) last_index_(p string) int { + if p.len > s.len || p.len == 0 { + return -1 + } + mut i := s.len - p.len + for i >= 0 { + mut j := 0 + for j < p.len && s[i + j] == p[j] { + j++ + } + if j == p.len { + return i + } + i-- + } + return -1 +} + +// last_index returns the position of the last occurence of the input string. +pub fn (s string) last_index(p string) ?int { + idx := s.last_index_(p) + if idx == -1 { + return none + } + return idx +} + +pub fn (s string) trim_space() string { + res := '' + #res.str = s.str.trim() + + return res +} + +pub fn (s string) index_after(p string, start int) int { + if p.len > s.len { + return -1 + } + + mut strt := start + if start < 0 { + strt = 0 + } + if start >= s.len { + return -1 + } + mut i := strt + + for i < s.len { + mut j := 0 + mut ii := i + for j < p.len && s[ii] == p[j] { + j++ + ii++ + } + + if j == p.len { + return i + } + i++ + } + return -1 +} + +pub fn (s string) split_into_lines() []string { + mut res := []string{} + #let i = 0 + #s.str.split('\n').forEach((str) => { + #res.arr[i] = new string(str); + #}) + + return res +} diff --git a/vlib/builtin/string.v b/vlib/builtin/string.v index 1b56a4e24f..7caf9e5063 100644 --- a/vlib/builtin/string.v +++ b/vlib/builtin/string.v @@ -359,6 +359,7 @@ pub fn (s string) replace_each(vals []string) string { with := vals[rep_i + 1] for { idx = s_.index_after(rep, idx) + if idx == -1 { break } @@ -374,6 +375,7 @@ pub fn (s string) replace_each(vals []string) string { idx: idx val_idx: rep_i } + idx += rep.len new_len += with.len - rep.len } diff --git a/vlib/os_js/file.js.v b/vlib/os/file.js.v similarity index 95% rename from vlib/os_js/file.js.v rename to vlib/os/file.js.v index a7541c52d3..abaeeab2b4 100644 --- a/vlib/os_js/file.js.v +++ b/vlib/os/file.js.v @@ -1,4 +1,4 @@ -module os_js +module os pub struct File { pub: @@ -128,3 +128,9 @@ pub fn (mut f File) write_string(s string) ?int { nbytes := f.write(s.bytes()) ? return nbytes } + +pub fn (mut f File) close() { + #f.valueOf().fd.close() +} + +pub fn (mut f File) write_full_buffer(s voidptr, buffer_len size_t) ? {} diff --git a/vlib/os/os.c.v b/vlib/os/os.c.v new file mode 100644 index 0000000000..b8c5070704 --- /dev/null +++ b/vlib/os/os.c.v @@ -0,0 +1,21 @@ +module os + +// write_file_array writes the data in `buffer` to a file in `path`. +pub fn write_file_array(path string, buffer array) ? { + mut f := create(path) ? + unsafe { f.write_full_buffer(buffer.data, size_t(buffer.len * buffer.element_size)) ? } + f.close() +} + +pub fn glob(patterns ...string) ?[]string { + mut matches := []string{} + for pattern in patterns { + native_glob_pattern(pattern, mut matches) ? + } + matches.sort() + return matches +} + +pub const ( + args = []string{} +) diff --git a/vlib/os/os.js.v b/vlib/os/os.js.v index a1342f466d..6ba3c77a48 100644 --- a/vlib/os/os.js.v +++ b/vlib/os/os.js.v @@ -4,9 +4,9 @@ module os #const $path = require('path'); pub const ( - args = []string{} path_delimiter = '/' path_separator = '/' + args = []string{} ) $if js_node { diff --git a/vlib/os/os.v b/vlib/os/os.v index 47ea85b0c3..4574156324 100644 --- a/vlib/os/os.v +++ b/vlib/os/os.v @@ -4,7 +4,6 @@ module os pub const ( - args = []string{} max_path_len = 4096 wd_at_startup = getwd() ) @@ -344,22 +343,15 @@ pub fn write_file(path string, text string) ? { f.close() } -// write_file_array writes the data in `buffer` to a file in `path`. -pub fn write_file_array(path string, buffer array) ? { - mut f := create(path) ? - unsafe { f.write_full_buffer(buffer.data, size_t(buffer.len * buffer.element_size)) ? } - f.close() -} - // executable_fallback is used when there is not a more platform specific and accurate implementation. // It relies on path manipulation of os.args[0] and os.wd_at_startup, so it may not work properly in // all cases, but it should be better, than just using os.args[0] directly. fn executable_fallback() string { - if os.args.len == 0 { + if args.len == 0 { // we are early in the bootstrap, os.args has not been initialized yet :-| return '' } - mut exepath := os.args[0] + mut exepath := args[0] $if windows { if !exepath.contains('.exe') { exepath += '.exe' @@ -639,12 +631,3 @@ pub fn execute_or_exit(cmd string) Result { } return res } - -pub fn glob(patterns ...string) ?[]string { - mut matches := []string{} - for pattern in patterns { - native_glob_pattern(pattern, mut matches) ? - } - matches.sort() - return matches -} diff --git a/vlib/os/os_js.js.v b/vlib/os/os_js.js.v index c198ce95c4..008a7c13cb 100644 --- a/vlib/os/os_js.js.v +++ b/vlib/os/os_js.js.v @@ -15,22 +15,25 @@ pub fn mkdir(path string) ?bool { pub fn is_dir(path string) bool { res := false - #res.val = $fs.existsSync(path,str) && $fs.lstatSync(path.str).isDirectory() - + $if js_node { + #res.val = $fs.existsSync(path,str) && $fs.lstatSync(path.str).isDirectory() + } return res } pub fn is_link(path string) bool { res := false - #res.val = $fs.existsSync(path.str) && $fs.lstatSync(path.str).isSymbolicLink() - + $if js_node { + #res.val = $fs.existsSync(path.str) && $fs.lstatSync(path.str).isSymbolicLink() + } return res } pub fn exists(path string) bool { res := false - #res.val = $fs.existsSync(path.str) - + $if js_node { + #res.val = $fs.existsSync(path.str) + } return res } @@ -40,8 +43,85 @@ pub fn ls(path string) ?[]string { } result := []string{} - #let i = 0 - #$fs.readdirSync(path.str).forEach((path) => result.arr[i++] = new builtin.string(path)) - + $if js_node { + #let i = 0 + #$fs.readdirSync(path.str).forEach((path) => result.arr[i++] = new builtin.string(path)) + } return result } + +pub fn get_raw_line() string { + return '' +} + +pub fn executable() string { + return '' +} + +pub fn is_executable(path string) bool { + eprintln('TODO: There is no isExecutable on fs.stats') + return false +} + +pub fn rmdir(path string) ? { + $if js_node { + err := '' + #try { + #$fs.rmdirSync(path.str) + #return; + #} catch (e) { + #err.str = 'Failed to remove "' + path.str + '": ' + e.toString() + #} + + return error(err) + } +} + +pub fn rm(path string) ? { + $if js_node { + err := '' + #try { + #$fs.rmSync(path.str) + #return; + #} catch (e) { + #err.str = 'Failed to remove "' + path.str + '": ' + e.toString() + #} + + return error(err) + } +} + +pub fn cp(src string, dst string) ? { + $if js_node { + err := '' + #try { + #$fs.cpSync(src.str,dst.str); + #return; + #} catch (e) { + #err.str = 'failed to copy ' + src.str + ' to ' + dst.str + ': ' + e.toString(); + #} + + return error(err) + } +} + +pub fn read_file(s string) ?string { + mut err := '' + err = err + res := '' + #try { + #res.str = $fs.readFileSync(s.str).toString() + #} catch (e) { + #err.str = 'Failed to read file: ' + e.toString() + #return error(err) + #} + + return res +} + +pub fn getwd() string { + res := '' + #res.str = $process.cwd() + + return res +} diff --git a/vlib/os_js/environment.js.v b/vlib/os_js/environment.js.v deleted file mode 100644 index 0f69f094c5..0000000000 --- a/vlib/os_js/environment.js.v +++ /dev/null @@ -1,31 +0,0 @@ -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/os.js.v b/vlib/os_js/os.js.v deleted file mode 100644 index 2fb088a865..0000000000 --- a/vlib/os_js/os.js.v +++ /dev/null @@ -1,95 +0,0 @@ -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 deleted file mode 100644 index 8b4e160ea4..0000000000 --- a/vlib/os_js/os.v +++ /dev/null @@ -1,13 +0,0 @@ -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 deleted file mode 100644 index e5574d8f28..0000000000 --- a/vlib/os_js/process.js.v +++ /dev/null @@ -1,173 +0,0 @@ -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.val.pid = $child_process.spawn( - #p.val.filename+'', - #p.val.args.arr.map((x) => x.valueOf() + ''), - #{ - #env: (p.val.env_is_custom ? p.val.env : $process.env), - #}) - #p.val.pid.on('error', function (err) { builtin.panic('Failed to start subprocess') }) - - p.status = .running - // todo(playX): stderr,stdin - if p.use_stdio_ctl { - #p.val.pid.stdout.pipe(process.stdout) - #p.val.pid.stdin.pipe(process.stdin) - #p.val.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.val.pid.kill('SIGKILL'); - - p.status = .aborted -} - -pub fn (mut p Process) signal_stop() { - if p.status !in [.running, .stopped] { - return - } - #p.val.pid.kill('SIGSTOP'); - - p.status = .aborted -} - -pub fn (mut p Process) signal_continue() { - if p.status != .stopped { - return - } - #p.val.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.val.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.val.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.val.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/gen/js/builtin_types.v b/vlib/v/gen/js/builtin_types.v index ea792ad90b..d5b915e8f2 100644 --- a/vlib/v/gen/js/builtin_types.v +++ b/vlib/v/gen/js/builtin_types.v @@ -338,7 +338,7 @@ fn (mut g JsGen) gen_builtin_type_defs() { g.gen_builtin_prototype( typ_name: typ_name default_value: 'new Number(0)' - constructor: 'this.val = typeof(val) == "string" ? val.charCodeAt() : (val | 0)' + constructor: 'if (typeof(val) == "string") { this.val = val.charCodeAt() } else if (val instanceof string) { this.val = val.str.charCodeAt(); } else { this.val = val | 0 }' value_of: 'this.val | 0' to_string: 'new string(this.val + "")' eq: 'this.valueOf() === other.valueOf()' diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v index eb2a2bc4c3..3aeb9fe099 100644 --- a/vlib/v/gen/js/js.v +++ b/vlib/v/gen/js/js.v @@ -525,6 +525,111 @@ fn (mut g JsGen) gen_global_decl(node ast.GlobalDecl) { } } +fn (mut g JsGen) stmt_no_semi(node ast.Stmt) { + g.stmt_start_pos = g.ns.out.len + match node { + ast.EmptyStmt {} + ast.AsmStmt { + panic('inline asm is not supported by js') + } + ast.AssertStmt { + g.write_v_source_line_info(node.pos) + g.gen_assert_stmt(node) + } + ast.AssignStmt { + g.write_v_source_line_info(node.pos) + g.gen_assign_stmt(node, false) + } + ast.Block { + g.write_v_source_line_info(node.pos) + g.gen_block(node) + g.writeln('') + } + ast.BranchStmt { + g.write_v_source_line_info(node.pos) + g.gen_branch_stmt(node) + } + ast.CompFor {} + ast.ConstDecl { + g.write_v_source_line_info(node.pos) + g.gen_const_decl(node) + } + ast.DeferStmt { + g.defer_stmts << node + } + ast.EnumDecl { + g.write_v_source_line_info(node.pos) + g.gen_enum_decl(node) + g.writeln('') + } + ast.ExprStmt { + g.write_v_source_line_info(node.pos) + g.gen_expr_stmt_no_semi(node) + } + ast.FnDecl { + g.write_v_source_line_info(node.pos) + g.fn_decl = unsafe { &node } + g.gen_fn_decl(node) + } + ast.ForCStmt { + g.write_v_source_line_info(node.pos) + g.gen_for_c_stmt(node) + g.writeln('') + } + ast.ForInStmt { + g.write_v_source_line_info(node.pos) + g.gen_for_in_stmt(node) + g.writeln('') + } + ast.ForStmt { + g.write_v_source_line_info(node.pos) + g.gen_for_stmt(node) + g.writeln('') + } + ast.GlobalDecl { + g.write_v_source_line_info(node.pos) + g.gen_global_decl(node) + g.writeln('') + } + ast.GotoLabel { + g.write_v_source_line_info(node.pos) + g.writeln('${g.js_name(node.name)}:') + } + ast.GotoStmt { + // skip: JS has no goto + } + ast.HashStmt { + g.write_v_source_line_info(node.pos) + g.gen_hash_stmt(node) + } + ast.Import { + g.ns.imports[node.mod] = node.alias + } + ast.InterfaceDecl { + g.write_v_source_line_info(node.pos) + g.gen_interface_decl(node) + } + ast.Module { + // skip: namespacing implemented externally + } + ast.NodeError {} + ast.Return { + if g.defer_stmts.len > 0 { + g.gen_defer_stmts() + } + g.gen_return_stmt(node) + } + ast.SqlStmt {} + ast.StructDecl { + g.write_v_source_line_info(node.pos) + g.gen_struct_decl(node) + } + ast.TypeDecl { + // skip JS has no typedecl + } + } +} + fn (mut g JsGen) stmt(node ast.Stmt) { g.stmt_start_pos = g.ns.out.len match node { @@ -538,7 +643,7 @@ fn (mut g JsGen) stmt(node ast.Stmt) { } ast.AssignStmt { g.write_v_source_line_info(node.pos) - g.gen_assign_stmt(node) + g.gen_assign_stmt(node, true) } ast.Block { g.write_v_source_line_info(node.pos) @@ -860,7 +965,7 @@ fn (mut g JsGen) gen_assert_stmt(a ast.AssertStmt) { g.writeln('}') } -fn (mut g JsGen) gen_assign_stmt(stmt ast.AssignStmt) { +fn (mut g JsGen) gen_assign_stmt(stmt ast.AssignStmt, semicolon bool) { if stmt.left.len > stmt.right.len { // multi return g.write('const [') @@ -874,7 +979,9 @@ fn (mut g JsGen) gen_assign_stmt(stmt ast.AssignStmt) { } g.write('] = ') g.expr(stmt.right[0]) - g.writeln(';') + if semicolon { + g.writeln(';') + } } else { // `a := 1` | `a,b := 1,2` for i, left in stmt.left { @@ -996,10 +1103,12 @@ fn (mut g JsGen) gen_assign_stmt(stmt ast.AssignStmt) { g.cast_stack.delete_last() } } - if g.inside_loop { - g.write('; ') - } else { - g.writeln(';') + if semicolon { + if g.inside_loop { + g.write('; ') + } else { + g.writeln(';') + } } } } @@ -1072,6 +1181,10 @@ fn (mut g JsGen) gen_expr_stmt(it ast.ExprStmt) { } } +fn (mut g JsGen) gen_expr_stmt_no_semi(it ast.ExprStmt) { + g.expr(it.expr) +} + fn (mut g JsGen) gen_fn_decl(it ast.FnDecl) { if it.no_body || it.is_method { // Struct methods are handled by class generation code. @@ -1211,7 +1324,7 @@ fn (mut g JsGen) gen_for_c_stmt(it ast.ForCStmt) { } g.write('; ') if it.has_inc { - g.stmt(it.inc) + g.stmt_no_semi(it.inc) } g.writeln(') {') g.stmts(it.stmts) @@ -1349,6 +1462,7 @@ fn (mut g JsGen) gen_interface_decl(it ast.InterfaceDecl) { // TODO: interfaces are always `pub`? name := g.js_name(it.name) g.push_pub_var('/** @type $name */\n\t\t$name: undefined') + g.writeln('function ${g.js_name(it.name)} (arg) { return arg; }') } fn (mut g JsGen) gen_return_stmt(it ast.Return) { @@ -1629,7 +1743,7 @@ fn (mut g JsGen) gen_method_call(it ast.CallExpr) bool { if it.or_block.stmts.len > 1 { g.stmts(it.or_block.stmts[..it.or_block.stmts.len - 1]) } - g.write('return ') + // g.write('return ') g.stmt(it.or_block.stmts.last()) } .propagate { @@ -1707,7 +1821,8 @@ fn (mut g JsGen) gen_call_expr(it ast.CallExpr) { if it.or_block.stmts.len > 1 { g.stmts(it.or_block.stmts[..it.or_block.stmts.len - 1]) } - g.write('return ') + + // g.write('return ') g.stmt(it.or_block.stmts.last()) } .propagate { @@ -1928,7 +2043,8 @@ fn (mut g JsGen) match_expr(node ast.MatchExpr) { } if node.cond is ast.Ident || node.cond is ast.SelectorExpr || node.cond is ast.IntegerLiteral - || node.cond is ast.StringLiteral || node.cond is ast.FloatLiteral { + || node.cond is ast.StringLiteral || node.cond is ast.FloatLiteral + || node.cond is ast.CallExpr { cond_var = CondExpr{node.cond} } else { s := g.new_tmp_var() @@ -2577,7 +2693,15 @@ fn (mut g JsGen) gen_string_literal(it ast.StringLiteral) { } g.write('string(') } - g.write("\"$text\"") + if it.is_raw { + g.writeln('(function() { let s = String(); ') + for x in text { + g.writeln('s += String.fromCharCode($x);') + } + g.writeln('return s; })()') + } else { + g.write("\"$text\"") + } if true || should_cast { g.write(')') }