diff --git a/evaluate.c b/evaluate.c
index b96696d..2a98a9b 100644
--- a/evaluate.c
+++ b/evaluate.c
@@ -2243,23 +2243,180 @@ static struct symbol *evaluate_alignof(struct expression *expr)
 	return size_t_ctype;
 }
 
+struct printf_state {
+	int	len;	/* size of the argument (ie, %d, %ld, %lld, etc.) */
+};
+
+static struct symbol *evaluate_printf_symbol(const char *string, struct printf_state *state)
+{
+	struct symbol *sym = NULL;
+
+	switch (string[0]) {
+	case 'C':
+		/* TODO - same as lc */
+		break;
+	case 'c':
+		/* TODO - can take l modifier */
+		sym = &char_ctype;
+		break;
+	case 'f':
+	case 'g':
+		sym = &double_ctype;
+		break;
+	case 'h':
+		/* TODO hh */
+		state->len = -1;
+		break;
+	case 'j':	/* ignore intmax/uintmax for the moment */
+		break;
+	case 'L':
+		sym = &ldouble_ctype;
+		break;
+	case 'l':
+		state->len++;
+		break;
+	case 'p':
+		/* TODO - deal with void * not being de-referenced in some cases*/
+		sym = &ptr_ctype_noderef;
+		break;
+	case 'q':
+		state->len = 2;
+		break;
+	case 's':
+		sym = &string_ctype;
+		break;
+	case 'n':
+		/* TODO - actually pointer to integer */
+		sym = &ptr_ctype;
+		break;
+		/* note, d is out of alpha order */
+	case 'd':
+		switch (state->len) {
+		case -1: sym = &short_ctype; break;
+		case 0: sym = &int_ctype; break;
+		case 1: sym = &long_ctype; break;
+		case 2: sym = &llong_ctype; break;
+		case 3: sym = &lllong_ctype; break;
+		}
+		break;
+	case 'u':
+	case 'x':
+	case 'X':
+		switch (state->len) {
+		case -1: sym = &ushort_ctype; break;
+		case 0: sym = &uint_ctype; break;
+		case 1: sym = &ulong_ctype; break;
+		case 2: sym = &ullong_ctype; break;
+		case 3: sym = &ulllong_ctype; break;
+		}
+		break;
+	case 'z':
+	case 'Z':
+		sym = &uint_ctype;	/* TODO */
+		break;
+	}
+
+	return sym;
+}
+
+static int decompose_format_printf(const char *string, struct symbol_list **result)
+{
+	struct printf_state state;
+	int count = 0;
+
+	/* TODO - deal with explitic position arguments */
+	for (; string[0] != '\0'; string++) {
+		struct symbol *sym;
+
+		if (string[0] != '%')
+			continue;
+		if (string[1] == '%') {
+			string++;
+			continue;
+		}
+
+		state.len = 0;
+
+		/* get rid of any formatting width bits */
+		while (isdigit(string[1]) || string[1] == '+' || string[1] == '-')
+			string++;
+
+		sym = evaluate_printf_symbol(string+1, &state);
+		if (sym) {
+			add_symbol(result, sym);
+			count++;
+		}
+
+		while (string[0] != ' ' && string[0] != '\0')
+			string++;
+
+		string--;
+	}
+
+	return count;
+}
+
+
+static int evaluate_format_printf(struct symbol *fn, struct expression *expr, struct symbol_list **result)
+{
+	const char *fmt_string = NULL;
+	int count = -1;
+
+	if (!expr)
+		return -1;
+	if (expr->string && expr->string->length)
+		fmt_string = expr->string->data;
+	if (!fmt_string) {
+		struct symbol *sym = evaluate_expression(expr);
+
+		/* attempt to find initialiser for this */
+		if (sym && sym->initializer && sym->initializer->string)
+			fmt_string = sym->initializer->string->data;
+	}
+
+	if (fmt_string)
+		count = decompose_format_printf(fmt_string, result);
+	return count;
+}
+
 static int evaluate_arguments(struct symbol *fn, struct expression_list *head)
 {
 	struct expression *expr;
 	struct symbol_list *argument_types = fn->arguments;
+	struct symbol_list *variadic_types = NULL;
 	struct symbol *argtype;
 	int i = 1;
 
+	/* if we have variadic type info, copy the original arguments
+	 * first so that the format parsing can modify this local set */
+
 	PREPARE_PTR_LIST(argument_types, argtype);
 	FOR_EACH_PTR (head, expr) {
 		struct expression **p = THIS_ADDRESS(expr);
-		struct symbol *ctype, *target;
+		struct symbol *ctype, *target = NULL;
 		ctype = evaluate_expression(expr);
 
 		if (!ctype)
 			return 0;
 
-		target = argtype;
+		if (i == fn->ctype.printf_msg) {
+			int ret = evaluate_format_printf(fn, *p, &variadic_types);
+			if (ret < 0)
+				warning((*p)->pos, "cannot parse format");
+		}
+
+		if (i >= fn->ctype.printf_va_start) {
+			struct symbol *sym;
+			int arg = i - fn->ctype.printf_va_start;
+
+			FOR_EACH_PTR(variadic_types, sym) {
+				if (arg == 0)
+					target = sym;
+				arg--;
+			} END_FOR_EACH_PTR(sym);
+		} else {
+			target = argtype;
+		}
 		if (!target) {
 			struct symbol *type;
 			int class = classify_type(ctype, &type);
@@ -2281,11 +2438,11 @@ static int evaluate_arguments(struct symbol *fn, struct expression_list *head)
 			sprintf(where, "argument %d", i);
 			compatible_argument_type(expr, target, p, where);
 		}
-
-		i++;
 		NEXT_PTR_LIST(argtype);
+		i++;
 	} END_FOR_EACH_PTR(expr);
 	FINISH_PTR_LIST(argtype);
+
 	return 1;
 }
 
diff --git a/parse.c b/parse.c
index 9b0d40e..6b0a20b 100644
--- a/parse.c
+++ b/parse.c
@@ -1078,7 +1078,7 @@ static struct token *attribute_format(struct token *token, struct symbol *attr,
 				fmt_sym = lookup_keyword(token->ident, NS_KEYWORD);
 
 			if (!fmt_sym || !fmt_sym->op ||
-			    fmt_sym->op != &attr_printf_op) {
+			    fmt_sym->op->type != KW_FORMAT) {
 				sparse_error(token->pos,
 					     "unknown format type '%s'\n",
 					     show_ident(token->ident));
