js: `os` now compiles to the JS backend, more builtins & minor codegen fixes (#11302)

pull/11311/head
playX 2021-08-25 14:40:53 +03:00 committed by GitHub
parent f257a23313
commit 109d5d5847
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 479 additions and 369 deletions

View File

@ -32,12 +32,38 @@ struct Option {
err Error 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 struct Error {
pub: pub:
msg string msg string
code int 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 { pub fn (o Option) str() string {
if o.state == 0 { if o.state == 0 {
return 'Option{ ok }' return 'Option{ ok }'
@ -48,21 +74,28 @@ pub fn (o Option) str() string {
return 'Option{ error: "$o.err" }' return 'Option{ error: "$o.err" }'
} }
pub fn error(s string) Option { [if trace_error ?]
return Option{ fn trace_error(x string) {
state: 2 eprintln('> ${@FN} | $x')
err: Error{
msg: s
} }
// 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 { // error_with_code returns a default error instance containing the given `message` and error `code`.
return Option{ // `if ouch { return error_with_code('an error occurred', 1) }`
state: 2 [inline]
err: Error{ pub fn error_with_code(message string, code int) IError {
msg: s // trace_error('$message | code: $code')
return &Error{
msg: message
code: code code: code
} }
} }
}

View File

@ -2,7 +2,15 @@ module builtin
pub fn (b byte) is_space() bool { pub fn (b byte) is_space() bool {
mut result := false 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 return result
} }

View File

@ -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
}

View File

@ -359,6 +359,7 @@ pub fn (s string) replace_each(vals []string) string {
with := vals[rep_i + 1] with := vals[rep_i + 1]
for { for {
idx = s_.index_after(rep, idx) idx = s_.index_after(rep, idx)
if idx == -1 { if idx == -1 {
break break
} }
@ -374,6 +375,7 @@ pub fn (s string) replace_each(vals []string) string {
idx: idx idx: idx
val_idx: rep_i val_idx: rep_i
} }
idx += rep.len idx += rep.len
new_len += with.len - rep.len new_len += with.len - rep.len
} }

View File

@ -1,4 +1,4 @@
module os_js module os
pub struct File { pub struct File {
pub: pub:
@ -128,3 +128,9 @@ pub fn (mut f File) write_string(s string) ?int {
nbytes := f.write(s.bytes()) ? nbytes := f.write(s.bytes()) ?
return nbytes 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) ? {}

21
vlib/os/os.c.v 100644
View File

@ -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{}
)

View File

@ -4,9 +4,9 @@ module os
#const $path = require('path'); #const $path = require('path');
pub const ( pub const (
args = []string{}
path_delimiter = '/' path_delimiter = '/'
path_separator = '/' path_separator = '/'
args = []string{}
) )
$if js_node { $if js_node {

View File

@ -4,7 +4,6 @@
module os module os
pub const ( pub const (
args = []string{}
max_path_len = 4096 max_path_len = 4096
wd_at_startup = getwd() wd_at_startup = getwd()
) )
@ -344,22 +343,15 @@ pub fn write_file(path string, text string) ? {
f.close() 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. // 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 // 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. // all cases, but it should be better, than just using os.args[0] directly.
fn executable_fallback() string { 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 :-| // we are early in the bootstrap, os.args has not been initialized yet :-|
return '' return ''
} }
mut exepath := os.args[0] mut exepath := args[0]
$if windows { $if windows {
if !exepath.contains('.exe') { if !exepath.contains('.exe') {
exepath += '.exe' exepath += '.exe'
@ -639,12 +631,3 @@ pub fn execute_or_exit(cmd string) Result {
} }
return res 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
}

View File

@ -15,22 +15,25 @@ pub fn mkdir(path string) ?bool {
pub fn is_dir(path string) bool { pub fn is_dir(path string) bool {
res := false res := false
$if js_node {
#res.val = $fs.existsSync(path,str) && $fs.lstatSync(path.str).isDirectory() #res.val = $fs.existsSync(path,str) && $fs.lstatSync(path.str).isDirectory()
}
return res return res
} }
pub fn is_link(path string) bool { pub fn is_link(path string) bool {
res := false res := false
$if js_node {
#res.val = $fs.existsSync(path.str) && $fs.lstatSync(path.str).isSymbolicLink() #res.val = $fs.existsSync(path.str) && $fs.lstatSync(path.str).isSymbolicLink()
}
return res return res
} }
pub fn exists(path string) bool { pub fn exists(path string) bool {
res := false res := false
$if js_node {
#res.val = $fs.existsSync(path.str) #res.val = $fs.existsSync(path.str)
}
return res return res
} }
@ -40,8 +43,85 @@ pub fn ls(path string) ?[]string {
} }
result := []string{} result := []string{}
$if js_node {
#let i = 0 #let i = 0
#$fs.readdirSync(path.str).forEach((path) => result.arr[i++] = new builtin.string(path)) #$fs.readdirSync(path.str).forEach((path) => result.arr[i++] = new builtin.string(path))
}
return result 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
}

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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()')
}
}

View File

@ -338,7 +338,7 @@ fn (mut g JsGen) gen_builtin_type_defs() {
g.gen_builtin_prototype( g.gen_builtin_prototype(
typ_name: typ_name typ_name: typ_name
default_value: 'new Number(0)' 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' value_of: 'this.val | 0'
to_string: 'new string(this.val + "")' to_string: 'new string(this.val + "")'
eq: 'this.valueOf() === other.valueOf()' eq: 'this.valueOf() === other.valueOf()'

View File

@ -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) { fn (mut g JsGen) stmt(node ast.Stmt) {
g.stmt_start_pos = g.ns.out.len g.stmt_start_pos = g.ns.out.len
match node { match node {
@ -538,7 +643,7 @@ fn (mut g JsGen) stmt(node ast.Stmt) {
} }
ast.AssignStmt { ast.AssignStmt {
g.write_v_source_line_info(node.pos) g.write_v_source_line_info(node.pos)
g.gen_assign_stmt(node) g.gen_assign_stmt(node, true)
} }
ast.Block { ast.Block {
g.write_v_source_line_info(node.pos) g.write_v_source_line_info(node.pos)
@ -860,7 +965,7 @@ fn (mut g JsGen) gen_assert_stmt(a ast.AssertStmt) {
g.writeln('}') 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 { if stmt.left.len > stmt.right.len {
// multi return // multi return
g.write('const [') g.write('const [')
@ -874,7 +979,9 @@ fn (mut g JsGen) gen_assign_stmt(stmt ast.AssignStmt) {
} }
g.write('] = ') g.write('] = ')
g.expr(stmt.right[0]) g.expr(stmt.right[0])
if semicolon {
g.writeln(';') g.writeln(';')
}
} else { } else {
// `a := 1` | `a,b := 1,2` // `a := 1` | `a,b := 1,2`
for i, left in stmt.left { for i, left in stmt.left {
@ -996,6 +1103,7 @@ fn (mut g JsGen) gen_assign_stmt(stmt ast.AssignStmt) {
g.cast_stack.delete_last() g.cast_stack.delete_last()
} }
} }
if semicolon {
if g.inside_loop { if g.inside_loop {
g.write('; ') g.write('; ')
} else { } else {
@ -1004,6 +1112,7 @@ fn (mut g JsGen) gen_assign_stmt(stmt ast.AssignStmt) {
} }
} }
} }
}
fn (mut g JsGen) gen_attrs(attrs []ast.Attr) { fn (mut g JsGen) gen_attrs(attrs []ast.Attr) {
for attr in attrs { for attr in attrs {
@ -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) { fn (mut g JsGen) gen_fn_decl(it ast.FnDecl) {
if it.no_body || it.is_method { if it.no_body || it.is_method {
// Struct methods are handled by class generation code. // 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('; ') g.write('; ')
if it.has_inc { if it.has_inc {
g.stmt(it.inc) g.stmt_no_semi(it.inc)
} }
g.writeln(') {') g.writeln(') {')
g.stmts(it.stmts) g.stmts(it.stmts)
@ -1349,6 +1462,7 @@ fn (mut g JsGen) gen_interface_decl(it ast.InterfaceDecl) {
// TODO: interfaces are always `pub`? // TODO: interfaces are always `pub`?
name := g.js_name(it.name) name := g.js_name(it.name)
g.push_pub_var('/** @type $name */\n\t\t$name: undefined') 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) { 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 { if it.or_block.stmts.len > 1 {
g.stmts(it.or_block.stmts[..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()) g.stmt(it.or_block.stmts.last())
} }
.propagate { .propagate {
@ -1707,7 +1821,8 @@ fn (mut g JsGen) gen_call_expr(it ast.CallExpr) {
if it.or_block.stmts.len > 1 { if it.or_block.stmts.len > 1 {
g.stmts(it.or_block.stmts[..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()) g.stmt(it.or_block.stmts.last())
} }
.propagate { .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 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} cond_var = CondExpr{node.cond}
} else { } else {
s := g.new_tmp_var() s := g.new_tmp_var()
@ -2577,7 +2693,15 @@ fn (mut g JsGen) gen_string_literal(it ast.StringLiteral) {
} }
g.write('string(') g.write('string(')
} }
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\"") g.write("\"$text\"")
}
if true || should_cast { if true || should_cast {
g.write(')') g.write(')')
} }