use cz/vars use cz/z-macrospace use cz/builtins local long private_count cstr private_prefix cstr private_prefix_up # subroutines / nested functions would be nice for this # TODO do this private_prefix stuff as a builtin, so can use a single buffer for it #def debug_macros warn def debug_macros void cstr new_private_prefix() static buffer struct__b static buffer *b = NULL if !b # 32 chars is more than long enough that it won't be realloc'd b = &struct__b init(b, buffer, 32) buffer_cat_cstr(b, "my__") buffer_set_size(b, 4) buffer_cat_long(b, private_count++) buffer_cat_char(b, '_') return buffer_add_nul(b) cz_expand_macros() New(global, macrospace, builtins) load_macros(global) cz_expand_macros__private_count_init(global) expand_macros(global) cz_expand_macros__private_count_init(macrospace *global): private_count = 0 private_prefix = new_private_prefix() Decl(private_prefix_key, macro_key) private_prefix_key->name = "my__prefix" private_prefix_key->n_args = -1 New(private_def_lines, vec, cstr, 1) vec_push(private_def_lines, private_prefix) New(private_def, macro_def, private_prefix_key->name, NULL, private_def_lines, 0, 0, 0) macrospace_set(global, private_prefix_key, private_def) private_prefix_up = Malloc(32) *private_prefix_up = '\0' Decl(private_prefix_up_key, macro_key) private_prefix_up_key->name = "my__prefix_up" private_prefix_up_key->n_args = -1 New(private_up_def_lines, vec, cstr, 1) vec_push(private_up_def_lines, private_prefix_up) New(private_up_def, macro_def, private_prefix_up_key->name, NULL, private_up_def_lines, 0, 0, 0) macrospace_set(global, private_prefix_up_key, private_up_def) expand_macros(macrospace *ms) # NOTE cz macro processor does not currently apply macros to ALL parts of the source, only the following: each(vov, structs_unions_classes_templates, enums, typedefs, functions, var_assignments, local_and_global_vars) for_vec(lines, vov, vec*) expand_macros_in_lines_inplace(*lines, ms) new(line1, vec, cstr, 1) vec_set_size(line1, 1) each(vos, struct_union_class_template_protos, struct_union_typedefs, function_protos, var_protos) for_vec(line, vos, cstr) *(cstr*)vec0(line1) = *line expand_macros_in_lines_inplace(line1, ms) let(l, veclen(line1)) if l != 1 # it expanded to zero or more than one line, we need to parse this again :( debug_macros("cz_expand_macros: macro expanded to %d lines in single-line context: %s", l, *(cstr*)line) swap(lines, line1) cz_parse() swap(lines, line1) *line = NULL vec_set_size(line1, 1) else: *line = *(cstr*)vec0(line1) remove_null(vos) vec_free(line1) # TODO reparse() like in perl version? is that needed? load_macros(macrospace *ms) for_vec(d, defines, vec*) cstr name, args, definition vec *params = NULL boolean block = 1, ldef = 0 int raw = 0 cstr line = *(cstr*)vec0(*d) int lines = veclen(*d) if line[0] == '^' continue cstr l0 = tofree(Strdup(line)) delimit(name, l0, ' ') else invalid_macro_def("missing name", line) cstr keyword = l0 if (ldef = *keyword == 'l') ++keyword if cstr_eq(keyword, "def") raw = 0 eif cstr_eq(keyword, "Def") raw = 1 eif cstr_eq(keyword, "DEF") raw = 2 else invalid_macro_def("keyword", line) # this is a bit ugly # how to do proper parsing nicely? char *space = strchr(name, ' ') char *paren = strchr(name, '(') if paren && (!space || paren < space) delimit(args, paren, '(') delimit(definition, args, ')') if *definition == ' ' ++definition block = 0 eif *definition invalid_macro_def("character after )", line) NEW(params, vec, cstr, 32) splitv(params, args, ',') for_vec(arg, params, cstr) while **arg == ' ' ++*arg else invalid_macro_def("missing )", line) eif space delimit(definition, space, ' ') block = 0 if block == 0 && lines != 1 invalid_macro_def("expression / block macro confusion", line) if block vec_shift(*d) else *(cstr*)vec0(*d) = definition New(m, macro_def, name, params, *d, block, ldef, raw) int n_args = params ? veclen(params) : -1 macro_key mk = { name, n_args } debug_macros("new macro: %s %d", mk.name, mk.n_args) macrospace_set(ms, &mk, m) # TODO use let later when we do macros in order... *d = NULL remove_null(defines) # macrospace_dump(ms) invalid_macro_def(cstr desc, cstr line) error("invalid macro definition, %s: %s", desc, line) expand_macros_in_lines_inplace(vec *lines_v, macrospace *ms) New(lines_out, vec, cstr, veclen(lines_v)*2) expand_macros_in_lines(lines_out, lines_v, ms) *lines_v = *lines_out vec_squeeze(lines_v) expand_macros_in_lines(vec *lines_out, vec *lines_v, macrospace *ms) # The working state for expanding macros is a little complex. The # initial input is a vector of lines, and the final output is a vector # of lines. The output of a single macro expansion may be part of a # line, or one or more lines. In either case, this output must go # through the macro processor again. We need to somehow prepend the # output to the input queue. I do want this to run fast, but I'm not # overly concerned about efficiency at this stage, I think it will be # fast enough. So I will simply convert the input vector to a deq, # accumulate output in a new vec, and then prepend that vec to the # existing input deq. For in-line macros, I will need to prepend the # output to the current line. The output can remain in a buffer, so I # can just append the rest of the current line to it. # I would like to be able to detect (deeply) recursive macros too. decl(lines, deq) vec_to_deq(lines, lines_v) while deqlen(lines): deq_shift(lines, l) expand_macros_in_line(lines_out, lines, l, ms) #def begins cstr_begins_with #def word cstr_begins_with_word vec_copy(vec *to, vec *from) vecclr(to) vec_append_vec(to, from) vec_append_vec(vec *to, vec *from) vec_append(to, vec0(from), veclen(from)) expand_macros_in_line(vec *lines_out, deq *lines, cstr l, macrospace *ms) debug_macros("expand_macros_in_line: %s", l) int indent = 0 char *paste = NULL boolean changed = 0 boolean block = 0 char *p = l vec struct__args vec *args = NULL buffer struct__b buffer *b = &struct__b char *tok_prev = NULL while *p char *tok = p macro_def *m = NULL token_t tt = token(&p) if tt == TABS: indent += p - tok eif tt == NAME: expand_macros_in_line__name_token() eif *tok == '^' && tok[1] == '^' && p-tok == 2: # token pasting operator if !tok_prev: error("pasting operator ^^ found at start of line - nothing to paste") if !changed: init(b, buffer, 256) buffer_cat_range(b, l, tok) changed = 1 paste = tok_prev m = (macro_def*)1 # token pasting is somewhat 'like' macro expansion if changed && !m && !paste: buffer_cat_range(b, tok, p) eif changed && !m && paste: debug_macros("prepaste: b = %s len = %d", buffer_nul_terminate(b)) new(b1, buffer, 256) expand_macros_in_line__paste(b1, paste, tok) p = l = tofree(buffer_to_cstr(b1)) debug_macros("pasted: b = %s paste = %s tok = %s p = %s", buffer_nul_terminate(b), paste, tok, p) paste = NULL tok_prev = tok if !block: if changed: vec_push(lines_out, tofree(buffer_to_cstr(b))) # TODO ? else: vec_push(lines_out, l) def expand_macros_in_line__paste(b1, paste, tok): char *tok0 = paste token(&paste) int tok0_len = paste - tok0 buffer_grow(b, -tok0_len) boolean tok0_string = paste[-1] == '"' boolean tok1_string = *tok == '"' if tok1_string && !tok0_string: buffer_cat_char(b1, '"') buffer_cat_range(b1, tok0, paste) if tok0_string: buffer_grow(b1, -1) if tok1_string: ++tok char *tok1 = tok token(&tok) buffer_cat_range(b1, tok1, tok) if tok0_string && !tok1_string: buffer_cat_char(b1, '"') buffer_cat_cstr(b1, tok) def expand_macros_in_line__name_token(): Tmpnul(p, c) debug_macros("name token: %s", tok) if macrospace_lookup_vague(ms, tok) expand_macros_in_line__found_vague() if m if m->block: if changed: if b: l = tofree(buffer_to_cstr(b)) b = NULL tok = l + strlen(l) else: changed = 1 block = 1 boolean at_start = tok == l || tok[-1] == '\t' || (tok - l >= 2 && tok[-1] == ' ' && tok[-2] == '\t') if *p || !at_start: error("cz_expand_macros: cannot expand block macro '%s' in the middle of a line: %s", m->name, l) apply_block_macro(lines, l, tok, m, args, ms, indent) else: if !changed: init(b, buffer, 256) buffer_cat_range(b, l, tok) changed = 1 boolean wrap = !(buffer_last_char(b) == '(' && *p == ')') apply_inline_macro(&l, &p, tok, m, args, ms, wrap) def expand_macros_in_line__found_vague(): debug_macros("found vague!") # look for a macro without args first macro_key mk = { tok, -1 } m = namespace_lookup(&ms->ns, &mk) if m: debug_macros(" found exact, without args!") if args: vec_clear(args) eif c == '(': # get the arguments ++p if !args: args = &struct__args vec_init(args, macro_arg, 20) else: vec_clear(args) char *p1 = get_macro_args(p, args, tok, ms) mk.n_args = veclen(args) m = namespace_lookup(&ms->ns, &mk) if m: debug_macros(" found exact, with %d args!", mk.n_args) p = p1 debug_macros(" p is now: %s", p) else: debug_macros(" putting args back - just replace \\0 chars with , , , )") debug_macros(" p: %s", p) debug_macros(" p1: %s", p1) char *fix for fix = p; fix != p1; ++fix: if !*fix: *fix = ',' p1[-1] = ')' debug_macros(" fixed: %s", p) # unfortunately applying / expanding macros with this data structure is quite tricky apply_inline_macro(cstr *l, char **after, char *before, macro_def *m, vec *args, macrospace *ms, boolean wrap): debug_macros(" apply_inline_macro %s", m->name) debug_macros(" after: %s", *after) cstr repl = *(cstr*)vec0(m->lines) # substitute args; aargh! new(b, buffer, 256) substitute_args_in_range(b, repl, repl+strlen(repl), m, args) new(expanded, vec, cstr, 1) vec_push(expanded, tofree(buffer_to_cstr(b))) expand_macros_in_lines_inplace_with_new_private_prefix(expanded, ms) cstr line = *(cstr*)vec0(expanded) long tok_count = count_tokens(line) wrap = wrap && !m->raw && tok_count > 1 cstr expansion = *(cstr*)vec0(expanded) char last_char = cstr_last_char(expansion) if among(last_char, '*', '&'): wrap = 0 new(b1, buffer, 256) if wrap: buffer_cat_char(b1, '(') buffer_cat_cstr(b1, expansion) if wrap: buffer_cat_char(b1, ')') buffer_cat_cstr(b1, *after) *l = tofree(buffer_to_cstr(b1)) *after = *l debug_macros(" rest: %s", *l) use(before) ; use(args) ; use(ms) long count_tokens(cstr s) long c = 0 while token(&s) != TOK_EOT ++c return c # this is junky but I'm getting desperate to get this all working! expand_macros_in_lines_inplace_with_new_private_prefix(vec *lines, macrospace *ms) char old_prefix[32] strcpy(old_prefix, private_prefix_up) strcpy(private_prefix_up, private_prefix) private_prefix = new_private_prefix() expand_macros_in_lines_inplace(lines, ms) strcpy(private_prefix, private_prefix_up) strcpy(private_prefix_up, old_prefix) apply_block_macro(deq *lines, cstr l, char *before, macro_def *m, vec *args, macrospace *ms, int indent): new(v, vec, cstr, 256) boolean first = 1 debug_macros(" apply_block_macro %s", m->name) debug_macros(" l: %s", l) debug_macros(" before: %s", before) # TODO use l for something? for_vec(xp, m->lines, cstr) cstr x = *xp # TODO args, is indent right? new(b, buffer, 256) int extra_tabs = indent - 1 if first: buffer_cat_range(b, l, before) extra_tabs = -1 first = 0 char *tab = *x == '\t' ? x : strchr(x, '\t') if !tab: error("cz macro line is missing any tab for indent!") if tab != x: substitute_args_in_range(b, x, tab, m, args) x = tab if extra_tabs >= 0: repeat(extra_tabs): # TODO make more efficient ala single grow + memset? buffer_cat_char(b, '\t') eif extra_tabs == -1: ++x else: # extra_tabs < -1 fault("apply_block_macro: attempt to shift left by more than 1 tab") substitute_args_in_range(b, x, x+strlen(x), m, args) cstr i = tofree(buffer_to_cstr(b)) debug_macros("unshift line: [%s]", i) vec_push(v, i) expand_macros_in_lines_inplace_with_new_private_prefix(v, ms) back_vec(xp, v, cstr): deq_unshift(lines, *xp) debug_macros(" done") use(l) ; use(before) # RM char *get_macro_args(char *p, vec *args, char *name, macrospace *ms): char *start_args = p char *tok char *after int paren_depth = 0 repeat: tok = p token_t tt = token(&p) which tt: TOK_EOT error("unbalanced parentheses in possible macro %s(%s", name, start_args) BRACKET if among(*tok, '(', '[', '{'): ++paren_depth else: --paren_depth if paren_depth < 0: *tok = '\0' done1 else . done1 debug_macros("all args for %s: [%s]", name, start_args) after = tok + 1 # expand macros in args new(v, vec, cstr, 1) vec_push(v, tofree(strdup(start_args))) expand_macros_in_lines_inplace(v, ms) start_args = *(cstr*)vec0(v) *tok = ')' paren_depth = 0 p = start_args char *val = start_args int tok_count = 0 while *p: char *tok = p token_t tt = token(&p) which tt: BRACKET if among(*tok, '(', '[', '{'): ++paren_depth else: --paren_depth DELIMIT if *tok == ',' && paren_depth == 0: *tok = '\0' macro_arg *arg = vec_push(args) arg->val = val arg->tok_count = tok_count val = p tok_count = 0 SPACE . else ++tok_count ++p # TODO set tok_count = 1 if all wrapped in ( ) or { } or [ ], check paren_depth if *val || veclen(args): macro_arg *arg = vec_push(args) arg->val = val arg->tok_count = tok_count debug_macros("args:") for_vec(i, args, macro_arg): use(i) debug_macros(" [%s] %d", i->val, i->tok_count) debug_macros("get_macro_args: after now pointing to: %s", after) return after int substitute_args_in_range(buffer *b, char *p0, char *p1, macro_def *m, vec *args): int tot_tok_count = 0 boolean wrapped = among(*p0, '(', '[', '{') int paren_depth = 0 char *p = p0 int i while p != p1: char *tok = p debug_macros("tokens remaining: %s", tok) token_t tt = token(&p) int tlen = p - tok if tt == NAME && args && veclen(args): i = 0 for_vec(param, m->params, cstr): int plen = strlen(*param) if tlen == plen && strncmp(*param, tok, plen) == 0: macro_arg *argp = (macro_arg *)v(args, i) cstr arg = argp->val int tok_count = argp->tok_count boolean wrap = !(buffer_last_char(b) == '(' && *p == ')') wrap = wrap && tok_count > 1 && !m->raw char last_char = cstr_last_char(arg) if among(last_char, '*', '&'): wrap = 0 boolean strip_spaces = m->raw != 2 if wrap: buffer_cat_char(b, '(') tot_tok_count += 2 if strip_spaces: while *arg == ' ': ++arg buffer_cat_cstr(b, arg) tot_tok_count += tok_count if wrap: buffer_cat_char(b, ')') subd ++i if tt == BRACKET: if among(*tok, '(', '[', '{'): ++paren_depth else: --paren_depth if p != p1: wrapped = 0 buffer_cat_range(b, tok, p) if tt != SPACE: ++tot_tok_count subd . if paren_depth: wrapped = 0 if wrapped: tot_tok_count = 1 return tot_tok_count