v.gen.js: add three backend options for JS, and comptime if support (#10859)

pull/10863/head
playX 2021-07-19 15:55:03 +03:00 committed by GitHub
parent 8e99a018df
commit 425ca5e3c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 416 additions and 44 deletions

View File

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

View File

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

View File

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

View File

@ -48,34 +48,36 @@ mut:
[heap]
struct JsGen {
table &ast.Table
pref &pref.Preferences
pref &pref.Preferences
mut:
definitions strings.Builder
ns &Namespace
namespaces map[string]&Namespace
doc &JsDoc
enable_doc bool
file &ast.File
tmp_count int
inside_ternary bool
inside_loop bool
inside_map_set bool // map.set(key, value)
inside_builtin bool
generated_builtin bool
inside_def_typ_decl bool
is_test bool
stmt_start_pos int
defer_stmts []ast.DeferStmt
fn_decl &ast.FnDecl // pointer to the FnDecl we are currently inside otherwise 0
str_types []string // types that need automatic str() generation
method_fn_decls map[string][]ast.FnDecl
builtin_fns []string // Functions defined in `builtin`
empty_line bool
cast_stack []ast.Type
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
table &ast.Table
definitions strings.Builder
ns &Namespace
namespaces map[string]&Namespace
doc &JsDoc
enable_doc bool
file &ast.File
tmp_count int
inside_ternary bool
inside_loop bool
inside_map_set bool // map.set(key, value)
inside_builtin bool
generated_builtin bool
inside_def_typ_decl bool
is_test bool
stmt_start_pos int
defer_stmts []ast.DeferStmt
fn_decl &ast.FnDecl // pointer to the FnDecl we are currently inside otherwise 0
str_types []string // types that need automatic str() generation
method_fn_decls map[string][]ast.FnDecl
builtin_fns []string // Functions defined in `builtin`
empty_line bool
cast_stack []ast.Type
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
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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('.#') {