cgen: restructure string_inter_literal()

pull/5394/head
Uwe Krüger 2020-06-16 10:41:51 +02:00 committed by GitHub
parent 015d0c5e33
commit f2d9fa3815
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 293 additions and 108 deletions

View File

@ -73,10 +73,16 @@ pub struct StringInterLiteral {
pub: pub:
vals []string vals []string
exprs []Expr exprs []Expr
expr_fmts []string fwidths []int
precisions []int
pluss []bool
fills []bool
fmt_poss []token.Position
pos token.Position pos token.Position
pub mut: pub mut:
expr_types []table.Type expr_types []table.Type
fmts []byte
need_fmts []bool // an explicit non-default fmt required, e.g. `x`
} }
pub struct CharLiteral { pub struct CharLiteral {

View File

@ -160,10 +160,28 @@ pub fn (x Expr) str() string {
continue continue
} }
res << '$' res << '$'
if it.expr_fmts[i].len > 0 { needs_fspec := it.need_fmts[i] || it.pluss[i] || (it.fills[i] && it.fwidths[i] >= 0) || it.fwidths[i] != 0 || it.precisions[i] != 0
if needs_fspec || (it.exprs[i] !is Ident && it.exprs[i] !is SelectorExpr) {
res << '{' res << '{'
res << it.exprs[i].str() res << it.exprs[i].str()
res << it.expr_fmts[i] if needs_fspec {
res << ':'
if it.pluss[i] {
res << '+'
}
if it.fills[i] && it.fwidths[i] >= 0 {
res << '0'
}
if it.fwidths[i] != 0 {
res << '${it.fwidths[i]}'
}
if it.precisions[i] != 0 {
res << '.${it.precisions[i]}'
}
if it.need_fmts[i] {
res << '${it.fmts[i]:c}'
}
}
res << '}' res << '}'
} else { } else {
res << it.exprs[i].str() res << it.exprs[i].str()

View File

@ -5,6 +5,7 @@ module checker
import v.table import v.table
import v.token import v.token
import v.ast
pub fn (c &Checker) check_basic(got, expected table.Type) bool { pub fn (c &Checker) check_basic(got, expected table.Type) bool {
t := c.table t := c.table
@ -254,3 +255,66 @@ pub fn (c &Checker) symmetric_check(left, right table.Type) bool {
} }
return c.check_basic(left, right) return c.check_basic(left, right)
} }
pub fn (c &Checker) get_default_fmt(ftyp, typ table.Type) byte {
if typ.is_float() {
return `g`
} else if typ.is_signed() || typ.is_any_int() {
return `d`
} else if typ.is_unsigned() {
return `u`
} else if typ.is_pointer() {
return `p`
} else {
sym := c.table.get_type_symbol(ftyp)
if ftyp in [table.string_type, table.bool_type] || sym.kind in
[.enum_, .array, .array_fixed, .struct_, .map] || ftyp.has_flag(.optional) ||
sym.has_method('str') {
return `s`
} else {
return `_`
}
}
}
pub fn (c &Checker) string_inter_lit(mut node ast.StringInterLiteral) table.Type {
for i, expr in node.exprs {
ftyp := c.expr(expr)
node.expr_types << ftyp
typ := c.table.unalias_num_type(ftyp)
mut fmt := node.fmts[i]
// analyze and validate format specifier
if fmt !in [`E`, `F`, `G`, `e`, `f`, `g`, `d`, `u`, `x`, `X`, `o`, `c`, `s`, `p`, `_`] {
c.error('unknown format specifier `${fmt:c}`', node.fmt_poss[i])
}
if fmt == `_` { // set default representation for type if none has been given
fmt = c.get_default_fmt(ftyp, typ)
if fmt == `_` {
c.error('no known default format for type `${c.table.get_type_name(ftyp)}`',
node.fmt_poss[i])
} else {
node.fmts[i] = fmt
node.need_fmts[i] = false
}
} else { // check if given format specifier is valid for type
if node.precisions[i] != 0 && !typ.is_float() {
c.error('precision specification only valid for float types', node.fmt_poss[i])
}
if node.pluss[i] && !typ.is_number() {
c.error('plus prefix only allowd for numbers', node.fmt_poss[i])
}
if (typ.is_unsigned() && fmt !in [`u`, `x`, `X`, `o`, `c`]) ||
(typ.is_signed() && fmt !in [`d`, `x`, `X`, `o`, `c`]) ||
(typ.is_any_int() && fmt !in [`d`, `c`, `x`, `X`, `o`, `u`, `x`, `X`, `o`]) ||
(typ.is_float() && fmt !in [`E`, `F`, `G`, `e`, `f`, `g`]) ||
(typ.is_pointer() && fmt !in [`p`, `x`, `X`]) ||
(typ.is_string() && fmt != `s`) ||
(typ.idx() in [table.i64_type_idx, table.f64_type_idx] && fmt == `c`) {
c.error('illegal format specifier `${fmt:c}` for type `${c.table.get_type_name(ftyp)}`',
node.fmt_poss[i])
}
node.need_fmts[i] = fmt != c.get_default_fmt(ftyp, typ)
}
}
return table.string_type
}

View File

@ -1985,10 +1985,7 @@ pub fn (mut c Checker) expr(node ast.Expr) table.Type {
return table.string_type return table.string_type
} }
ast.StringInterLiteral { ast.StringInterLiteral {
for expr in it.exprs { return c.string_inter_lit(mut it)
it.expr_types << c.expr(expr)
}
return table.string_type
} }
ast.StructInit { ast.StructInit {
return c.struct_init(mut it) return c.struct_init(mut it)

View File

@ -0,0 +1,7 @@
vlib/v/checker/tests/string_interpolation_invalid_fmt.v:3:12: error: format specifier may only be one letter
1 | fn interpolate_wrong() string {
2 | a := 5
3 | x := '${a:xy}'
| ~~
4 | return x
5 | }

View File

@ -0,0 +1,5 @@
fn interpolate_wrong() string {
a := 5
x := '${a:xy}'
return x
}

View File

@ -0,0 +1,63 @@
vlib/v/checker/tests/string_interpolation_wrong_fmt.v:3:16: error: precision specification only valid for float types
1 | fn interpolate_str() string {
2 | a := 'hallo'
3 | x := '>${a:8.3s}<'
| ^
4 | y := '${a:G}'
5 | z := '${a:d}'
vlib/v/checker/tests/string_interpolation_wrong_fmt.v:4:12: error: illegal format specifier `G` for type `string`
2 | a := 'hallo'
3 | x := '>${a:8.3s}<'
4 | y := '${a:G}'
| ^
5 | z := '${a:d}'
6 | return x + y + z
vlib/v/checker/tests/string_interpolation_wrong_fmt.v:5:12: error: illegal format specifier `d` for type `string`
3 | x := '>${a:8.3s}<'
4 | y := '${a:G}'
5 | z := '${a:d}'
| ^
6 | return x + y + z
7 | }
vlib/v/checker/tests/string_interpolation_wrong_fmt.v:11:15: error: illegal format specifier `s` for type `f64`
9 | fn interpolate_f64() string {
10 | b := 1367.57
11 | x := '>${b:20s}<'
| ^
12 | y := '${b:d}'
13 | return x + y
vlib/v/checker/tests/string_interpolation_wrong_fmt.v:12:12: error: illegal format specifier `d` for type `f64`
10 | b := 1367.57
11 | x := '>${b:20s}<'
12 | y := '${b:d}'
| ^
13 | return x + y
14 | }
vlib/v/checker/tests/string_interpolation_wrong_fmt.v:19:14: error: illegal format specifier `d` for type `u32`
17 | u := u32(15)
18 | s := -12
19 | x := '${u:13d}'
| ^
20 | y := '${s:04u}'
21 | z := '${s:f}'
vlib/v/checker/tests/string_interpolation_wrong_fmt.v:20:14: error: illegal format specifier `u` for type `int`
18 | s := -12
19 | x := '${u:13d}'
20 | y := '${s:04u}'
| ^
21 | z := '${s:f}'
22 | q := '${u:v}'
vlib/v/checker/tests/string_interpolation_wrong_fmt.v:21:12: error: illegal format specifier `f` for type `int`
19 | x := '${u:13d}'
20 | y := '${s:04u}'
21 | z := '${s:f}'
| ^
22 | q := '${u:v}'
23 | return x + y + z + q
vlib/v/checker/tests/string_interpolation_wrong_fmt.v:22:12: error: unknown format specifier `v`
20 | y := '${s:04u}'
21 | z := '${s:f}'
22 | q := '${u:v}'
| ^
23 | return x + y + z + q
24 | }

View File

@ -0,0 +1,24 @@
fn interpolate_str() string {
a := 'hallo'
x := '>${a:8.3s}<'
y := '${a:G}'
z := '${a:d}'
return x + y + z
}
fn interpolate_f64() string {
b := 1367.57
x := '>${b:20s}<'
y := '${b:d}'
return x + y
}
fn interpolate_int() string {
u := u32(15)
s := -12
x := '${u:13d}'
y := '${s:04u}'
z := '${s:f}'
q := '${u:v}'
return x + y + z + q
}

View File

@ -721,10 +721,28 @@ pub fn (mut f Fmt) expr(node ast.Expr) {
continue continue
} }
f.write('$') f.write('$')
if it.expr_fmts[i].len > 0 { needs_fspec := it.need_fmts[i] || it.pluss[i] || (it.fills[i] && it.fwidths[i] >= 0) || it.fwidths[i] != 0 || it.precisions[i] != 0
if needs_fspec || (it.exprs[i] !is ast.Ident && it.exprs[i] !is ast.SelectorExpr) {
f.write('{') f.write('{')
f.expr(it.exprs[i]) f.expr(it.exprs[i])
f.write(it.expr_fmts[i]) if needs_fspec {
f.write(':')
if it.pluss[i] {
f.write('+')
}
if it.fills[i] && it.fwidths[i] >= 0 {
f.write('0')
}
if it.fwidths[i] != 0 {
f.write('${it.fwidths[i]}')
}
if it.precisions[i] != 0 {
f.write('.${it.precisions[i]}')
}
if it.need_fmts[i] {
f.write('${it.fmts[i]:c}')
}
}
f.write('}') f.write('}')
} else { } else {
f.expr(it.exprs[i]) f.expr(it.exprs[i])

View File

@ -4,7 +4,6 @@
module gen module gen
import strings import strings
import strconv
import v.ast import v.ast
import v.table import v.table
import v.pref import v.pref
@ -2918,101 +2917,40 @@ fn (g Gen) sort_structs(typesa []table.TypeSymbol) []table.TypeSymbol {
} }
fn (mut g Gen) string_inter_literal(node ast.StringInterLiteral) { fn (mut g Gen) string_inter_literal(node ast.StringInterLiteral) {
//if g.pref.autofree {
//g.write('_STR_TMP("')
//} else {
g.write('_STR("') g.write('_STR("')
//}
// Build the string with % // Build the string with %
mut fieldwidths := []int{}
mut specs := []byte{}
mut end_string := false mut end_string := false
for i, val in node.vals { for i, val in node.vals {
escaped_val := val.replace_each(['"', '\\"', '\r\n', '\\n', '\n', '\\n', '%', '%%']) escaped_val := val.replace_each(['"', '\\"', '\r\n', '\\n', '\n', '\\n', '%', '%%'])
if i >= node.exprs.len { if i >= node.exprs.len {
if escaped_val.len > 0 { if escaped_val.len > 0 {
end_string = true end_string = true
//if !g.pref.autofree { g.write('\\000')
g.write('\\000')
//}
g.write(escaped_val) g.write(escaped_val)
} }
continue break
} }
g.write(escaped_val) g.write(escaped_val)
sym := g.table.get_type_symbol(node.expr_types[i])
sfmt := node.expr_fmts[i]
mut fspec := `_` // placeholder
mut fmt := '' // field width and precision
if sfmt.len > 0 {
// analyze and validate format specifier
if sfmt[sfmt.len - 1] in [`E`, `F`, `G`, `e`, `f`, `g`,
`d`, `u`, `x`, `X`, `o`, `c`, `s`, `p`] {
fspec = sfmt[sfmt.len - 1]
}
fmt = if fspec == `_` {
sfmt[1..sfmt.len]
} else {
sfmt[1..sfmt.len - 1]
}
}
if fspec == `_` { // set default representation for type if still missing
if node.expr_types[i].is_float() {
fspec = `g`
} else if node.expr_types[i].is_signed() || node.expr_types[i].is_any_int() {
fspec = `d`
} else if node.expr_types[i].is_unsigned() {
fspec = `u`
} else if node.expr_types[i].is_pointer() {
fspec = `p`
} else if node.expr_types[i] in [table.string_type, table.bool_type] || sym.kind in
[.enum_, .array, .array_fixed, .struct_, .map] || g.typ(node.expr_types[i]).starts_with('Option') ||
sym.has_method('str') {
fspec = `s`
} else {
// default to int - TODO: should better be checked
fspec = `d`
}
}
fields := fmt.split('.')
// validate format
// only floats should have precision specifier
/*
if fields.len > 2 || fields.len == 2 && !(node.expr_types[i].is_float()) || node.expr_types[i].is_signed() &&
fspec !in [`d`, `c`, `x`, `X`, `o`] || node.expr_types[i].is_unsigned() && fspec !in [`u`, `x`,
`X`, `o`, `c`] || node.expr_types[i].is_any_int() && fspec !in [`d`, `c`, `x`, `X`,
`o`, `u`,
`x`, `X`, `o`] || node.expr_types[i].is_float() && fspec !in [`E`, `F`, `G`, `e`,
`f`, `g`] || node.expr_types[i].is_pointer() && fspec !in [`p`, `x`, `X`] {
verror('illegal format specifier ${fspec:c} for type ${g.table.get_type_name(node.expr_types[i])}')
}
*/
// make sure that format paramters are valid numbers
/*
for j, f in fields {
for k, c in f {
if (c < `0` || c > `9`) && !(j == 0 && k == 0 && (node.expr_types[i].is_number() &&
c == `+` || c == `-`)) {
verror('illegal character ${c:c} in format specifier ${fmt}')
}
}
}
*/
specs << fspec
fieldwidths << if fields.len == 0 {
0
} else {
strconv.atoi(fields[0])
}
// write correct format specifier to intermediate string // write correct format specifier to intermediate string
g.write('%') g.write('%')
fspec := node.fmts[i]
mut fmt := if node.pluss[i] { '+' } else { '' }
if node.fills[i] && node.fwidths[i] >= 0 {
fmt = '${fmt}0'
}
if node.fwidths[i] != 0 {
fmt = '$fmt${node.fwidths[i]}'
}
if node.precisions[i] != 0 {
fmt = '${fmt}.${node.precisions[i]}'
}
if fspec == `s` { if fspec == `s` {
if fields.len == 0 || strconv.atoi(fields[0]) == 0 { if node.fwidths[i] == 0 {
g.write('.*s') g.write('.*s')
} else { } else {
g.write('*.*s') g.write('*.*s')
} }
} else if node.expr_types[i].is_float() || node.expr_types[i].is_pointer() { } else if node.expr_types[i].is_float() {
g.write('$fmt${fspec:c}') g.write('$fmt${fspec:c}')
} else if node.expr_types[i].is_pointer() { } else if node.expr_types[i].is_pointer() {
if fspec == `p` { if fspec == `p` {
@ -3022,11 +2960,7 @@ fn (mut g Gen) string_inter_literal(node ast.StringInterLiteral) {
} }
} else if node.expr_types[i].is_int() { } else if node.expr_types[i].is_int() {
if fspec == `c` { if fspec == `c` {
if node.expr_types[i].idx() in [table.i64_type_idx, table.f64_type_idx] { g.write('${fmt}c')
verror('64 bit integer types cannot be interpolated as character')
} else {
g.write('${fmt}c')
}
} else { } else {
g.write('${fmt}"PRI${fspec:c}') g.write('${fmt}"PRI${fspec:c}')
if node.expr_types[i] in [table.i8_type, table.byte_type] { if node.expr_types[i] in [table.i8_type, table.byte_type] {
@ -3057,9 +2991,9 @@ fn (mut g Gen) string_inter_literal(node ast.StringInterLiteral) {
} else if node.expr_types[i] == table.bool_type { } else if node.expr_types[i] == table.bool_type {
g.expr(expr) g.expr(expr)
g.write(' ? _SLIT("true") : _SLIT("false")') g.write(' ? _SLIT("true") : _SLIT("false")')
} else if node.expr_types[i].is_number() || node.expr_types[i].is_pointer() || specs[i] == } else if node.expr_types[i].is_number() || node.expr_types[i].is_pointer() || node.fmts[i] ==
`d` { `d` {
if node.expr_types[i].is_signed() && specs[i] in [`x`, `X`, `o`] { if node.expr_types[i].is_signed() && node.fmts[i] in [`x`, `X`, `o`] {
// convert to unsigned first befors C's integer propagation strikes // convert to unsigned first befors C's integer propagation strikes
if node.expr_types[i] == table.i8_type { if node.expr_types[i] == table.i8_type {
g.write('(byte)(') g.write('(byte)(')
@ -3075,13 +3009,13 @@ fn (mut g Gen) string_inter_literal(node ast.StringInterLiteral) {
} else { } else {
g.expr(expr) g.expr(expr)
} }
} else if specs[i] == `s` { } else if node.fmts[i] == `s` {
g.gen_expr_to_string(expr, node.expr_types[i]) g.gen_expr_to_string(expr, node.expr_types[i])
} else { } else {
g.expr(expr) g.expr(expr)
} }
if specs[i] == `s` && fieldwidths[i] != 0 { if node.fmts[i] == `s` && node.fwidths[i] != 0 {
g.write(', ${fieldwidths[i]}') g.write(', ${node.fwidths[i]}')
} }
if i < node.exprs.len - 1 { if i < node.exprs.len - 1 {
g.write(', ') g.write(', ')

View File

@ -1367,9 +1367,11 @@ fn (mut g JsGen) gen_string_inter_literal(it ast.StringInterLiteral) {
continue continue
} }
expr := it.exprs[i] expr := it.exprs[i]
sfmt := it.expr_fmts[i] fmt := it.fmts[i]
fwidth := it.fwidths[i]
precision := it.precisions[i]
g.write('\${') g.write('\${')
if sfmt.len > 0 { if fmt != `_` || fwidth !=0 || precision != 0 {
// TODO: Handle formatting // TODO: Handle formatting
g.expr(expr) g.expr(expr)
} else { } else {

View File

@ -13,6 +13,7 @@ import v.errors
import os import os
import runtime import runtime
import time import time
import strconv
pub struct Parser { pub struct Parser {
file_name string // "/home/user/hello.v" file_name string // "/home/user/hello.v"
@ -1166,7 +1167,13 @@ fn (mut p Parser) string_expr() ast.Expr {
} }
mut exprs := []ast.Expr{} mut exprs := []ast.Expr{}
mut vals := []string{} mut vals := []string{}
mut efmts := []string{} mut has_fmts := []bool{}
mut fwidths := []int{}
mut precisions := []int{}
mut visible_pluss := []bool{}
mut fills := []bool{}
mut fmts := []byte{}
mut fposs := []token.Position{}
// Handle $ interpolation // Handle $ interpolation
p.inside_str_interp = true p.inside_str_interp = true
for p.tok.kind == .string { for p.tok.kind == .string {
@ -1177,31 +1184,66 @@ fn (mut p Parser) string_expr() ast.Expr {
} }
p.next() p.next()
exprs << p.expr(0) exprs << p.expr(0)
mut efmt := []string{} mut has_fmt := false
mut fwidth := 0
mut fwidthneg := false
mut precision := 0
mut visible_plus := false
mut fill := false
mut fmt := `_` // placeholder
if p.tok.kind == .colon { if p.tok.kind == .colon {
efmt << ':' has_fmt = true
p.next() p.next()
// ${num:-2d} // ${num:-2d}
if p.tok.kind == .minus { if p.tok.kind == .minus {
efmt << '-' fwidthneg = true
p.next()
} else if p.tok.kind == .plus {
visible_plus = true
p.next() p.next()
} }
// ${num:2d} // ${num:2d}
if p.tok.kind == .number { if p.tok.kind == .number {
efmt << p.tok.lit fields := p.tok.lit.split('.')
if fields[0].len > 0 && fields[0][0] == `0` {
fill = true
}
fwidth = strconv.atoi(fields[0])
if fwidthneg {
fwidth = -fwidth
}
if fields.len > 1 {
precision = strconv.atoi(fields[1])
}
p.next() p.next()
} }
if p.tok.kind == .name && p.tok.lit.len == 1 { if p.tok.kind == .name {
efmt << p.tok.lit if p.tok.lit.len == 1 {
p.next() fmt = p.tok.lit[0]
p.next()
} else {
p.error('format specifier may only be one letter')
}
} }
} }
efmts << efmt.join('') fwidths << fwidth
has_fmts << has_fmt
precisions << precision
visible_pluss << visible_plus
fmts << fmt
fills << fill
fposs << p.prev_tok.position()
} }
node = ast.StringInterLiteral{ node = ast.StringInterLiteral{
vals: vals vals: vals
exprs: exprs exprs: exprs
expr_fmts: efmts need_fmts: has_fmts // prelimery - until checker finds out if really needed
fwidths: fwidths
precisions: precisions
pluss: visible_pluss
fills: fills
fmts: fmts
fmt_poss: fposs
pos: pos pos: pos
} }
p.inside_str_interp = false p.inside_str_interp = false

View File

@ -187,6 +187,11 @@ pub fn (typ Type) is_number() bool {
return typ.idx() in number_type_idxs return typ.idx() in number_type_idxs
} }
[inline]
pub fn (typ Type) is_string() bool {
return typ.idx() in string_type_idxs
}
pub const ( pub const (
void_type_idx = 1 void_type_idx = 1
voidptr_type_idx = 2 voidptr_type_idx = 2