mirror of
https://github.com/vlang/v.git
synced 2025-09-24 04:48:28 -04:00
This commit is contained in:
parent
89a24958a1
commit
d2174e47a5
@ -17,8 +17,16 @@ pub const max_i32 = i32(2147483647)
|
|||||||
pub const min_i64 = i64(-9223372036854775807 - 1)
|
pub const min_i64 = i64(-9223372036854775807 - 1)
|
||||||
pub const max_i64 = i64(9223372036854775807)
|
pub const max_i64 = i64(9223372036854775807)
|
||||||
|
|
||||||
pub const min_int = int(min_i32)
|
pub const min_int = $if new_int ? && (arm64 || amd64 || rv64 || s390x || ppc64le || loongarch64) {
|
||||||
pub const max_int = int(max_i32)
|
int(min_i64)
|
||||||
|
} $else {
|
||||||
|
int(min_i32)
|
||||||
|
}
|
||||||
|
pub const max_int = $if new_int ? && (arm64 || amd64 || rv64 || s390x || ppc64le || loongarch64) {
|
||||||
|
int(max_i64)
|
||||||
|
} $else {
|
||||||
|
int(max_i32)
|
||||||
|
}
|
||||||
|
|
||||||
pub const min_u8 = u8(0)
|
pub const min_u8 = u8(0)
|
||||||
pub const max_u8 = u8(255)
|
pub const max_u8 = u8(255)
|
||||||
|
@ -18,334 +18,15 @@ fn (mut g JsGen) gen_branch_context_string() string {
|
|||||||
return arr.join(',')
|
return arr.join(',')
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (mut g JsGen) comptime_if(node ast.IfExpr) {
|
fn (mut g JsGen) comptime_if_result(branch ast.IfBranch) bool {
|
||||||
if !node.is_expr && !node.has_else && node.branches.len == 1 {
|
idx_str := g.gen_branch_context_string() + '|id=${branch.id}|'
|
||||||
if node.branches[0].stmts.len == 0 {
|
$if debug_comptime_branch_context ? {
|
||||||
// empty ifdef; result of target OS != conditional => skip
|
g.write('/* ${idx_str} */')
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if comptime_is_true := g.table.comptime_is_true[idx_str] {
|
||||||
mut comptime_branch_context_str := g.gen_branch_context_string()
|
return comptime_is_true.val
|
||||||
mut is_true := ast.ComptTimeCondResult{}
|
} else {
|
||||||
for i, branch in node.branches {
|
panic('checker error: cond result idx string not found => [${idx_str}]')
|
||||||
idx_str := comptime_branch_context_str + '|id=${branch.id}|'
|
return false
|
||||||
if comptime_is_true := g.table.comptime_is_true[idx_str] {
|
|
||||||
is_true = comptime_is_true
|
|
||||||
} else {
|
|
||||||
panic('checker error: cond result idx string not found => [${idx_str}]')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if i == node.branches.len - 1 && node.has_else {
|
|
||||||
g.writeln('else')
|
|
||||||
} else {
|
|
||||||
result := if is_true.val { '1' } else { '0' }
|
|
||||||
if i == 0 {
|
|
||||||
g.writeln('if (${result})')
|
|
||||||
} else {
|
|
||||||
g.writeln('else if (${result})')
|
|
||||||
}
|
|
||||||
$if debug_comptime_branch_context ? {
|
|
||||||
g.writeln('// ${node.branches[i].cond} generic=[${comptime_branch_context_str}]')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if node.is_expr {
|
|
||||||
print('${branch.stmts}')
|
|
||||||
len := branch.stmts.len
|
|
||||||
if len > 0 {
|
|
||||||
last := branch.stmts.last() as ast.ExprStmt
|
|
||||||
if len > 1 {
|
|
||||||
tmp := g.new_tmp_var()
|
|
||||||
g.inc_indent()
|
|
||||||
g.writeln('let ${tmp};')
|
|
||||||
g.writeln('{')
|
|
||||||
g.stmts(branch.stmts[..len - 1])
|
|
||||||
g.write('\t${tmp} = ')
|
|
||||||
g.stmt(last)
|
|
||||||
g.writeln('}')
|
|
||||||
g.dec_indent()
|
|
||||||
g.writeln('${tmp};')
|
|
||||||
} else {
|
|
||||||
g.stmt(last)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
g.writeln('{')
|
|
||||||
if is_true.val {
|
|
||||||
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) comptime_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.comptime_if_cond(cond.expr, pkg_exist)
|
|
||||||
g.write(')')
|
|
||||||
return is_cond_true
|
|
||||||
}
|
|
||||||
ast.PrefixExpr {
|
|
||||||
g.write(cond.op.str())
|
|
||||||
return g.comptime_if_cond(cond.right, pkg_exist)
|
|
||||||
}
|
|
||||||
ast.PostfixExpr {
|
|
||||||
ifdef := g.comptime_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.comptime_if_cond(cond.left, pkg_exist)
|
|
||||||
g.write(' ${cond.op} ')
|
|
||||||
r := g.comptime_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.no_type
|
|
||||||
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.sym(got_type).kind == .interface {
|
|
||||||
// `$if Foo is Interface {`
|
|
||||||
interface_sym := g.table.sym(got_type)
|
|
||||||
if interface_sym.info is ast.Interface {
|
|
||||||
// q := g.table.sym(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 {
|
|
||||||
// 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.comptime_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) comptime_if_to_ifdef(name string, is_comptime_option 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'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'es5' {
|
|
||||||
if g.pref.output_es5 {
|
|
||||||
return 'true'
|
|
||||||
} else {
|
|
||||||
return 'false'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//
|
|
||||||
'js' {
|
|
||||||
return 'true'
|
|
||||||
}
|
|
||||||
'native' {
|
|
||||||
return 'false'
|
|
||||||
}
|
|
||||||
// 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 'checkDefine("CUSTOM_DEFINE_no_bounds_checking")'
|
|
||||||
}
|
|
||||||
'freestanding' {
|
|
||||||
return '_VFREESTANDING'
|
|
||||||
}
|
|
||||||
'autofree' {
|
|
||||||
return '_VAUTOFREE'
|
|
||||||
}
|
|
||||||
// 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.endianness == "LE")'
|
|
||||||
}
|
|
||||||
'big_endian' {
|
|
||||||
return '(\$os.endianness == "BE")'
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if is_comptime_option
|
|
||||||
|| (g.pref.compile_defines_all.len > 0 && name in g.pref.compile_defines_all) {
|
|
||||||
return 'checkDefine("CUSTOM_DEFINE_${name}")'
|
|
||||||
}
|
|
||||||
return error('bad os ifdef name "${name}"') // should never happen, caught in the checker
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return error('none')
|
|
||||||
}
|
|
||||||
|
@ -2722,10 +2722,6 @@ fn (mut g JsGen) need_tmp_var_in_if(node ast.IfExpr) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn (mut g JsGen) gen_if_expr(node ast.IfExpr) {
|
fn (mut g JsGen) gen_if_expr(node ast.IfExpr) {
|
||||||
if node.is_comptime {
|
|
||||||
g.comptime_if(node)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// For simpe if expressions we can use C's `?:`
|
// For simpe if expressions we can use C's `?:`
|
||||||
// `if x > 0 { 1 } else { 2 }` => `(x > 0)? (1) : (2)`
|
// `if x > 0 { 1 } else { 2 }` => `(x > 0)? (1) : (2)`
|
||||||
// For if expressions with multiple statements or another if expression inside, it's much
|
// For if expressions with multiple statements or another if expression inside, it's much
|
||||||
@ -2734,6 +2730,8 @@ fn (mut g JsGen) gen_if_expr(node ast.IfExpr) {
|
|||||||
// Always use this in -autofree, since ?: can have tmp expressions that have to be freed.
|
// Always use this in -autofree, since ?: can have tmp expressions that have to be freed.
|
||||||
needs_tmp_var := g.need_tmp_var_in_if(node)
|
needs_tmp_var := g.need_tmp_var_in_if(node)
|
||||||
tmp := if needs_tmp_var { g.new_tmp_var() } else { '' }
|
tmp := if needs_tmp_var { g.new_tmp_var() } else { '' }
|
||||||
|
mut is_true := false
|
||||||
|
mut comptime_has_true_branch := false
|
||||||
|
|
||||||
if needs_tmp_var {
|
if needs_tmp_var {
|
||||||
if node.typ.has_flag(.option) {
|
if node.typ.has_flag(.option) {
|
||||||
@ -2746,14 +2744,32 @@ fn (mut g JsGen) gen_if_expr(node ast.IfExpr) {
|
|||||||
prev := g.inside_ternary
|
prev := g.inside_ternary
|
||||||
g.inside_ternary = true
|
g.inside_ternary = true
|
||||||
for i, branch in node.branches {
|
for i, branch in node.branches {
|
||||||
if i > 0 {
|
if node.is_comptime {
|
||||||
g.write(' : ')
|
$if debug_comptime_branch_context ? {
|
||||||
}
|
g.write('/* ${branch.cond} */')
|
||||||
if i < node.branches.len - 1 || !node.has_else {
|
}
|
||||||
g.write('(')
|
// comptime $if, only generate the true branch
|
||||||
g.expr(branch.cond)
|
if i < node.branches.len - 1 || !node.has_else {
|
||||||
g.write(').valueOf()')
|
if !g.comptime_if_result(branch) {
|
||||||
g.write(' ? ')
|
continue
|
||||||
|
}
|
||||||
|
comptime_has_true_branch = true
|
||||||
|
} else {
|
||||||
|
// else branch
|
||||||
|
if comptime_has_true_branch {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if i > 0 {
|
||||||
|
g.write(' : ')
|
||||||
|
}
|
||||||
|
if i < node.branches.len - 1 || !node.has_else {
|
||||||
|
g.write('(')
|
||||||
|
g.expr(branch.cond)
|
||||||
|
g.write(').valueOf()')
|
||||||
|
g.write(' ? ')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
g.stmts(branch.stmts)
|
g.stmts(branch.stmts)
|
||||||
}
|
}
|
||||||
@ -2784,6 +2800,8 @@ fn (mut g JsGen) gen_if_expr(node ast.IfExpr) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is_true = false
|
||||||
|
comptime_has_true_branch = false
|
||||||
for i, branch in node.branches {
|
for i, branch in node.branches {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
g.write('} else ')
|
g.write('} else ')
|
||||||
@ -2796,6 +2814,9 @@ fn (mut g JsGen) gen_if_expr(node ast.IfExpr) {
|
|||||||
cvar_name := guard_vars[guard_idx]
|
cvar_name := guard_vars[guard_idx]
|
||||||
g.writeln('\tlet err = ${cvar_name}.err;')
|
g.writeln('\tlet err = ${cvar_name}.err;')
|
||||||
}
|
}
|
||||||
|
if node.is_comptime && !comptime_has_true_branch {
|
||||||
|
is_true = true
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
match branch.cond {
|
match branch.cond {
|
||||||
ast.IfGuardExpr {
|
ast.IfGuardExpr {
|
||||||
@ -2809,7 +2830,20 @@ fn (mut g JsGen) gen_if_expr(node ast.IfExpr) {
|
|||||||
g.writeln('if (${var_name}.state == 0) {')
|
g.writeln('if (${var_name}.state == 0) {')
|
||||||
} else {
|
} else {
|
||||||
g.write('if (${var_name} = ')
|
g.write('if (${var_name} = ')
|
||||||
g.expr(branch.cond.expr)
|
if node.is_comptime {
|
||||||
|
$if debug_comptime_branch_context ? {
|
||||||
|
g.write('/* ${branch.cond} */')
|
||||||
|
}
|
||||||
|
is_true = g.comptime_if_result(branch)
|
||||||
|
if is_true {
|
||||||
|
g.write('1')
|
||||||
|
comptime_has_true_branch = true
|
||||||
|
} else {
|
||||||
|
g.write('0')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
g.expr(branch.cond.expr)
|
||||||
|
}
|
||||||
g.writeln(', ${var_name}.state == 0) {')
|
g.writeln(', ${var_name}.state == 0) {')
|
||||||
}
|
}
|
||||||
if short_opt || branch.cond.vars[0].name != '_' {
|
if short_opt || branch.cond.vars[0].name != '_' {
|
||||||
@ -2820,7 +2854,17 @@ fn (mut g JsGen) gen_if_expr(node ast.IfExpr) {
|
|||||||
branch.cond.vars[0].name
|
branch.cond.vars[0].name
|
||||||
}
|
}
|
||||||
g.write('\tlet ${cond_var_name} = ')
|
g.write('\tlet ${cond_var_name} = ')
|
||||||
g.expr(branch.cond.expr)
|
if node.is_comptime {
|
||||||
|
is_true = g.comptime_if_result(branch)
|
||||||
|
if is_true {
|
||||||
|
g.write('1')
|
||||||
|
comptime_has_true_branch = true
|
||||||
|
} else {
|
||||||
|
g.write('0')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
g.expr(branch.cond.expr)
|
||||||
|
}
|
||||||
g.writeln(';')
|
g.writeln(';')
|
||||||
} else {
|
} else {
|
||||||
g.writeln('\tlet ${branch.cond.vars}[0].name = ${var_name}.data;')
|
g.writeln('\tlet ${branch.cond.vars}[0].name = ${var_name}.data;')
|
||||||
@ -2828,16 +2872,31 @@ fn (mut g JsGen) gen_if_expr(node ast.IfExpr) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
g.write('if ((')
|
if node.is_comptime {
|
||||||
g.expr(branch.cond)
|
g.write('if (')
|
||||||
g.writeln(').valueOf()) {')
|
$if debug_comptime_branch_context ? {
|
||||||
|
g.write('/* ${branch.cond} */')
|
||||||
|
}
|
||||||
|
is_true = g.comptime_if_result(branch)
|
||||||
|
if is_true {
|
||||||
|
g.write('1')
|
||||||
|
comptime_has_true_branch = true
|
||||||
|
} else {
|
||||||
|
g.write('0')
|
||||||
|
}
|
||||||
|
g.writeln(') {')
|
||||||
|
} else {
|
||||||
|
g.write('if ((')
|
||||||
|
g.expr(branch.cond)
|
||||||
|
g.writeln(').valueOf()) {')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
g.inc_indent()
|
g.inc_indent()
|
||||||
if needs_tmp_var {
|
if needs_tmp_var {
|
||||||
g.stmts_with_tmp_var(branch.stmts, tmp)
|
g.stmts_with_tmp_var(branch.stmts, tmp)
|
||||||
} else {
|
} else if (node.is_comptime && is_true) || !node.is_comptime {
|
||||||
g.stmts(branch.stmts)
|
g.stmts(branch.stmts)
|
||||||
}
|
}
|
||||||
g.dec_indent()
|
g.dec_indent()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user