diff --git a/compiler/cgen.v b/compiler/cgen.v index 3585c8e1c0..04aa52b7f0 100644 --- a/compiler/cgen.v +++ b/compiler/cgen.v @@ -5,6 +5,7 @@ module main import os +import strings struct CGen { out os.File @@ -20,6 +21,7 @@ struct CGen { so_fns []string consts_init []string lines []string + //buf strings.Builder is_user bool mut: run Pass @@ -37,11 +39,11 @@ fn new_cgen(out_name_c string) *CGen { out := os.create(path) or { println('failed to create $path') return &CGen{} -} - + } gen := &CGen { out_path: path out: out + //buf: strings.new_builder(10000) lines: _make(0, 1000, sizeof(string)) } return gen @@ -144,7 +146,8 @@ fn (g mut CGen) set_placeholder2(pos int, val string) { } fn (g mut CGen) insert_before(val string) { - g.lines.insert(g.lines.len - 1, val) + prev := g.lines[g.lines.len - 1] + g.lines[g.lines.len - 1] = '$prev \n $val \n' } fn (g mut CGen) register_thread_fn(wrapper_name, wrapper_text, struct_text string) { diff --git a/compiler/main.v b/compiler/main.v index 4f8d6be143..c000011cb4 100644 --- a/compiler/main.v +++ b/compiler/main.v @@ -44,11 +44,14 @@ enum OS { } enum Pass { - // A very short pass that only looks at imports in the beginning of each file + // A very short pass that only looks at imports in the beginning of + // each file imports - // First pass, only parses and saves declarations (fn signatures, consts, types). + // First pass, only parses and saves declarations (fn signatures, + // consts, types). // Skips function bodies. - // We need this because in V things can be used before they are declared. + // We need this because in V things can be used before they are + // declared. decl // Second pass, parses function bodies and generates C or machine code. main @@ -622,6 +625,7 @@ mut args := '' if v.pref.build_mode != .build && (v.os == .linux || v.os == .freebsd || v.os == .openbsd || v.os == .netbsd || v.os == .dragonfly) { a << '-lm -lpthread ' + // -ldl is a Linux only thing. BSDs have it in libc. if v.os == .linux { a << ' -ldl ' } diff --git a/compiler/parser.v b/compiler/parser.v index e83070e056..dd8f9205f6 100644 --- a/compiler/parser.v +++ b/compiler/parser.v @@ -104,6 +104,7 @@ fn (c mut V) new_parser(path string, run Pass) Parser { fn (p mut Parser) next() { p.prev_tok2 = p.prev_tok p.prev_tok = p.tok + p.scanner.prev_tok = p.tok res := p.scanner.scan() p.tok = res.tok p.lit = res.lit @@ -144,7 +145,7 @@ fn (p mut Parser) parse() { p.mod = fq_mod.replace('.', '_dot_') if p.run == .imports { for p.tok == .key_import && p.peek() != .key_const { - p.import_statement() + p.imports() } if p.table.imports.contains('builtin') { p.error('module `builtin` cannot be imported') @@ -160,7 +161,10 @@ fn (p mut Parser) parse() { } else { // TODO remove imported consts from the language - p.import_statement() + p.imports() + if p.tok != .key_import { + p.fgenln('') + } } case Token.key_enum: p.next() @@ -285,7 +289,7 @@ fn (p mut Parser) parse() { } } -fn (p mut Parser) import_statement() { +fn (p mut Parser) imports() { p.check(.key_import) // `import ()` if p.tok == .lpar { @@ -482,7 +486,7 @@ fn (p mut Parser) struct_decl() { else { // type alias is generated later if !is_c { - kind := if is_union{'union'} else { 'struct'} + kind := if is_union {'union'} else {'struct'} p.gen_typedef('typedef $kind $name $name;') p.gen_type('$kind $name {') } @@ -510,9 +514,7 @@ fn (p mut Parser) struct_decl() { } } // Struct `C.Foo` declaration, no body - // println('EEEE $is_c $is_struct') if is_c && is_struct && p.tok != .lcbr { - // println('skipping struct header $name') p.table.register_type2(typ) return } @@ -522,7 +524,15 @@ fn (p mut Parser) struct_decl() { mut is_pub := false mut is_mut := false mut names := []string// to avoid dup names TODO alloc perf - // mut is_mut_mut := false +/* + mut fmt_max_len := 0 + for field in typ.fields { + if field.name.len > max_len { + fmt_max_len = field.name.len + } + } + println('fmt max len = $max_len nrfields=$typ.fields.len pass=$p.run') +*/ for p.tok != .rcbr { if p.tok == .key_pub { if is_pub { @@ -592,6 +602,7 @@ fn (p mut Parser) struct_decl() { } if !is_ph && p.first_run() { p.table.register_type2(typ) + //println('registering 1 nrfields=$typ.fields.len') } p.check(.rcbr) if !is_c { @@ -619,8 +630,8 @@ fn (p mut Parser) enum_decl(_enum_name string) { for p.tok == .name { field := p.check_name() fields << field - name := '${p.mod}__${enum_name}_$field' p.fgenln('') + name := '${p.mod}__${enum_name}_$field' if p.run == .main { p.cgen.consts << '#define $name $val' } @@ -655,7 +666,7 @@ fn (p mut Parser) check_name() string { fn (p mut Parser) check_string() string { s := p.lit - p.check(.strtoken) + p.check(.str) return s } @@ -663,7 +674,7 @@ fn (p &Parser) strtok() string { if p.tok == .name { return p.lit } - if p.tok == .strtoken { + if p.tok == .str { return '"$p.lit"' } res := p.tok.str() @@ -701,6 +712,11 @@ fn (p mut Parser) check(expected Token) { p.fmt_inc() } p.next() + +if p.scanner.line_comment != '' { + //p.fgenln('// ! "$p.scanner.line_comment"') + //p.scanner.line_comment = '' +} } fn (p mut Parser) error(s string) { @@ -919,7 +935,7 @@ fn (p &Parser) print_tok() { println(p.lit) return } - if p.tok == .strtoken { + if p.tok == .str { println('"$p.lit"') return } @@ -1585,6 +1601,7 @@ fn (p mut Parser) dot(str_typ string, method_ph int) string { if p.tok == .key_type { field_name = 'type' } + p.fgen(field_name) p.log('dot() field_name=$field_name typ=$str_typ') //if p.fileis('main.v') { //println('dot() field_name=$field_name typ=$str_typ prev_tok=${prev_tok.str()}') @@ -2120,7 +2137,7 @@ fn (p mut Parser) factor() string { p.char_expr() typ = 'byte' return typ - case Token.strtoken: + case Token.str: p.string_expr() typ = 'string' return typ @@ -2232,11 +2249,11 @@ fn format_str(str string) string { } fn (p mut Parser) string_expr() { - // println('.strtoken EXPR') + // println('.str EXPR') str := p.lit - p.fgen('\'$str\'') - // No ${}, just return simple string + // No ${}, just return a simple string if p.peek() != .dollar { + p.fgen('\'$str\'') // println('before format: "$str"') f := format_str(str) // println('after format: "$str"') @@ -2252,8 +2269,11 @@ fn (p mut Parser) string_expr() { // tmp := p.get_tmp() mut args := '"' mut format := '"' - for p.tok == .strtoken { + p.fgen('\'') + mut complex_inter := false // for vfmt + for p.tok == .str { // Add the string between %d's + p.fgen(p.lit) p.lit = p.lit.replace('%', '%%') format += format_str(p.lit) p.next()// skip $ @@ -2261,7 +2281,13 @@ fn (p mut Parser) string_expr() { continue } // Handle .dollar - p.next() + p.check(.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 p.cgen.start_tmp() typ := p.bool_expression() @@ -2299,6 +2325,10 @@ fn (p mut Parser) string_expr() { format += f } } + if complex_inter { + p.fgen('}') +} + p.fgen('\'') // println("hello %d", num) optimization. if p.cgen.nogen { return @@ -3140,7 +3170,7 @@ else { fn (p mut Parser) return_st() { p.cgen.insert_before(p.cur_fn.defer_text) p.check(.key_return) - + p.fgen(' ') fn_returns := p.cur_fn.typ != 'void' if fn_returns { if p.tok == .rcbr { diff --git a/compiler/scanner.v b/compiler/scanner.v index 1dd2b64bcd..f1e8555e8e 100644 --- a/compiler/scanner.v +++ b/compiler/scanner.v @@ -19,15 +19,15 @@ mut: debug bool line_comment string started bool - is_fmt bool // vfmt fields fmt_out strings.Builder fmt_indent int fmt_line_empty bool + prev_tok Token } const ( - SINGLE_QUOTE = `\'` + SingleQuote = `\'` //QUOTE = `"` ) @@ -159,9 +159,6 @@ fn (s mut Scanner) skip_whitespace() { if !(s.text[s.pos] == `\n` && s.pos > 0 && s.text[s.pos-1] == `\r`) { s.line_nr++ } - if s.is_fmt { - return - } } s.pos++ } @@ -179,11 +176,17 @@ fn (s mut Scanner) get_var_name(pos int) string { } // CAO stands for Compound Assignment Operators (e.g '+=' ) +/* fn (s mut Scanner) cao_change(operator string) { s.text = s.text.substr(0, s.pos - operator.len) + ' = ' + s.get_var_name(s.pos - operator.len) + ' ' + operator + ' ' + s.text.substr(s.pos + 1, s.text.len) } +*/ fn (s mut Scanner) scan() ScanRes { +if s.line_comment != '' { + //s.fgenln('// LOL "$s.line_comment"') + //s.line_comment = '' +} // if s.file_path == 'd.v' { // println('\nscan()') // } @@ -199,19 +202,15 @@ fn (s mut Scanner) scan() ScanRes { if !s.inside_string { s.skip_whitespace() } - if s.is_fmt && s.text[s.pos] == `\n` { - return scan_res(.nl, '') - } // End of $var, start next string - if !s.is_fmt && s.dollar_end { + if s.dollar_end { // fmt.Println("end of $var, get string", s.pos, string(s.text[s.pos])) - if s.text[s.pos] == SINGLE_QUOTE { - // fmt.Println("ENDDD") + if s.text[s.pos] == SingleQuote { s.dollar_end = false - return scan_res(.strtoken, '') + return scan_res(.str, '') } s.dollar_end = false - return scan_res(.strtoken, s.ident_string()) + return scan_res(.str, s.ident_string()) } s.skip_whitespace() // end of file @@ -242,15 +241,15 @@ fn (s mut Scanner) scan() ScanRes { // at the next ', skip it if s.inside_string { // println('is_letter inside string! nextc=${nextc.str()}') - if next_char == SINGLE_QUOTE { + if next_char == SingleQuote { // println('var is last before QUOTE') s.pos++ s.dollar_start = false s.inside_string = false } } - if s.dollar_start && next_char != `.` { - // println('INSIDE .strtoken .dollar var=$name') + if s.dollar_start && next_char != `.` {//&& next_char != `(` { + // println('INSIDE .str .dollar var=$name') s.dollar_end = true s.dollar_start = false } @@ -261,7 +260,7 @@ fn (s mut Scanner) scan() ScanRes { } return scan_res(.name, name) } - // number, `.123` + // `123`, `.123` else if c.is_digit() || c == `.` && nextc.is_digit() { num := s.ident_number() return scan_res(.integer, num) @@ -308,11 +307,11 @@ fn (s mut Scanner) scan() ScanRes { return scan_res(.mod, '') case `?`: return scan_res(.question, '') - case SINGLE_QUOTE: - return scan_res(.strtoken, s.ident_string()) + case SingleQuote: + return scan_res(.str, s.ident_string()) // TODO allow double quotes // case QUOTE: - // return scan_res(.strtoken, s.ident_string()) + // return scan_res(.str, s.ident_string()) case `\``: return scan_res(.chartoken, s.ident_char()) case `(`: @@ -337,11 +336,11 @@ fn (s mut Scanner) scan() ScanRes { if s.inside_string { s.pos++ // TODO UN.neEDED? - if s.text[s.pos] == SINGLE_QUOTE { + if s.text[s.pos] == SingleQuote { s.inside_string = false - return scan_res(.strtoken, '') + return scan_res(.str, '') } - return scan_res(.strtoken, s.ident_string()) + return scan_res(.str, s.ident_string()) } else { return scan_res(.rcbr, '') @@ -388,10 +387,6 @@ fn (s mut Scanner) scan() ScanRes { } s.line_nr++ hash := s.text.substr(start, s.pos) - if s.is_fmt { - // fmt needs NL after # - s.pos-- - } return scan_res(.hash, hash.trim_space()) case `>`: if nextc == `=` { @@ -471,16 +466,9 @@ fn (s mut Scanner) scan() ScanRes { s.line_nr++ s.line_comment = s.text.substr(start + 1, s.pos) s.line_comment = s.line_comment.trim_space() - s.fgenln('// $s.line_comment') - if s.is_fmt { - // fmt needs NL after comment - s.pos-- - } - else { - // Skip comment - return s.scan() - } - return scan_res(.line_com, s.line_comment) + s.fgenln('// ${s.prev_tok.str()} "$s.line_comment"') + // Skip the comment (return the next token) + return s.scan() } // Multiline comments if nextc == `*` { @@ -509,9 +497,6 @@ fn (s mut Scanner) scan() ScanRes { end := s.pos + 1 comm := s.text.substr(start, end) s.fgenln(comm) - if s.is_fmt { - return scan_res(.mline_com, comm) - } // Skip if not in fmt mode return s.scan() } @@ -564,7 +549,7 @@ fn (s mut Scanner) ident_string() string { } prevc := s.text[s.pos - 1] // end of string - if c == SINGLE_QUOTE && (prevc != slash || (prevc == slash && s.text[s.pos - 2] == slash)) { + if c == SingleQuote && (prevc != slash || (prevc == slash && s.text[s.pos - 2] == slash)) { // handle '123\\' slash at the end break } @@ -581,17 +566,15 @@ fn (s mut Scanner) ident_string() string { s.error('0 character in a string literal') } // ${var} - if !s.is_fmt && c == `{` && prevc == `$` { + if c == `{` && prevc == `$` { s.inside_string = true - // fmt.Println("breaking out of is()") // so that s.pos points to $ at the next step s.pos -= 2 - // fmt.Println("break pos=", s.pos, "c=", string(s.text[s.pos]), "d=", s.text[s.pos]) break } // $var // if !s.is_fmt && c != `{` && c != ` ` && ! (c >= `0` && c <= `9`) && prevc == `$` { - if !s.is_fmt && (c.is_letter() || c == `_`) && prevc == `$` { + if (c.is_letter() || c == `_`) && prevc == `$` { s.inside_string = true s.dollar_start = true // println('setting s.dollar=true pos=$s.pos') @@ -600,7 +583,7 @@ fn (s mut Scanner) ident_string() string { } } mut lit := '' - if s.text[start] == SINGLE_QUOTE { + if s.text[start] == SingleQuote { start++ } mut end := s.pos @@ -685,7 +668,7 @@ fn (s mut Scanner) peek() Token { fn (s mut Scanner) debug_tokens() { s.pos = 0 fname := s.file_path.all_after('/') - println('\n===DEBUG TOKENS $fname ============') + println('\n===DEBUG TOKENS $fname===') // allToks := '' s.debug = true for { diff --git a/compiler/token.v b/compiler/token.v index f68b409af0..364299ebed 100644 --- a/compiler/token.v +++ b/compiler/token.v @@ -6,20 +6,21 @@ module main enum Token { eof - name - integer - strtoken - chartoken + name // user + integer // 123 + str // 'foo' + str_inter // 'name=$user.name' + chartoken // `A` plus minus mul div mod - xor - pipe - inc - dec - and + xor // ^ + pipe // | + inc // ++ + dec // -- + and // && logical_or not bit_not @@ -61,8 +62,8 @@ enum Token { ge le // comments - line_com - mline_com + //line_com + //mline_com nl dot dotdot @@ -127,7 +128,7 @@ fn build_token_str() []string { s[Token.eof] = '.eof' s[Token.name] = '.name' s[Token.integer] = '.integer' - s[Token.strtoken] = 'STR' + s[Token.str] = 'STR' s[Token.chartoken] = '.chartoken' s[Token.plus] = '+' s[Token.minus] = '-' @@ -177,7 +178,7 @@ fn build_token_str() []string { s[Token.question] = '?' s[Token.left_shift] = '<<' s[Token.righ_shift] = '>>' - s[Token.line_com] = '//' + //s[Token.line_com] = '//' s[Token.nl] = 'NLL' s[Token.dollar] = '$' s[Token.key_assert] = 'assert' diff --git a/examples/terminal_control.v b/examples/terminal_control.v index 43e185f745..c8ef29cedf 100644 --- a/examples/terminal_control.v +++ b/examples/terminal_control.v @@ -27,4 +27,4 @@ fn standing_line(x,y,size int, ch string) { print(term.bold(term.yellow(ch))) i++ } -} \ No newline at end of file +} diff --git a/vlib/builtin/string_test.v b/vlib/builtin/string_test.v index 99507d58b7..6d80ec6b37 100644 --- a/vlib/builtin/string_test.v +++ b/vlib/builtin/string_test.v @@ -299,3 +299,22 @@ fn test_reverse() { t := '' assert t.reverse() == t } + + +struct Foo { + bar int +} + +fn (f Foo) baz() string { + return 'baz' +} + +fn test_interpolation() { + num := 7 + mut s := 'number=$num' + assert s == 'number=7' + foo := Foo{} + s = 'baz=${foo.baz()}' + assert s == 'baz=baz' + +} diff --git a/vlib/cmath/complex.v b/vlib/cmath/complex.v index 06695ef2db..adcedc3d0d 100644 --- a/vlib/cmath/complex.v +++ b/vlib/cmath/complex.v @@ -505,4 +505,4 @@ pub fn (c Complex) acsch() Complex { // Complex Equals pub fn (c1 Complex) equals(c2 Complex) bool { return (c1.re == c2.re) && (c1.im == c2.im) -} \ No newline at end of file +} diff --git a/vlib/cmath/complex_test.v b/vlib/cmath/complex_test.v index 2a3757f83b..43a698a5cb 100644 --- a/vlib/cmath/complex_test.v +++ b/vlib/cmath/complex_test.v @@ -771,4 +771,4 @@ fn test_complex_acsch() { result = c1.acsch() // Some issue with precision comparison in f64 using == operator hence serializing to string assert result.str().eq(c2.str()) -} \ No newline at end of file +} diff --git a/vlib/crypto/md5/md5.v b/vlib/crypto/md5/md5.v index b17a8f5e12..9870a4271f 100644 --- a/vlib/crypto/md5/md5.v +++ b/vlib/crypto/md5/md5.v @@ -145,4 +145,4 @@ fn block(dig &Digest, p []byte) { pub fn (d &Digest) size() int { return Size } -pub fn (d &Digest) block_size() int { return BlockSize } \ No newline at end of file +pub fn (d &Digest) block_size() int { return BlockSize } diff --git a/vlib/crypto/sha1/sha1.v b/vlib/crypto/sha1/sha1.v index 61f5f6bcd9..ab1d23a223 100644 --- a/vlib/crypto/sha1/sha1.v +++ b/vlib/crypto/sha1/sha1.v @@ -150,4 +150,4 @@ fn block(dig &Digest, p []byte) { pub fn (d &Digest) size() int { return Size } -pub fn (d &Digest) block_size() int { return BlockSize } \ No newline at end of file +pub fn (d &Digest) block_size() int { return BlockSize } diff --git a/vlib/encoding/binary/binary.v b/vlib/encoding/binary/binary.v index db15aea4f1..69790d514f 100644 --- a/vlib/encoding/binary/binary.v +++ b/vlib/encoding/binary/binary.v @@ -90,4 +90,4 @@ pub fn big_endian_put_u64(b []byte, v u64) { b[5] = byte(v >> u64(16)) b[6] = byte(v >> u64(8)) b[7] = byte(v) -} \ No newline at end of file +} diff --git a/vlib/hash/crc32/crc32_test.v b/vlib/hash/crc32/crc32_test.v index 4aad09c614..928baeefd6 100644 --- a/vlib/hash/crc32/crc32_test.v +++ b/vlib/hash/crc32/crc32_test.v @@ -7,4 +7,4 @@ fn test_hash_crc32() { c := crc32.new(crc32.IEEE) assert c.checksum('testing crc32 again') == u32(1420327025) assert c.checksum('testing crc32 again').hex() == '0x54a87871' -} \ No newline at end of file +} diff --git a/vlib/log/log.v b/vlib/log/log.v index f808821e4d..a61595e51b 100644 --- a/vlib/log/log.v +++ b/vlib/log/log.v @@ -50,4 +50,4 @@ pub fn (l Log) debug(s string){ f := term.blue('D') println('[$f]$s') } -} \ No newline at end of file +} diff --git a/vlib/math/bits/bits.v b/vlib/math/bits/bits.v index b4cfe8d7fa..85e07aba83 100644 --- a/vlib/math/bits/bits.v +++ b/vlib/math/bits/bits.v @@ -44,4 +44,4 @@ pub fn rotate_left_64(x u64, k int) u64 { n := u64(64) s := u64(k) & (n - u64(1)) return u64(u64(x<>(n-s))) -} \ No newline at end of file +} diff --git a/vlib/math/fraction_test.v b/vlib/math/fraction_test.v index da760fa516..b46bae243a 100644 --- a/vlib/math/fraction_test.v +++ b/vlib/math/fraction_test.v @@ -138,4 +138,4 @@ fn test_gcd_and_reduce(){ mut f := fractions.fraction(3, 9) assert f.gcd() == 3 assert f.reduce().equals(fractions.fraction(1, 3)) -} \ No newline at end of file +} diff --git a/vlib/term/colors.v b/vlib/term/colors.v index f2ac7c6c0c..a7d5b9c894 100644 --- a/vlib/term/colors.v +++ b/vlib/term/colors.v @@ -128,4 +128,4 @@ pub fn white(msg string) string { pub fn yellow(msg string) string { return format(msg, '33', '39') -} \ No newline at end of file +} diff --git a/vlib/term/control.v b/vlib/term/control.v index 3040495734..56f67b0a79 100644 --- a/vlib/term/control.v +++ b/vlib/term/control.v @@ -107,4 +107,4 @@ pub fn show_cursor() pub fn hide_cursor() { print('\x1b[?25l') -} \ No newline at end of file +}