fix "" string interpolation bug
parent
d58982a6f6
commit
4d1f721558
|
@ -565,3 +565,11 @@ fn test_inter_before_comp_if() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn test_double_quote_inter() {
|
||||||
|
a := 1
|
||||||
|
b := 2
|
||||||
|
println("${a} ${b}")
|
||||||
|
assert "${a} ${b}" == "1 2"
|
||||||
|
assert '${a} ${b}' == "1 2"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -2280,154 +2280,6 @@ fn format_str(_str string) string {
|
||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (p mut Parser) string_expr() {
|
|
||||||
is_raw := p.tok == .name && p.lit == 'r'
|
|
||||||
is_cstr := p.tok == .name && p.lit == 'c'
|
|
||||||
if is_raw || is_cstr {
|
|
||||||
p.next()
|
|
||||||
}
|
|
||||||
str := p.lit
|
|
||||||
// No ${}, just return a simple string
|
|
||||||
if p.peek() != .str_dollar || is_raw {
|
|
||||||
f := if is_raw { cescaped_path(str) } else { format_str(str) }
|
|
||||||
// `C.puts('hi')` => `puts("hi");`
|
|
||||||
/*
|
|
||||||
Calling a C function sometimes requires a call to a string method
|
|
||||||
C.fun('ssss'.to_wide()) => fun(string_to_wide(tos3("ssss")))
|
|
||||||
*/
|
|
||||||
if (p.calling_c && p.peek() != .dot) || is_cstr || (p.pref.translated && p.mod == 'main') {
|
|
||||||
p.gen('"$f"')
|
|
||||||
}
|
|
||||||
else if p.is_sql {
|
|
||||||
p.gen("'$str'")
|
|
||||||
}
|
|
||||||
else if p.is_js {
|
|
||||||
p.gen('tos("$f")')
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
p.gen('tos3("$f")')
|
|
||||||
}
|
|
||||||
p.next()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
$if js {
|
|
||||||
p.error('js backend does not support string formatting yet')
|
|
||||||
}
|
|
||||||
p.is_alloc = true // $ interpolation means there's allocation
|
|
||||||
mut args := '"'
|
|
||||||
mut format := '"'
|
|
||||||
mut complex_inter := false // for vfmt
|
|
||||||
for p.tok == .str {
|
|
||||||
// Add the string between %d's
|
|
||||||
p.lit = p.lit.replace('%', '%%')
|
|
||||||
format += format_str(p.lit)
|
|
||||||
p.next()// skip $
|
|
||||||
if p.tok != .str_dollar {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Handle .dollar
|
|
||||||
p.check(.str_dollar)
|
|
||||||
// If there's no string after current token, it means we are in
|
|
||||||
// a complex expression (`${...}`)
|
|
||||||
if p.peek() != .str {
|
|
||||||
p.fgen('{')
|
|
||||||
complex_inter = true
|
|
||||||
}
|
|
||||||
// Get bool expr inside a temp var
|
|
||||||
typ, val_ := p.tmp_expr()
|
|
||||||
val := val_.trim_space()
|
|
||||||
args += ', $val'
|
|
||||||
if typ == 'string' {
|
|
||||||
// args += '.str'
|
|
||||||
// printf("%.*s", a.len, a.str) syntax
|
|
||||||
args += '.len, ${val}.str'
|
|
||||||
}
|
|
||||||
if typ == 'ustring' {
|
|
||||||
args += '.len, ${val}.s.str'
|
|
||||||
}
|
|
||||||
if typ == 'bool' {
|
|
||||||
//args += '.len, ${val}.str'
|
|
||||||
}
|
|
||||||
// Custom format? ${t.hour:02d}
|
|
||||||
custom := p.tok == .colon
|
|
||||||
if custom {
|
|
||||||
mut cformat := ''
|
|
||||||
p.next()
|
|
||||||
if p.tok == .dot {
|
|
||||||
cformat += '.'
|
|
||||||
p.next()
|
|
||||||
}
|
|
||||||
if p.tok == .minus { // support for left aligned formatting
|
|
||||||
cformat += '-'
|
|
||||||
p.next()
|
|
||||||
}
|
|
||||||
cformat += p.lit// 02
|
|
||||||
p.next()
|
|
||||||
fspec := p.lit // f
|
|
||||||
cformat += fspec
|
|
||||||
if fspec == 's' {
|
|
||||||
//println('custom str F=$cformat | format_specifier: "$fspec" | typ: $typ ')
|
|
||||||
if typ != 'string' {
|
|
||||||
p.error('only V strings can be formatted with a :${cformat} format, but you have given "${val}", which has type ${typ}')
|
|
||||||
}
|
|
||||||
args = args.all_before_last('${val}.len, ${val}.str') + '${val}.str'
|
|
||||||
}
|
|
||||||
format += '%$cformat'
|
|
||||||
p.next()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
f := p.typ_to_fmt(typ, 0)
|
|
||||||
if f == '' {
|
|
||||||
is_array := typ.starts_with('array_')
|
|
||||||
typ2 := p.table.find_type(typ)
|
|
||||||
has_str_method := p.table.type_has_method(typ2, 'str')
|
|
||||||
if is_array || has_str_method {
|
|
||||||
if is_array && !has_str_method {
|
|
||||||
p.gen_array_str(typ2)
|
|
||||||
}
|
|
||||||
tmp_var := p.get_tmp()
|
|
||||||
p.cgen.insert_before('string $tmp_var = ${typ}_str(${val});')
|
|
||||||
args = args.all_before_last(val) + '${tmp_var}.len, ${tmp_var}.str'
|
|
||||||
format += '%.*s '
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
p.error('unhandled sprintf format "$typ" ')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
format += f
|
|
||||||
}
|
|
||||||
//println('interpolation format is: |${format}| args are: |${args}| ')
|
|
||||||
}
|
|
||||||
if complex_inter {
|
|
||||||
p.fgen('}')
|
|
||||||
}
|
|
||||||
//p.fgen('\'')
|
|
||||||
// println("hello %d", num) optimization.
|
|
||||||
if p.cgen.nogen {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// println: don't allocate a new string, just print it.
|
|
||||||
$if !windows {
|
|
||||||
cur_line := p.cgen.cur_line.trim_space()
|
|
||||||
if cur_line == 'println (' && p.tok != .plus {
|
|
||||||
p.cgen.resetln(cur_line.replace('println (', 'printf('))
|
|
||||||
p.gen('$format\\n$args')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// '$age'! means the user wants this to be a tmp string (uses global buffer, no allocation,
|
|
||||||
// won't be used again)
|
|
||||||
// TODO remove this hack, do this automatically
|
|
||||||
if p.tok == .not {
|
|
||||||
p.check(.not)
|
|
||||||
p.gen('_STR_TMP($format$args)')
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Otherwise do len counting + allocation + sprintf
|
|
||||||
p.gen('_STR($format$args)')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// m := map[string]int{}
|
// m := map[string]int{}
|
||||||
// m := { 'one': 1 }
|
// m := { 'one': 1 }
|
||||||
fn (p mut Parser) map_init() string {
|
fn (p mut Parser) map_init() string {
|
||||||
|
|
|
@ -298,14 +298,15 @@ fn (s mut Scanner) scan() ScanRes {
|
||||||
s.inside_string = false
|
s.inside_string = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// end of `$expr`
|
||||||
if s.inter_start && next_char != `.` {
|
if s.inter_start && next_char != `.` {
|
||||||
s.inter_end = true
|
s.inter_end = true
|
||||||
s.inter_start = false
|
s.inter_start = false
|
||||||
}
|
}
|
||||||
if s.pos == 0 && next_char == ` ` {
|
if s.pos == 0 && next_char == ` ` {
|
||||||
s.pos++
|
|
||||||
//If a single letter name at the start of the file, increment
|
//If a single letter name at the start of the file, increment
|
||||||
//Otherwise the scanner would be stuck at s.pos = 0
|
//Otherwise the scanner would be stuck at s.pos = 0
|
||||||
|
s.pos++
|
||||||
}
|
}
|
||||||
return scan_res(.name, name)
|
return scan_res(.name, name)
|
||||||
}
|
}
|
||||||
|
@ -390,8 +391,7 @@ fn (s mut Scanner) scan() ScanRes {
|
||||||
return scan_res(.lcbr, '')
|
return scan_res(.lcbr, '')
|
||||||
}
|
}
|
||||||
`$` {
|
`$` {
|
||||||
// if s.inter_start {
|
if s.inside_string {
|
||||||
if s.inside_string {// || s.inter_start {
|
|
||||||
return scan_res(.str_dollar, '')
|
return scan_res(.str_dollar, '')
|
||||||
} else {
|
} else {
|
||||||
return scan_res(.dollar, '')
|
return scan_res(.dollar, '')
|
||||||
|
@ -402,8 +402,7 @@ fn (s mut Scanner) scan() ScanRes {
|
||||||
// s = `hello ${name} !`
|
// s = `hello ${name} !`
|
||||||
if s.inside_string {
|
if s.inside_string {
|
||||||
s.pos++
|
s.pos++
|
||||||
// TODO UNNEEDED?
|
if s.text[s.pos] == s.quote {
|
||||||
if s.text[s.pos] == single_quote {
|
|
||||||
s.inside_string = false
|
s.inside_string = false
|
||||||
return scan_res(.str, '')
|
return scan_res(.str, '')
|
||||||
}
|
}
|
||||||
|
@ -704,15 +703,19 @@ fn (s mut Scanner) ident_string() string {
|
||||||
if c == `0` && s.pos > 5 && s.expect('\\x0', s.pos - 3) {
|
if c == `0` && s.pos > 5 && s.expect('\\x0', s.pos - 3) {
|
||||||
s.error('0 character in a string literal')
|
s.error('0 character in a string literal')
|
||||||
}
|
}
|
||||||
// ${var}
|
// ${var} (ignore in vfmt mode)
|
||||||
if c == `{` && prevc == `$` && !is_raw && !s.is_fmt && s.count_symbol_before(s.pos-2, slash) % 2 == 0 {
|
if c == `{` && prevc == `$` && !is_raw && !s.is_fmt &&
|
||||||
|
s.count_symbol_before(s.pos-2, slash) % 2 == 0
|
||||||
|
{
|
||||||
s.inside_string = true
|
s.inside_string = true
|
||||||
// so that s.pos points to $ at the next step
|
// so that s.pos points to $ at the next step
|
||||||
s.pos -= 2
|
s.pos -= 2
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
// $var
|
// $var
|
||||||
if (c.is_letter() || c == `_`) && prevc == `$` && !s.is_fmt && !is_raw && s.count_symbol_before(s.pos-2, slash) % 2 == 0 {
|
if (c.is_letter() || c == `_`) && prevc == `$` && !s.is_fmt &&
|
||||||
|
!is_raw && s.count_symbol_before(s.pos-2, slash) % 2 == 0
|
||||||
|
{
|
||||||
s.inside_string = true
|
s.inside_string = true
|
||||||
s.inter_start = true
|
s.inter_start = true
|
||||||
s.pos -= 2
|
s.pos -= 2
|
||||||
|
|
|
@ -0,0 +1,154 @@
|
||||||
|
// Copyright (c) 2019 Alexander Medvednikov. All rights reserved.
|
||||||
|
// Use of this source code is governed by an MIT license
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
module compiler
|
||||||
|
|
||||||
|
fn (p mut Parser) string_expr() {
|
||||||
|
is_raw := p.tok == .name && p.lit == 'r'
|
||||||
|
is_cstr := p.tok == .name && p.lit == 'c'
|
||||||
|
if is_raw || is_cstr {
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
str := p.lit
|
||||||
|
// No ${}, just return a simple string
|
||||||
|
if p.peek() != .str_dollar || is_raw {
|
||||||
|
f := if is_raw { cescaped_path(str) } else { format_str(str) }
|
||||||
|
// `C.puts('hi')` => `puts("hi");`
|
||||||
|
/*
|
||||||
|
Calling a C function sometimes requires a call to a string method
|
||||||
|
C.fun('ssss'.to_wide()) => fun(string_to_wide(tos3("ssss")))
|
||||||
|
*/
|
||||||
|
if (p.calling_c && p.peek() != .dot) || is_cstr || (p.pref.translated && p.mod == 'main') {
|
||||||
|
p.gen('"$f"')
|
||||||
|
}
|
||||||
|
else if p.is_sql {
|
||||||
|
p.gen("'$str'")
|
||||||
|
}
|
||||||
|
else if p.is_js {
|
||||||
|
p.gen('tos("$f")')
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
p.gen('tos3("$f")')
|
||||||
|
}
|
||||||
|
p.next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
$if js {
|
||||||
|
p.error('js backend does not support string formatting yet')
|
||||||
|
}
|
||||||
|
p.is_alloc = true // $ interpolation means there's allocation
|
||||||
|
mut args := '"'
|
||||||
|
mut format := '"'
|
||||||
|
mut complex_inter := false // for vfmt
|
||||||
|
for p.tok == .str {
|
||||||
|
// Add the string between %d's
|
||||||
|
p.lit = p.lit.replace('%', '%%')
|
||||||
|
format += format_str(p.lit)
|
||||||
|
p.next()// skip $
|
||||||
|
if p.tok != .str_dollar {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Handle .dollar
|
||||||
|
p.check(.str_dollar)
|
||||||
|
// If there's no string after current token, it means we are in
|
||||||
|
// a complex expression (`${...}`)
|
||||||
|
if p.peek() != .str {
|
||||||
|
p.fgen('{')
|
||||||
|
complex_inter = true
|
||||||
|
}
|
||||||
|
// Get bool expr inside a temp var
|
||||||
|
typ, val_ := p.tmp_expr()
|
||||||
|
val := val_.trim_space()
|
||||||
|
args += ', $val'
|
||||||
|
if typ == 'string' {
|
||||||
|
// args += '.str'
|
||||||
|
// printf("%.*s", a.len, a.str) syntax
|
||||||
|
args += '.len, ${val}.str'
|
||||||
|
}
|
||||||
|
if typ == 'ustring' {
|
||||||
|
args += '.len, ${val}.s.str'
|
||||||
|
}
|
||||||
|
if typ == 'bool' {
|
||||||
|
//args += '.len, ${val}.str'
|
||||||
|
}
|
||||||
|
// Custom format? ${t.hour:02d}
|
||||||
|
custom := p.tok == .colon
|
||||||
|
if custom {
|
||||||
|
mut cformat := ''
|
||||||
|
p.next()
|
||||||
|
if p.tok == .dot {
|
||||||
|
cformat += '.'
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
if p.tok == .minus { // support for left aligned formatting
|
||||||
|
cformat += '-'
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
cformat += p.lit// 02
|
||||||
|
p.next()
|
||||||
|
fspec := p.lit // f
|
||||||
|
cformat += fspec
|
||||||
|
if fspec == 's' {
|
||||||
|
//println('custom str F=$cformat | format_specifier: "$fspec" | typ: $typ ')
|
||||||
|
if typ != 'string' {
|
||||||
|
p.error('only V strings can be formatted with a :${cformat} format, but you have given "${val}", which has type ${typ}')
|
||||||
|
}
|
||||||
|
args = args.all_before_last('${val}.len, ${val}.str') + '${val}.str'
|
||||||
|
}
|
||||||
|
format += '%$cformat'
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
f := p.typ_to_fmt(typ, 0)
|
||||||
|
if f == '' {
|
||||||
|
is_array := typ.starts_with('array_')
|
||||||
|
typ2 := p.table.find_type(typ)
|
||||||
|
has_str_method := p.table.type_has_method(typ2, 'str')
|
||||||
|
if is_array || has_str_method {
|
||||||
|
if is_array && !has_str_method {
|
||||||
|
p.gen_array_str(typ2)
|
||||||
|
}
|
||||||
|
tmp_var := p.get_tmp()
|
||||||
|
p.cgen.insert_before('string $tmp_var = ${typ}_str(${val});')
|
||||||
|
args = args.all_before_last(val) + '${tmp_var}.len, ${tmp_var}.str'
|
||||||
|
format += '%.*s '
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
p.error('unhandled sprintf format "$typ" ')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
format += f
|
||||||
|
}
|
||||||
|
//println('interpolation format is: |${format}| args are: |${args}| ')
|
||||||
|
}
|
||||||
|
if complex_inter {
|
||||||
|
p.fgen('}')
|
||||||
|
}
|
||||||
|
//p.fgen('\'')
|
||||||
|
// println("hello %d", num) optimization.
|
||||||
|
if p.cgen.nogen {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// println: don't allocate a new string, just print it.
|
||||||
|
$if !windows {
|
||||||
|
cur_line := p.cgen.cur_line.trim_space()
|
||||||
|
if cur_line == 'println (' && p.tok != .plus {
|
||||||
|
p.cgen.resetln(cur_line.replace('println (', 'printf('))
|
||||||
|
p.gen('$format\\n$args')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// '$age'! means the user wants this to be a tmp string (uses global buffer, no allocation,
|
||||||
|
// won't be used again)
|
||||||
|
// TODO remove this hack, do this automatically
|
||||||
|
if p.tok == .not {
|
||||||
|
p.check(.not)
|
||||||
|
p.gen('_STR_TMP($format$args)')
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Otherwise do len counting + allocation + sprintf
|
||||||
|
p.gen('_STR($format$args)')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue