| 1 | // Copyright (c) 2025 Alexander Medvednikov. All rights reserved. |
| 2 | // Use of this source code is governed by an MIT license |
| 3 | // that can be found in the LICENSE file. |
| 4 | module util |
| 5 | |
| 6 | const d_sig = "\$d('" |
| 7 | |
| 8 | // resolve_d_value replaces all occurrences of `$d('ident','value')` |
| 9 | // in `str` with either the default `'value'` param or a compile value passed via `-d ident=value`. |
| 10 | pub fn resolve_d_value(compile_values map[string]string, str string) !string { |
| 11 | start := str.index(d_sig) or { return error('no "${d_sig}...\')" could be found in "${str}"') } |
| 12 | mut i := 0 |
| 13 | mut ch := u8(`.`) |
| 14 | mut bd_ident := []u8{cap: 20} |
| 15 | mut blevel := 1 |
| 16 | for i = start + d_sig.len; i < str.len && ch != `'`; i++ { |
| 17 | ch = str[i] |
| 18 | if ch == `)` { |
| 19 | blevel-- |
| 20 | } else if ch == `(` { |
| 21 | blevel++ |
| 22 | } |
| 23 | if ch.is_letter() || ch.is_digit() || ch == `_` { |
| 24 | bd_ident << ch |
| 25 | } else { |
| 26 | if !(ch == `'`) { |
| 27 | if ch == `$` { |
| 28 | return error('cannot use string interpolation in compile time \$d() expression') |
| 29 | } |
| 30 | return error('invalid `\$d` identifier in "${str}", invalid character `${rune(ch)}`') |
| 31 | } |
| 32 | } |
| 33 | } |
| 34 | d_ident := bd_ident.bytestr().trim_space() |
| 35 | if d_ident == '' { |
| 36 | return error('first argument of `\$d` must be a string identifier') |
| 37 | } |
| 38 | |
| 39 | // At this point we should have a valid identifier in `d_ident`. |
| 40 | // Next we parse out the default string value. |
| 41 | |
| 42 | // Advance past the `,` and the opening `'` in second argument, or ... eat whatever is there: |
| 43 | for ; i < str.len; i++ { |
| 44 | ch = str[i] |
| 45 | match ch { |
| 46 | ` `, `,` { |
| 47 | continue |
| 48 | } |
| 49 | `'` { |
| 50 | i++ |
| 51 | } |
| 52 | else {} |
| 53 | } |
| 54 | |
| 55 | break |
| 56 | } |
| 57 | // Rinse, repeat for the expected default value string |
| 58 | ch = `.` |
| 59 | dv_start := i |
| 60 | mut dv_end := i |
| 61 | for i < str.len { |
| 62 | ch = str[i] |
| 63 | dv_end++ |
| 64 | i++ |
| 65 | match ch { |
| 66 | `'` { |
| 67 | break |
| 68 | } |
| 69 | `(` { |
| 70 | blevel++ |
| 71 | } |
| 72 | `)` { |
| 73 | blevel-- |
| 74 | if blevel <= 0 { |
| 75 | break |
| 76 | } |
| 77 | } |
| 78 | `$` { |
| 79 | return error('cannot use string interpolation in compile time \$d() expression') |
| 80 | } |
| 81 | else {} |
| 82 | } |
| 83 | } |
| 84 | if dv_end - dv_start == 0 { |
| 85 | return error('second argument of `\$d` must be a pure literal') |
| 86 | } |
| 87 | for ; blevel > 0 && i < str.len; i++ { |
| 88 | if str[i] == `)` { |
| 89 | i++ |
| 90 | break |
| 91 | } |
| 92 | } |
| 93 | d_default_value := str#[dv_start..dv_end - 1].trim_space() // last character is the closing `)` |
| 94 | // at this point we have the identifier and the default value. |
| 95 | // now we need to resolve which one to use from `compile_values`. |
| 96 | d_value := compile_values[d_ident] or { d_default_value } |
| 97 | original_expr_to_be_replaced := str#[start..i] |
| 98 | if original_expr_to_be_replaced[original_expr_to_be_replaced.len - 1] != `)` { |
| 99 | panic('the last character of `${original_expr_to_be_replaced}` should be `)`') |
| 100 | } |
| 101 | rep := str.replace_once(original_expr_to_be_replaced, d_value) |
| 102 | if original_expr_to_be_replaced.len > 0 && rep.contains(d_sig) { |
| 103 | // if more `$d()` calls remains, resolve those as well: |
| 104 | return resolve_d_value(compile_values, rep) |
| 105 | } |
| 106 | return rep |
| 107 | } |
| 108 | |