v.gen.js: add three backend options for JS, and comptime if support (#10859)
parent
8e99a018df
commit
425ca5e3c3
|
@ -40,7 +40,7 @@ pub fn compile(command string, pref &pref.Preferences) {
|
|||
mut sw := time.new_stopwatch({})
|
||||
match pref.backend {
|
||||
.c { b.compile_c() }
|
||||
.js { b.compile_js() }
|
||||
.js_node, .js_freestanding, .js_browser { b.compile_js() }
|
||||
.native { b.compile_native() }
|
||||
}
|
||||
mut timers := util.get_timers()
|
||||
|
@ -118,7 +118,7 @@ fn (mut b Builder) run_compiled_executable_and_exit() {
|
|||
}
|
||||
mut exefile := os.real_path(b.pref.out_name)
|
||||
mut cmd := '"$exefile"'
|
||||
if b.pref.backend == .js {
|
||||
if b.pref.backend.is_js() {
|
||||
exefile = os.real_path('${b.pref.out_name}.js')
|
||||
cmd = 'node "$exefile"'
|
||||
}
|
||||
|
@ -200,7 +200,7 @@ pub fn (v Builder) get_builtin_files() []string {
|
|||
for location in v.pref.lookup_path {
|
||||
if os.exists(os.join_path(location, 'builtin')) {
|
||||
mut builtin_files := []string{}
|
||||
if v.pref.backend == .js {
|
||||
if v.pref.backend.is_js() {
|
||||
builtin_files << v.v_files_from_dir(os.join_path(location, 'builtin',
|
||||
'js'))
|
||||
} else {
|
||||
|
|
|
@ -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']
|
||||
'no_bounds_checking', 'freestanding', 'threads', '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']
|
||||
|
@ -4802,7 +4802,7 @@ fn (mut c Checker) asm_stmt(mut stmt ast.AsmStmt) {
|
|||
c.warn('inline assembly goto is not supported, it will most likely not work',
|
||||
stmt.pos)
|
||||
}
|
||||
if c.pref.backend == .js {
|
||||
if c.pref.backend.is_js() {
|
||||
c.error('inline assembly is not supported in the js backend', stmt.pos)
|
||||
}
|
||||
if c.pref.backend == .c && c.pref.ccompiler_type == .msvc {
|
||||
|
@ -4901,7 +4901,7 @@ fn (mut c Checker) hash_stmt(mut node ast.HashStmt) {
|
|||
if c.skip_flags {
|
||||
return
|
||||
}
|
||||
if c.pref.backend == .js {
|
||||
if c.pref.backend.is_js() {
|
||||
if !c.file.path.ends_with('.js.v') {
|
||||
c.error('hash statements are only allowed in backend specific files such "x.js.v"',
|
||||
node.pos)
|
||||
|
@ -6771,7 +6771,7 @@ fn (mut c Checker) comp_if_branch(cond ast.Expr, pos token.Position) bool {
|
|||
return false
|
||||
} else if cname in checker.valid_comp_if_other {
|
||||
match cname {
|
||||
'js' { return c.pref.backend != .js }
|
||||
'js' { return !c.pref.backend.is_js() }
|
||||
'debug' { return !c.pref.is_debug }
|
||||
'prod' { return !c.pref.is_prod }
|
||||
'test' { return !c.pref.is_test }
|
||||
|
|
|
@ -0,0 +1,311 @@
|
|||
module js
|
||||
|
||||
import v.ast
|
||||
import v.pref
|
||||
|
||||
fn (mut g JsGen) comp_if(node ast.IfExpr) {
|
||||
if !node.is_expr && !node.has_else && node.branches.len == 1 {
|
||||
if node.branches[0].stmts.len == 0 {
|
||||
// empty ifdef; result of target OS != conditional => skip
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for i, branch in node.branches {
|
||||
if i == node.branches.len - 1 && node.has_else {
|
||||
g.writeln('else')
|
||||
} else {
|
||||
if i == 0 {
|
||||
g.write('if (')
|
||||
} else {
|
||||
g.write('else if (')
|
||||
}
|
||||
g.comp_if_cond(branch.cond, branch.pkg_exist)
|
||||
g.writeln(')')
|
||||
}
|
||||
|
||||
if node.is_expr {
|
||||
print('$branch.stmts')
|
||||
len := branch.stmts.len
|
||||
if len > 0 {
|
||||
last := branch.stmts[len - 1] as ast.ExprStmt
|
||||
if len > 1 {
|
||||
tmp := g.new_tmp_var()
|
||||
g.inc_indent()
|
||||
g.writeln('let $tmp;')
|
||||
g.writeln('{')
|
||||
g.stmts(branch.stmts[0..len - 1])
|
||||
g.write('\t$tmp = ')
|
||||
g.stmt(last)
|
||||
g.writeln('}')
|
||||
g.dec_indent()
|
||||
g.writeln('$tmp;')
|
||||
} else {
|
||||
g.stmt(last)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
g.writeln('{')
|
||||
g.stmts(branch.stmts)
|
||||
g.writeln('}')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
// returning `false` means the statements inside the $if can be skipped
|
||||
*/
|
||||
// returns the value of the bool comptime expression
|
||||
fn (mut g JsGen) comp_if_cond(cond ast.Expr, pkg_exist bool) bool {
|
||||
match cond {
|
||||
ast.BoolLiteral {
|
||||
g.expr(cond)
|
||||
return true
|
||||
}
|
||||
ast.ParExpr {
|
||||
g.write('(')
|
||||
is_cond_true := g.comp_if_cond(cond.expr, pkg_exist)
|
||||
g.write(')')
|
||||
return is_cond_true
|
||||
}
|
||||
ast.PrefixExpr {
|
||||
g.write(cond.op.str())
|
||||
return g.comp_if_cond(cond.right, pkg_exist)
|
||||
}
|
||||
ast.PostfixExpr {
|
||||
ifdef := g.comp_if_to_ifdef((cond.expr as ast.Ident).name, true) or {
|
||||
verror(err.msg)
|
||||
return false
|
||||
}
|
||||
g.write('$ifdef')
|
||||
return true
|
||||
}
|
||||
ast.InfixExpr {
|
||||
match cond.op {
|
||||
.and, .logical_or {
|
||||
l := g.comp_if_cond(cond.left, pkg_exist)
|
||||
g.write(' $cond.op ')
|
||||
r := g.comp_if_cond(cond.right, pkg_exist)
|
||||
return if cond.op == .and { l && r } else { l || r }
|
||||
}
|
||||
.key_is, .not_is {
|
||||
left := cond.left
|
||||
mut name := ''
|
||||
mut exp_type := ast.Type(0)
|
||||
got_type := (cond.right as ast.TypeNode).typ
|
||||
// Handle `$if x is Interface {`
|
||||
// mut matches_interface := 'false'
|
||||
if left is ast.TypeNode && cond.right is ast.TypeNode
|
||||
&& g.table.get_type_symbol(got_type).kind == .interface_ {
|
||||
// `$if Foo is Interface {`
|
||||
interface_sym := g.table.get_type_symbol(got_type)
|
||||
if interface_sym.info is ast.Interface {
|
||||
// q := g.table.get_type_symbol(interface_sym.info.types[0])
|
||||
checked_type := g.unwrap_generic(left.typ)
|
||||
// TODO PERF this check is run twice (also in the checker)
|
||||
// store the result in a field
|
||||
is_true := g.table.does_type_implement_interface(checked_type,
|
||||
got_type)
|
||||
// true // exp_type in interface_sym.info.types
|
||||
if cond.op == .key_is {
|
||||
if is_true {
|
||||
g.write('1')
|
||||
} else {
|
||||
g.write('0')
|
||||
}
|
||||
return is_true
|
||||
} else if cond.op == .not_is {
|
||||
if is_true {
|
||||
g.write('0')
|
||||
} else {
|
||||
g.write('1')
|
||||
}
|
||||
return !is_true
|
||||
}
|
||||
// matches_interface = '/*iface:$got_type $exp_type*/ true'
|
||||
//}
|
||||
}
|
||||
} else if left is ast.SelectorExpr {
|
||||
name = '${left.expr}.$left.field_name'
|
||||
exp_type = g.comptime_var_type_map[name]
|
||||
} else if left is ast.TypeNode {
|
||||
name = left.str()
|
||||
// this is only allowed for generics currently, otherwise blocked by checker
|
||||
exp_type = g.unwrap_generic(left.typ)
|
||||
}
|
||||
|
||||
if cond.op == .key_is {
|
||||
g.write('$exp_type == $got_type')
|
||||
return exp_type == got_type
|
||||
} else {
|
||||
g.write('$exp_type != $got_type')
|
||||
return exp_type != got_type
|
||||
}
|
||||
}
|
||||
.eq, .ne {
|
||||
// TODO Implement `$if method.args.len == 1`
|
||||
g.write('1')
|
||||
return true
|
||||
}
|
||||
else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
ast.Ident {
|
||||
ifdef := g.comp_if_to_ifdef(cond.name, false) or { 'true' } // handled in checker
|
||||
g.write('$ifdef')
|
||||
return true
|
||||
}
|
||||
ast.ComptimeCall {
|
||||
g.write('$pkg_exist')
|
||||
return true
|
||||
}
|
||||
else {
|
||||
// should be unreachable, but just in case
|
||||
g.write('1')
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g JsGen) comp_if_to_ifdef(name string, is_comptime_optional bool) ?string {
|
||||
match name {
|
||||
// platforms/os-es:
|
||||
'windows' {
|
||||
return '(\$process.platform == "windows")'
|
||||
}
|
||||
'ios' {
|
||||
return '(\$process.platform == "darwin")'
|
||||
}
|
||||
'macos' {
|
||||
return '(\$process.platform == "darwin")'
|
||||
}
|
||||
'mach' {
|
||||
return '(\$process.platform == "darwin")'
|
||||
}
|
||||
'darwin' {
|
||||
return '(\$process.platform == "darwin")'
|
||||
}
|
||||
'linux' {
|
||||
return '(\$process.platform == "linux")'
|
||||
}
|
||||
'freebsd' {
|
||||
return '(\$process.platform == "freebsd")'
|
||||
}
|
||||
'openbsd' {
|
||||
return '(\$process.platform == "openbsd")'
|
||||
}
|
||||
'bsd' {
|
||||
return '(\$process.platform == "freebsd" || (\$process.platform == "openbsd"))'
|
||||
}
|
||||
'android' {
|
||||
return '(\$process.platform == "android")'
|
||||
}
|
||||
'solaris' {
|
||||
return '(\$process.platform == "sunos")'
|
||||
}
|
||||
'js_node' {
|
||||
if g.pref.backend == .js_node {
|
||||
return 'true'
|
||||
} else {
|
||||
return 'false'
|
||||
}
|
||||
}
|
||||
'js_freestanding' {
|
||||
if g.pref.backend == .js_freestanding {
|
||||
return 'true'
|
||||
} else {
|
||||
return 'false'
|
||||
}
|
||||
}
|
||||
'js_browser' {
|
||||
if g.pref.backend == .js_browser {
|
||||
return 'true'
|
||||
} else {
|
||||
return 'false'
|
||||
}
|
||||
}
|
||||
//
|
||||
'js' {
|
||||
return 'true'
|
||||
}
|
||||
// compilers:
|
||||
'gcc' {
|
||||
return 'false'
|
||||
}
|
||||
'tinyc' {
|
||||
return 'false'
|
||||
}
|
||||
'clang' {
|
||||
return 'false'
|
||||
}
|
||||
'mingw' {
|
||||
return 'false'
|
||||
}
|
||||
'msvc' {
|
||||
return 'false'
|
||||
}
|
||||
'cplusplus' {
|
||||
return 'false'
|
||||
}
|
||||
// other:
|
||||
'threads' {
|
||||
return 'false'
|
||||
}
|
||||
'gcboehm' {
|
||||
return 'false'
|
||||
}
|
||||
// todo(playX): these should return true or false depending on CLI options
|
||||
'debug' {
|
||||
return 'false'
|
||||
}
|
||||
'prod' {
|
||||
return 'false'
|
||||
}
|
||||
'test' {
|
||||
return 'false'
|
||||
}
|
||||
'glibc' {
|
||||
return 'false'
|
||||
}
|
||||
'prealloc' {
|
||||
return 'false'
|
||||
}
|
||||
'no_bounds_checking' {
|
||||
return 'CUSTOM_DEFINE_no_bounds_checking'
|
||||
}
|
||||
'freestanding' {
|
||||
return '_VFREESTANDING'
|
||||
}
|
||||
// architectures:
|
||||
'amd64' {
|
||||
return '(\$process.arch == "x64")'
|
||||
}
|
||||
'aarch64', 'arm64' {
|
||||
return '(\$process.arch == "arm64)'
|
||||
}
|
||||
// bitness:
|
||||
'x64' {
|
||||
return '(\$process.arch == "x64")'
|
||||
}
|
||||
'x32' {
|
||||
return '(\$process.arch == "x32")'
|
||||
}
|
||||
// endianness:
|
||||
'little_endian' {
|
||||
return '(\$os.endianess == "LE")'
|
||||
}
|
||||
'big_endian' {
|
||||
return '(\$os.endianess == "BE")'
|
||||
}
|
||||
else {
|
||||
if is_comptime_optional
|
||||
|| (g.pref.compile_defines_all.len > 0 && name in g.pref.compile_defines_all) {
|
||||
return 'CUSTOM_DEFINE_$name'
|
||||
}
|
||||
return error('bad os ifdef name "$name"') // should never happen, caught in the checker
|
||||
}
|
||||
}
|
||||
return none
|
||||
}
|
|
@ -48,9 +48,9 @@ mut:
|
|||
|
||||
[heap]
|
||||
struct JsGen {
|
||||
table &ast.Table
|
||||
pref &pref.Preferences
|
||||
mut:
|
||||
table &ast.Table
|
||||
definitions strings.Builder
|
||||
ns &Namespace
|
||||
namespaces map[string]&Namespace
|
||||
|
@ -76,6 +76,8 @@ mut:
|
|||
call_stack []ast.CallExpr
|
||||
is_vlines_enabled bool // is it safe to generate #line directives when -g is passed
|
||||
sourcemap sourcemap.SourceMap // maps lines in generated javascrip file to original source files and line
|
||||
comptime_var_type_map map[string]ast.Type
|
||||
defer_ifdef string
|
||||
}
|
||||
|
||||
pub fn gen(files []&ast.File, table &ast.Table, pref &pref.Preferences) string {
|
||||
|
@ -303,6 +305,25 @@ pub fn (mut g JsGen) init() {
|
|||
g.definitions.writeln('"use strict";')
|
||||
g.definitions.writeln('')
|
||||
g.definitions.writeln('var \$global = (new Function("return this"))();')
|
||||
|
||||
if g.pref.backend != .js_node {
|
||||
g.definitions.writeln('const \$process = {')
|
||||
g.definitions.writeln(' arch: "js",')
|
||||
if g.pref.backend == .js_freestanding {
|
||||
g.definitions.writeln(' platform: "freestanding"')
|
||||
} else {
|
||||
g.definitions.writeln(' platform: "browser"')
|
||||
}
|
||||
g.definitions.writeln('}')
|
||||
|
||||
g.definitions.writeln('const \$os = {')
|
||||
g.definitions.writeln(' endianess: "LE",')
|
||||
|
||||
g.definitions.writeln('}')
|
||||
} else {
|
||||
g.definitions.writeln('const \$os = require("os");')
|
||||
g.definitions.writeln('const \$process = process;')
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (g JsGen) hashes() string {
|
||||
|
@ -1443,6 +1464,10 @@ fn (mut g JsGen) gen_lock_expr(node ast.LockExpr) {
|
|||
}
|
||||
|
||||
fn (mut g JsGen) gen_if_expr(node ast.IfExpr) {
|
||||
if node.is_comptime {
|
||||
g.comp_if(node)
|
||||
return
|
||||
}
|
||||
type_sym := g.table.get_type_symbol(node.typ)
|
||||
// one line ?:
|
||||
if node.is_expr && node.branches.len >= 2 && node.has_else && type_sym.kind != .void {
|
||||
|
@ -1936,3 +1961,14 @@ fn (mut g JsGen) gen_float_literal_expr(it ast.FloatLiteral) {
|
|||
|
||||
g.write('${g.typ(typ)}($it.val)')
|
||||
}
|
||||
|
||||
fn (mut g JsGen) unwrap_generic(typ ast.Type) ast.Type {
|
||||
if typ.has_flag(.generic) {
|
||||
if t_typ := g.table.resolve_generic_to_concrete(typ, g.table.cur_fn.generic_names,
|
||||
g.table.cur_concrete_types)
|
||||
{
|
||||
return t_typ
|
||||
}
|
||||
}
|
||||
return typ
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ fn check_path(dir string, tests []string) ?int {
|
|||
os.write_file(program_out, '') ?
|
||||
}
|
||||
print(program + ' ')
|
||||
res := os.execute('$vexe -b js run $program')
|
||||
res := os.execute('$vexe -b js_node run $program')
|
||||
if res.exit_code < 0 {
|
||||
panic(res.output)
|
||||
}
|
||||
|
|
|
@ -633,7 +633,7 @@ fn (mut p Parser) anon_fn() ast.AnonFn {
|
|||
old_inside_defer := p.inside_defer
|
||||
p.inside_defer = false
|
||||
p.open_scope()
|
||||
if p.pref.backend != .js {
|
||||
if !p.pref.backend.is_js() {
|
||||
p.scope.detached_from_parent = true
|
||||
}
|
||||
// TODO generics
|
||||
|
|
|
@ -13,7 +13,9 @@ pub enum OS {
|
|||
openbsd
|
||||
netbsd
|
||||
dragonfly
|
||||
js // TODO
|
||||
js_node
|
||||
js_browser
|
||||
js_freestanding
|
||||
android
|
||||
solaris
|
||||
serenity
|
||||
|
@ -34,7 +36,9 @@ pub fn os_from_string(os_str string) ?OS {
|
|||
'openbsd' { return .openbsd }
|
||||
'netbsd' { return .netbsd }
|
||||
'dragonfly' { return .dragonfly }
|
||||
'js' { return .js }
|
||||
'js', 'js_node' { return .js_node }
|
||||
'js_freestanding' { return .js_freestanding }
|
||||
'js_browser' { return .js_browser }
|
||||
'solaris' { return .solaris }
|
||||
'serenity' { return .serenity }
|
||||
'vinix' { return .vinix }
|
||||
|
@ -58,7 +62,9 @@ pub fn (o OS) str() string {
|
|||
.openbsd { return 'OpenBSD' }
|
||||
.netbsd { return 'NetBSD' }
|
||||
.dragonfly { return 'Dragonfly' }
|
||||
.js { return 'JavaScript' }
|
||||
.js_node { return 'NodeJS' }
|
||||
.js_freestanding { return 'JavaScript' }
|
||||
.js_browser { return 'JavaScript(Browser)' }
|
||||
.android { return 'Android' }
|
||||
.solaris { return 'Solaris' }
|
||||
.serenity { return 'SerenityOS' }
|
||||
|
|
|
@ -45,10 +45,23 @@ pub enum ColorOutput {
|
|||
|
||||
pub enum Backend {
|
||||
c // The (default) C backend
|
||||
js // The JavaScript backend
|
||||
js_node // The JavaScript NodeJS backend
|
||||
js_browser // The JavaScript browser backend
|
||||
js_freestanding // The JavaScript freestanding backend
|
||||
native // The Native backend
|
||||
}
|
||||
|
||||
pub fn (b Backend) is_js() bool {
|
||||
match b {
|
||||
.js_node, .js_browser, .js_freestanding {
|
||||
return true
|
||||
}
|
||||
else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum CompilerType {
|
||||
gcc
|
||||
tinyc
|
||||
|
@ -513,7 +526,7 @@ pub fn parse_args(known_external_commands []string, args []string) (&Preferences
|
|||
'-o', '-output' {
|
||||
res.out_name = cmdline.option(current_args, arg, '')
|
||||
if res.out_name.ends_with('.js') {
|
||||
res.backend = .js
|
||||
res.backend = .js_node
|
||||
}
|
||||
if !os.is_abs_path(res.out_name) {
|
||||
res.out_name = os.join_path(os.getwd(), res.out_name)
|
||||
|
@ -524,6 +537,9 @@ pub fn parse_args(known_external_commands []string, args []string) (&Preferences
|
|||
sbackend := cmdline.option(current_args, arg, 'c')
|
||||
res.build_options << '$arg $sbackend'
|
||||
b := backend_from_string(sbackend) or { continue }
|
||||
if b.is_js() {
|
||||
res.output_cross_c = true
|
||||
}
|
||||
res.backend = b
|
||||
i++
|
||||
}
|
||||
|
@ -723,7 +739,10 @@ fn is_source_file(path string) bool {
|
|||
pub fn backend_from_string(s string) ?Backend {
|
||||
match s {
|
||||
'c' { return .c }
|
||||
'js' { return .js }
|
||||
'js' { return .js_node }
|
||||
'js_node' { return .js_node }
|
||||
'js_browser' { return .js_browser }
|
||||
'js_freestanding' { return .js_freestanding }
|
||||
'native' { return .native }
|
||||
else { return error('Unknown backend type $s') }
|
||||
}
|
||||
|
|
|
@ -18,13 +18,13 @@ pub fn (prefs &Preferences) should_compile_filtered_files(dir string, files_ []s
|
|||
if prefs.backend == .c && !prefs.should_compile_c(file) {
|
||||
continue
|
||||
}
|
||||
if prefs.backend == .js && !prefs.should_compile_js(file) {
|
||||
if prefs.backend.is_js() && !prefs.should_compile_js(file) {
|
||||
continue
|
||||
}
|
||||
if prefs.backend == .native && !prefs.should_compile_native(file) {
|
||||
continue
|
||||
}
|
||||
if prefs.backend != .js && !prefs.should_compile_asm(file) {
|
||||
if !prefs.backend.is_js() && !prefs.should_compile_asm(file) {
|
||||
continue
|
||||
}
|
||||
if file.starts_with('.#') {
|
||||
|
|
Loading…
Reference in New Issue