
global curr_function_stack_space

; is this token the start of a type?
function token_is_type
	argument token
	local c
	c = *1token
	if c == TOKEN_IDENTIFIER goto token_is_ident_type
	if c == KEYWORD_UNSIGNED goto return_1
	if c == KEYWORD_CHAR goto return_1
	if c == KEYWORD_SHORT goto return_1
	if c == KEYWORD_INT goto return_1
	if c == KEYWORD_LONG goto return_1
	if c == KEYWORD_FLOAT goto return_1
	if c == KEYWORD_DOUBLE goto return_1
	if c == KEYWORD_VOID goto return_1
	if c == KEYWORD_STRUCT goto return_1
	if c == KEYWORD_UNION goto return_1
	if c == KEYWORD_ENUM goto return_1
	goto return_0
	:token_is_ident_type
		token += 8
		c = *8token
		local b
		b = ident_list_lookup(typedefs, c)
		if b != 0 goto return_1
		goto return_0
	

function type_is_array
	argument type
	local p
	p = types + type
	if *1p == TYPE_ARRAY goto return_1
	return 0

function type_is_function
	argument type
	local p
	p = types + type
	if *1p == TYPE_FUNCTION goto return_1
	return 0

function type_is_floating
	argument type
	local p
	p = types + type
	if *1p == TYPE_FLOAT goto return_1
	if *1p == TYPE_DOUBLE goto return_1
	return 0

function type_is_incomplete_struct
	argument type
	local p
	p = types + type
	if *1p != TYPE_STRUCT goto return_0
	p += 1
	p = *8p
	if p == 0 goto return_1
	if *8p == 0 goto return_1
	return 0

function functype_return_type
	argument ftype
	local type
	local p
	
	type = ftype + 1
	:ftype_rettype_loop
		p = types + type
		if *1p == 0 goto ftype_rettype_loop_end
		type += type_length(type)
		goto ftype_rettype_loop
	:ftype_rettype_loop_end
	return type + 1

; NB: this takes a pointer to struct data, NOT a type
; Returns 1 if it's a union OR a struct with 1 member (we don't distinguish between these in any way)
function structure_is_union
	argument struct
	local offset
	; calculate offset of 2nd member, or 0 if there is only one member
	offset = ident_list_value_at_index(struct, 1)
	offset &= 0xffffffff
	if offset == 0 goto return_1 ; if that's 0, it's a union or 1-element struct
	goto return_0
	
; parse a translation unit
function parse_tokens
	argument tokens
	local token
	
	token = tokens
	:parse_tokens_loop
		if *1token == TOKEN_EOF goto parse_tokens_eof
		parse_toplevel_declaration(&token, global_variables)
		goto parse_tokens_loop
	:parse_tokens_eof
	return


; also handles static declarations inside functions
; advances *p_token past semicolon
; static_vars = where to put static variables
function parse_toplevel_declaration
	argument p_token
	argument static_vars
	
	local token
	local ident
	local type
	local list
	local p
	local b
	local c
	local n
	local base_type
	local base_type_end
	local name
	local prefix
	local prefix_end
	local suffix
	local suffix_end
	local is_extern
	local out
	local out0
	
	token = *8p_token
	is_extern = 0

	if *1token == KEYWORD_STATIC goto parse_static_toplevel_decl
	if *1token == KEYWORD_EXTERN goto parse_extern_toplevel_decl
	if *1token == KEYWORD_TYPEDEF goto parse_typedef
	
	b = token_is_type(token)
	if b != 0 goto parse_toplevel_decl
	
	token_error(token, .str_bad_decl)
	:str_bad_decl
		string Bad declaration.
		byte 0
	:parse_tld_ret
		*8p_token = token
		return
	:parse_static_toplevel_decl
		token += 16 ; we don't care that this is static
		goto parse_toplevel_decl
	:parse_extern_toplevel_decl
		token += 16
		is_extern = 1
		goto parse_toplevel_decl
	:parse_toplevel_decl
		base_type = token
		base_type_end = type_get_base_end(token)
		token = base_type_end
		:tl_decl_loop
			prefix = token
			prefix_end = type_get_prefix_end(prefix)
			if *1prefix_end != TOKEN_IDENTIFIER goto tl_decl_no_ident
			name = prefix_end + 8
			name = *8name
			suffix = prefix_end + 16
			suffix_end = type_get_suffix_end(prefix)
			type = types_bytes_used
			*1function_param_names = 0
			parse_type_declarators(prefix, prefix_end, suffix, suffix_end, function_param_names)
			parse_base_type(base_type, base_type_end)
			
			b = type_is_incomplete_struct(type)
			if b != 0 goto tld_incomplete_struct
			
			; ensure rwdata_end_addr is aligned to 8 bytes
			; otherwise addresses could be screwed up
			rwdata_end_addr += 7
			rwdata_end_addr >= 3
			rwdata_end_addr <= 3
			
			token = suffix_end
			p = types + type
			if *1p == TYPE_FUNCTION goto parse_function_declaration
			
			; ignore external variable declarations
			;  @NONSTANDARD: this means we don't handle
			;     extern int X;
			;     int main() { printf("%d\n", X); }
			;     int X;
			; correctly. There is no (good) way for us to handle this properly without two passes.
			; Consider:
			;      extern int X[];  /* how many bytes to allocate? */
			;      int Y = 123;           /* where do we put this? */
			;      int main() { printf("%d\n", Y); }
			;      int X[] = {1, 2, 3, 4}; /* 16 bytes (but it's too late) */
			if is_extern != 0 goto parse_tl_decl_cont
			
			; deal with the initializer if there is one
			if *1token == SYMBOL_SEMICOLON goto parse_tld_no_initializer
			if *1token == SYMBOL_COMMA goto parse_tld_no_initializer
			if *1token == SYMBOL_EQ goto parse_tld_initializer
			token_error(token, .str_unrecognized_stuff_after_declaration)
			:str_unrecognized_stuff_after_declaration
				string Declaration should be followed by one of: { , =
				byte 32
				byte 59 ; semicolon
				byte 0	
			:parse_tl_decl_cont
			
			
			if *1token == SYMBOL_SEMICOLON goto tl_decl_loop_done
			if *1token != SYMBOL_COMMA goto tld_bad_stuff_after_decl
			token += 16
			goto tl_decl_loop
		:tl_decl_loop_done
		token += 16 ; skip semicolon
		goto parse_tld_ret
		:tld_incomplete_struct
			token_error(token, .str_tld_incomplete_struct)
		:str_tld_incomplete_struct
			string Declaration has incomplete struct as its type.
			byte 0
		:tl_decl_no_ident
			; this might actually be okay, e.g.
			; struct Something { int x, y; }
			if *1base_type == KEYWORD_STRUCT goto tldni_basetype_ok
			if *1base_type == KEYWORD_UNION goto tldni_basetype_ok
			if *1base_type == KEYWORD_ENUM goto tldni_basetype_ok
			goto tldni_bad
			:tldni_basetype_ok
			if prefix != prefix_end goto tldni_bad ; e.g. struct Something {...} *;
			if *1prefix_end != SYMBOL_SEMICOLON goto tldni_bad ; you can't do struct Something { ...}, struct SomethingElse {...};
			parse_base_type(base_type) ; this will properly define the struct/union/enum and any enumerators
			token = prefix_end
			goto tl_decl_loop_done
			:tldni_bad
				token_error(prefix_end, .str_tl_decl_no_ident)
		:str_tl_decl_no_ident
			string No identifier in top-level declaration.
			byte 0
		:tld_bad_stuff_after_decl
			;b = token - 160
			;c = token + 160
			;print_tokens(b, token)
			;print_tokens(token, c)
			token_error(token, .str_tld_bad_stuff_after_decl)
		:str_tld_bad_stuff_after_decl
			string Declarations should be immediately followed by a comma or semicolon.
			byte 0
	:parse_function_declaration
		b = ident_list_lookup(function_types, name)
		if b != 0 goto function_decl_have_type ; e.g. function declared then defined
		ident_list_add(function_types, name, type)
		:function_decl_have_type
		if *1token == SYMBOL_LBRACE goto parse_function_definition
		if *1token == SYMBOL_SEMICOLON goto parse_tl_decl_cont
		token_error(token, .str_bad_fdecl_suffix)
		:str_bad_fdecl_suffix
			string Expected semicolon or { after function declaration.
			byte 0
	:parse_tld_no_initializer
		b = ident_list_lookup(static_vars, name)
		if b != 0 goto global_redefinition
		c = type < 32
		c |= rwdata_end_addr
		ident_list_add(static_vars, name, c)
		; just skip forward by the size of this variable -- it'll automatically be filled with 0s.
		rwdata_end_addr += type_sizeof(type)
		goto parse_tl_decl_cont
	:parse_tld_initializer
		p = types + type
		if *1p == TYPE_FUNCTION goto function_initializer
		b = ident_list_lookup(static_vars, name)
		if b != 0 goto global_redefinition
		token += 16 ; skip =
		c = type < 32
		c |= rwdata_end_addr
		ident_list_add(static_vars, name, c)
		parse_constant_initializer(&token, type)
		goto parse_tl_decl_cont
	:global_redefinition
		token_error(token, .str_global_redefinition)
	:str_global_redefinition
		string Redefinition of global variable.
		byte 0
	:function_initializer
		token_error(token, .str_function_initializer)
	:str_function_initializer
		string Functions should not have initializers.
		byte 0
	:parse_function_definition
		local ret_type
		local param_offset
		local f_name
		
		f_name = name
		
		if block_depth != 0 goto nested_function
		if function_param_has_no_name != 0 goto function_no_param_name
		p = types + type
		if *1p != TYPE_FUNCTION goto lbrace_after_declaration
		
		c = ident_list_lookup(function_statements, name)
		if c != 0 goto function_redefinition
		
		ret_type = functype_return_type(type)
		
		global function_stmt_data ; initialized in main
		global function_stmt_data_bytes_used
						
		out = function_stmt_data + function_stmt_data_bytes_used
		out0 = out
		ident_list_add(function_statements, name, out)
		
		; deal with function parameters
		; function parameters go above return value on the stack
		n = type_sizeof(ret_type)
		n += 7
		n >= 3
		n <= 3
		param_offset = n + 16  ; + 16 for old rbp and return address
		
		p = type + 1
		name = function_param_names
		list = local_variables
		list += 8
		list = *8list
		:fn_params_loop
			if *1name == 0 goto fn_params_loop_end
			c = p < 32
			c |= param_offset
			ident_list_add(list, name, c)
			param_offset += type_sizeof(p)
			param_offset += 7
			param_offset >= 3
			param_offset <= 3
			p += type_length(p)
			name = memchr(name, 0)
			name += 1
			goto fn_params_loop
		:fn_params_loop_end
		
		local_var_rbp_offset = 0
		
		; NOTE: it's the caller's responsibility to properly set rsp to accomodate all the arguments.
		;       it needs to be this way because of varargs functions (the function doesn't know how many arguments there are).
		parse_statement(&token, &out)
		if block_depth != 0 goto blockdepth_internal_err
		function_stmt_data_bytes_used = out - function_stmt_data
		
		ident_list_add(functions_required_stack_space, f_name, curr_function_stack_space)
		
		debug_puts(.str_parsed)
		debug_putsln(f_name)
		
		;PARSING DEBUG OUTPUT:
		;if G_DEBUG == 0 goto skip_print_statement
		;print_statement(out0)
		;:skip_print_statement
		
		goto parse_tld_ret
		
		:str_parsed
			string Parsed
			byte 32
			byte 0
		:function_no_param_name
			token_error(base_type, .str_function_no_param_name)
		:str_function_no_param_name
			string Function definition with unnamed parameters.
			byte 0
		:blockdepth_internal_err
			token_error(token, .str_blockdepth_internal_err)
		:str_blockdepth_internal_err
			string Internal compiler error: block_depth is not 0 after parsing function body.
			byte 0
		:lbrace_after_declaration
			token_error(token, .str_lbrace_after_declaration)
		:str_lbrace_after_declaration
			string Opening { after declaration of non-function.
			byte 0
		:nested_function
			token_error(token, .str_nested_function)
		:str_nested_function
			string Nested function.
			byte 0
		:function_redefinition
			token_error(token, .str_function_redefinition)
		:str_function_redefinition
			string Redefinition of function.
			byte 0
	:parse_typedef
		if block_depth > 0 goto local_typedef
		base_type = token + 16
		base_type_end = type_get_base_end(base_type)
		
		token = base_type_end
		
		:typedef_loop
			prefix = token
			prefix_end = type_get_prefix_end(prefix)
			if *1prefix_end != TOKEN_IDENTIFIER goto typedef_no_ident
			ident = prefix_end + 8
			ident = *8ident
			suffix = prefix_end + 16
			suffix_end = type_get_suffix_end(prefix)
			
			;putc('B)
			;putc(':)
			;print_tokens(base_type, base_type_end)
			;putc('P)
			;putc(':)
			;print_tokens(prefix, prefix_end)
			;putc('S)
			;putc(':)
			;print_tokens(suffix, suffix_end)
			
			type = types_bytes_used
			parse_type_declarators(prefix, prefix_end, suffix, suffix_end, 0)
			parse_base_type(base_type)
			
			;puts(.str_typedef)
			;putc(32)
			;print_type(type)
			;putc(10)
			
			b = ident_list_lookup(typedefs, ident)
			if b != 0 goto typedef_redefinition
			
			ident_list_add(typedefs, ident, type)
			token = suffix_end
			if *1token == SYMBOL_SEMICOLON goto typedef_loop_end
			if *1token != SYMBOL_COMMA goto bad_typedef
			token += 16 ; skip comma
			goto typedef_loop
		:typedef_loop_end
		token += 16 ; skip semicolon
		goto parse_tld_ret
	:local_typedef
		; @NONSTANDARD
		; we could add an extra "typedefs" argument to this function to fix this.
		token_error(token, .str_local_typedef)
	:str_local_typedef
		string typedefs inside functions are not supported.
		byte 0
	:typedef_no_ident
		token_error(token, .str_typedef_no_ident)
	:str_typedef_no_ident
		string No identifier in typedef declaration.
		byte 0
	:bad_typedef
		token_error(token, .str_bad_typedef)
	:str_bad_typedef
		string Bad typedef.
		byte 0
	:typedef_redefinition
		token_error(token, .str_typedef_redefinition)
	:str_typedef_redefinition
		string typedef redefinition.
		byte 0

; write type, file, and line info for statement
function write_statement_header
	local out
	local type
	local token
	*1out = type
	out += 2
	token += 2
	*2out = *2token
	out += 2
	token += 2
	*4out = *4token
	return 0


; writes statement data for the statement at *p_token to (*)*p_out
; always advances *p_out by exactly 40 bytes, since that's the length of a statement.
function parse_statement
	argument p_token
	argument p_out
	local out
	local token
	local p
	local c
	local n
	local b
	
	out = *8p_out
	token = *8p_token
	
	; needed so that:
	;   if (something)
	;    lbl: f();
	; works
	:parse_another_statement
	
	c = *1token
	if c == SYMBOL_SEMICOLON goto stmt_empty
	if c == SYMBOL_LBRACE goto stmt_block
	if c == KEYWORD_BREAK goto stmt_break
	if c == KEYWORD_CONTINUE goto stmt_continue
	if c == KEYWORD_RETURN goto stmt_return
	if c == KEYWORD_GOTO goto stmt_goto
	if c == KEYWORD_CASE goto stmt_case
	if c == KEYWORD_DEFAULT goto stmt_default
	if c == KEYWORD_STATIC goto stmt_static_declaration
	if c == KEYWORD_EXTERN goto stmt_extern_declaration
	if c == KEYWORD_WHILE goto stmt_while
	if c == KEYWORD_DO goto stmt_do
	if c == KEYWORD_FOR goto stmt_for
	if c == KEYWORD_SWITCH goto stmt_switch
	if c == KEYWORD_IF goto stmt_if
	
	if *1token != TOKEN_IDENTIFIER goto stmt_not_label
	; if second token in statement is a colon, this must be a label
	p = token + 16
	if *1p == SYMBOL_COLON goto stmt_label
	:stmt_not_label
	
	b = token_is_type(token)
	if b != 0 goto stmt_local_declaration
	
	; it's an expression statement
	write_statement_header(out, STATEMENT_EXPRESSION, token)
	out += 8
	p = token_next_semicolon_not_in_brackets(token)
	*8out = expressions_end
	b = expressions_end + 4 ; type of expression
	expressions_end = parse_expression(token, p, expressions_end)
	type_decay_array_to_pointer_in_place(*4b)
	out += 32
	token = p + 16
	goto parse_statement_ret
	
	:parse_statement_ret
		*8p_token = token
		*8p_out = out
		return
	:stmt_extern_declaration
		token_error(token, .str_stmt_extern_declaration)
	:str_stmt_extern_declaration
		; @NONSTANDARD
		string Local extern declarations are not supported.
		byte 0
	:stmt_label
		write_statement_header(out, STATEMENT_LABEL, token)
		out += 8
		token += 8
		*8out = *8token ; copy label name
		out += 32
		token += 24 ; skip ident name, and colon
		goto parse_another_statement
	:stmt_switch
		write_statement_header(out, STATEMENT_SWITCH, token)		
		token += 16
		if *1token != SYMBOL_LPAREN goto switch_no_lparen
		p = token_matching_rparen(token)
		token += 16
		out += 8
		*8out = expressions_end
		c = expressions_end + 4
		expressions_end = parse_expression(token, p, expressions_end)
		c = types + *4c
		if *1c > TYPE_UNSIGNED_LONG goto bad_switch_type
		
		token = p + 16
		out += 8
		
		; put the body statement 1 block_depth deeper
		p = statement_datas_ends
		p += block_depth < 3
		block_depth += 1
		if block_depth >= BLOCK_DEPTH_LIMIT goto too_much_nesting
		*8out = *8p
		out += 24
		parse_statement(&token, p) ; the body
		block_depth -= 1
		
		goto parse_statement_ret
		:switch_no_lparen
			token_error(token, .str_switch_no_lparen)
		:str_switch_no_lparen
			string No ( after switch.
			byte 0
		:bad_switch_type
			token_error(token, .str_bad_switch_type)
		:str_bad_switch_type
			string The expression in a switch statement must have an integer type.
			byte 0
	:stmt_while
		write_statement_header(out, STATEMENT_WHILE, token)		
		token += 16
		if *1token != SYMBOL_LPAREN goto while_no_lparen
		p = token_matching_rparen(token)
		token += 16
		out += 8
		*8out = expressions_end
		expressions_end = parse_expression(token, p, expressions_end)
		token = p + 16
		out += 8
		
		; put the body statement 1 block_depth deeper
		p = statement_datas_ends
		p += block_depth < 3
		block_depth += 1
		if block_depth >= BLOCK_DEPTH_LIMIT goto too_much_nesting
		*8out = *8p
		out += 24
		parse_statement(&token, p) ; the body
		block_depth -= 1
		
		goto parse_statement_ret
		:while_no_lparen
			token_error(token, .str_while_no_lparen)
		:str_while_no_lparen
			string No ( after while.
			byte 0
	:stmt_do
		write_statement_header(out, STATEMENT_DO, token)
		out += 8
		token += 16
		
		; put the body statement 1 block_depth deeper
		p = statement_datas_ends
		p += block_depth < 3
		block_depth += 1
		if block_depth >= BLOCK_DEPTH_LIMIT goto too_much_nesting
		*8out = *8p
		out += 8
		parse_statement(&token, p) ; the body
		block_depth -= 1
		
		if *1token != KEYWORD_WHILE goto do_no_while
		token += 16
		if *1token != SYMBOL_LPAREN goto do_no_lparen
		p = token_matching_rparen(token)
		token += 16
		*8out = expressions_end
		expressions_end = parse_expression(token, p, expressions_end)
		token = p + 16
		if *1token != SYMBOL_SEMICOLON goto do_no_semicolon
		token += 16
		
		out += 24
		goto parse_statement_ret
		
		:do_no_while
			token_error(token, .str_do_no_while)
		:str_do_no_while
			string No while after do body.
			byte 0
		:do_no_lparen
			token_error(token, .str_do_no_lparen)
		:str_do_no_lparen
			string No ( after do ... while
			byte 0
		:do_no_semicolon	
			token_error(token, .str_do_no_semicolon)
		:str_do_no_semicolon
			string No semicolon after do ... while (...)
			byte 0
	:stmt_for
		write_statement_header(out, STATEMENT_FOR, token)
		out += 8
		token += 16
		if *1token != SYMBOL_LPAREN goto for_no_lparen
		c = token_matching_rparen(token)
		token += 16
		p = token_next_semicolon_not_in_brackets(token)
		if token == p goto for_no_expr1
		*8out = expressions_end
		expressions_end = parse_expression(token, p, expressions_end)
		:for_no_expr1
		out += 8
		token = p + 16
		p = token_next_semicolon_not_in_brackets(token)
		if token == p goto for_no_expr2
		*8out = expressions_end
		expressions_end = parse_expression(token, p, expressions_end)
		:for_no_expr2
		out += 8
		token = p + 16
		if c < token goto bad_for
		if c == token goto for_no_expr3
		*8out = expressions_end
		expressions_end = parse_expression(token, c, expressions_end)
		:for_no_expr3
		out += 8
		token = c + 16
		
		; put the body statement 1 block_depth deeper
		p = statement_datas_ends
		p += block_depth < 3
		block_depth += 1
		if block_depth >= BLOCK_DEPTH_LIMIT goto too_much_nesting
		*8out = *8p
		out += 8
		parse_statement(&token, p) ; the body
		block_depth -= 1
		
		goto parse_statement_ret
		
		:bad_for
			token_error(c, .str_bad_for)
		:str_bad_for
			string Bad for loop header.
			byte 0
		:for_no_lparen
			token_error(token, .str_for_no_lparen)
		:str_for_no_lparen
			string Missing ( after for.
			byte 0
	:stmt_if
		write_statement_header(out, STATEMENT_IF, token)
		out += 8
		token += 16
		if *1token != SYMBOL_LPAREN goto if_no_lparen
		p = token_matching_rparen(token)
		token += 16
		*8out = expressions_end
		out += 8
		expressions_end = parse_expression(token, p, expressions_end)
		token = p + 16
		
		
		; put the body statement(s) 1 block_depth deeper
		p = statement_datas_ends
		p += block_depth < 3
		block_depth += 1
		if block_depth >= BLOCK_DEPTH_LIMIT goto too_much_nesting
		*8out = *8p
		out += 8
		parse_statement(&token, p) ; if body
		if *1token != KEYWORD_ELSE goto stmt_if_no_else
		token += 16
		*8out = *8p
		parse_statement(&token, p) ; else body
		:stmt_if_no_else
		out += 16
		block_depth -= 1
		goto parse_statement_ret
		:if_no_lparen
			token_error(token, .str_if_no_lparen)
		:str_if_no_lparen
			string No ( after if
			byte 0
	:stmt_local_declaration
		local l_base_type
		local l_prefix
		local l_prefix_end
		local l_suffix
		local l_suffix_end
		local l_type
		local l_offset
		local l_name
		
		l_base_type = token
		token = type_get_base_end(l_base_type)
		:local_decl_loop
			l_prefix = token
			l_prefix_end = type_get_prefix_end(l_prefix)
			if *1l_prefix_end != TOKEN_IDENTIFIER goto local_decl_no_ident
			l_name = l_prefix_end + 8
			l_name = *8l_name
			l_suffix = l_prefix_end + 16
			l_suffix_end = type_get_suffix_end(l_prefix)
			l_type = types_bytes_used
			parse_type_declarators(l_prefix, l_prefix_end, l_suffix, l_suffix_end, 0)
			parse_base_type(l_base_type)
			
			b = type_is_incomplete_struct(l_type)
			if b != 0 goto local_decl_incomplete_struct
			
			; create pseudo-entry for variable in local variables list.
			;   this allows for  int *x = malloc(sizeof *x);
			; unfortunately, it also allows  int x = x;
			; oh well
			p = local_variables
			p += block_depth < 3
			c = ident_list_lookup(*8p, l_name)
			if c != 0 goto local_redeclaration
			c = l_type < 32
			ident_list_add(*8p, l_name, c)
			
			
			token = l_suffix_end
			if *1token == SYMBOL_EQ goto local_decl_initializer
			:local_decl_continue
			; we need to calculate the size of the type here, because of stuff like
			;    int x[] = {1,2,3};
			n = type_sizeof(l_type)
			
			; advance
			local_var_rbp_offset += n
			; align
			local_var_rbp_offset = round_up_to_8(local_var_rbp_offset)
			
			write_statement_header(out, STATEMENT_LOCAL_DECLARATION, token)
			out += 8
			*8out = local_var_rbp_offset
			out += 8
			*8out = l_type
			out += 24
			
			curr_function_stack_space = local_var_rbp_offset
			
			p = local_variables
			p += block_depth < 3
			; local variables are stored below rbp
			l_offset = 0 - local_var_rbp_offset
			c = l_offset & 0xffffffff
			c |= l_type < 32
			ident_list_set(*8p, l_name, c)
			
			if *1token == SYMBOL_SEMICOLON goto local_decl_loop_end
			if *1token != SYMBOL_COMMA goto local_decl_badsuffix
			
			token += 16 ; skip comma
			goto local_decl_loop
			
			:local_decl_initializer
				token += 16
				if *1token == SYMBOL_LBRACE goto local_const_init
				if *1token == TOKEN_STRING_LITERAL goto maybe_string_init
				:not_string_init
				n = token_next_semicolon_comma_rbracket(token)
				out += 24
				p = expressions_end
				*8out = p
				out -= 24
				expressions_end = parse_expression(token, n, p)
				p += 4
				type_decay_array_to_pointer_in_place(*4p) ; fix typing for `int[] x = {5,6}; int *y = x;`
				token = n
				goto local_decl_continue
				:maybe_string_init
					; check if we have  char x[] = "hello"; or char *x = "hello"; (we'll use parse_constant_initializer for that)
					p = token + 16
					if *1p == SYMBOL_SEMICOLON goto local_const_init
					if *1p == SYMBOL_COMMA goto local_const_init
					goto not_string_init
			:local_const_init
				rwdata_end_addr += 7
				rwdata_end_addr >= 3
				rwdata_end_addr <= 3
				out += 32
				*8out = rwdata_end_addr
				out -= 32
				parse_constant_initializer(&token, l_type)
				goto local_decl_continue
			:local_decl_badsuffix
				token_error(token, .str_local_decl_badsuffix)
			:str_local_decl_badsuffix
				string Expected equals, comma, or semicolon after variable declaration.
				byte 0
			:local_redeclaration
				token_error(token, .str_local_redeclaration)
			:str_local_redeclaration
				string Redeclaration of local variable.
				byte 0
			:local_decl_no_ident						
				:local_decl_no_ident_bad
					token_error(token, .str_local_decl_no_ident)
				:str_local_decl_no_ident
					string No identifier in declaration.
					byte 0
			:local_decl_incomplete_struct
				token_error(token, .str_local_decl_incomplete_struct)
			:str_local_decl_incomplete_struct
				string Location declaration has incomplete struct as its type.
				byte 0
		:local_decl_loop_end
		token += 16 ; skip semicolon
		goto parse_statement_ret
	:stmt_static_declaration
		p = block_static_variables
		p += block_depth < 3
		parse_toplevel_declaration(&token, *8p)
		goto parse_statement_ret
	:stmt_break
		write_statement_header(out, STATEMENT_BREAK, token)
		token += 16
		if *1token != SYMBOL_SEMICOLON goto break_no_semicolon
		token += 16
		out += 40
		goto parse_statement_ret
		:break_no_semicolon
			token_error(token, .str_break_no_semicolon)
		:str_break_no_semicolon
			string No semicolon after break.
			byte 0
	:stmt_continue
		write_statement_header(out, STATEMENT_CONTINUE, token)
		token += 16
		if *1token != SYMBOL_SEMICOLON goto continue_no_semicolon
		token += 16
		out += 40
		goto parse_statement_ret
		:continue_no_semicolon
			token_error(token, .str_continue_no_semicolon)
		:str_continue_no_semicolon
			string No semicolon after continue.
			byte 0
	:stmt_case
		write_statement_header(out, STATEMENT_CASE, token)
		token += 16
		out += 8
		p = token
		; @NONSTANDARD
		;  technically (horribly), this is legal C:
		;     switch (x) {
		;       case 1 == 7 ? 5 : 6:
		;          ...
		;     }
		; we don't handle it properly, even if the conditional is put in parentheses.
		; at least it will definitely give an error if it encounters something like this.
		:case_find_colon_loop
			if *1p == TOKEN_EOF goto case_no_colon
			if *1p == SYMBOL_COLON goto case_found_colon
			p += 16
			goto case_find_colon_loop
		:case_found_colon
		c = expressions_end
		expressions_end = parse_expression(token, p, expressions_end)
		evaluate_constant_expression(token, c, &n)
		*8out = n
		out += 32
		token = p + 16
		goto parse_another_statement
		:case_no_colon
			token_error(token, .str_case_no_colon)
		:str_case_no_colon
			string No : after case.
			byte 0
	:stmt_default
		write_statement_header(out, STATEMENT_DEFAULT, token)
		token += 16
		out += 40
		if *1token != SYMBOL_COLON goto default_no_colon
		token += 16
		goto parse_another_statement
		:default_no_colon
			token_error(token, .str_default_no_colon)
		:str_default_no_colon
			string No : after default.
			byte 0
	:stmt_return
		write_statement_header(out, STATEMENT_RETURN, token)
		out += 8
		token += 16
		if *1token == SYMBOL_SEMICOLON goto return_no_expr
		n = token_next_semicolon_not_in_brackets(token)
		*8out = expressions_end
		p = expressions_end + 4 ; type of expression
		expressions_end = parse_expression(token, n, expressions_end)
		type_decay_array_to_pointer_in_place(*4p)
		token = n + 16
		:return_no_expr
		out += 32
		goto parse_statement_ret
	:stmt_goto
		write_statement_header(out, STATEMENT_GOTO, token)
		out += 8
		token += 16
		if *1token != TOKEN_IDENTIFIER goto goto_not_ident
		token += 8
		*8out = *8token
		out += 32
		token += 8
		if *1token != SYMBOL_SEMICOLON goto goto_no_semicolon
		token += 16
		goto parse_statement_ret
		:goto_not_ident
			token_error(token, .str_goto_not_ident)
		:str_goto_not_ident
			string goto not immediately followed by identifier.
			byte 0
		:goto_no_semicolon
			token_error(token, .str_goto_no_semicolon)
		:str_goto_no_semicolon
			string No semicolon after goto.
			byte 0
	:stmt_block
		local Z
		Z = out
		write_statement_header(out, STATEMENT_BLOCK, token)
		out += 8
		
		local block_p_out
		; find the appropriate statement data to use for this block's body
		block_p_out = statement_datas_ends
		block_p_out += block_depth < 3
		*8out = *8block_p_out
		out += 32
		
		block_depth += 1
		if block_depth >= BLOCK_DEPTH_LIMIT goto too_much_nesting
		
		token += 16 ; skip opening {
		:parse_block_loop
			if *1token == TOKEN_EOF goto parse_block_eof
			if *1token == SYMBOL_RBRACE goto parse_block_loop_end
			parse_statement(&token, block_p_out)
			goto parse_block_loop
		:parse_block_loop_end
		token += 16 ; skip closing }
		p = *8block_p_out
		*1p = 0  ; probably redundant, but whatever
		*8block_p_out += 8 ; add 8 and not 1 because of alignment
		
		; clear block-related stuff for this depth
		p = block_static_variables
		p += block_depth < 3
		ident_list_clear(*8p)
		p = local_variables
		p += block_depth < 3
		ident_list_clear(*8p)
		
		block_depth -= 1
		
		goto parse_statement_ret
		
		:parse_block_eof
			token_error(*8p_token, .str_parse_block_eof)
		:str_parse_block_eof
			string End of file reached while trying to parse block. Are you missing a closing brace?
			byte 0
		:too_much_nesting
			token_error(token, .str_too_much_nesting)
		:str_too_much_nesting
			string Too many levels of nesting blocks.
			byte 0
	:stmt_empty
		; empty statement, e.g. while(something)-> ; <-
		; we do have to output a statement here because otherwise that kind of thing would be screwed up
		write_statement_header(out, STATEMENT_NOOP, token)
		out += 40
		token += 16 ; skip semicolon
		goto parse_statement_ret

function print_statement
	argument statement
	print_statement_with_depth(statement, 0)
	return


function print_indents
	argument count
	:print_indent_loop
		if count == 0 goto return_0
		putc(9)
		count -= 1
		goto print_indent_loop
	
function print_statement_with_depth
	argument statement
	argument depth
	local c
	local dat1
	local dat2
	local dat3
	local dat4
	
	print_indents(depth)
	
	c = *1statement
	dat1 = statement + 8
	dat1 = *8dat1
	dat2 = statement + 16
	dat2 = *8dat2
	dat3 = statement + 24
	dat3 = *8dat3
	dat4 = statement + 32
	dat4 = *8dat4
	
	if c == STATEMENT_NOOP goto print_stmt_noop
	if c == STATEMENT_EXPRESSION goto print_stmt_expr
	if c == STATEMENT_BLOCK goto print_stmt_block
	if c == STATEMENT_CONTINUE goto print_stmt_continue
	if c == STATEMENT_BREAK goto print_stmt_break
	if c == STATEMENT_RETURN goto print_stmt_return
	if c == STATEMENT_GOTO goto print_stmt_goto
	if c == STATEMENT_LABEL goto print_stmt_label
	if c == STATEMENT_CASE goto print_stmt_case
	if c == STATEMENT_DEFAULT goto print_stmt_default
	if c == STATEMENT_WHILE goto print_stmt_while
	if c == STATEMENT_DO goto print_stmt_do
	if c == STATEMENT_IF goto print_stmt_if
	if c == STATEMENT_SWITCH goto print_stmt_switch
	if c == STATEMENT_FOR goto print_stmt_for
	if c == STATEMENT_LOCAL_DECLARATION goto print_stmt_local_decl
	
	die(.str_bad_print_stmt)
	:str_bad_print_stmt
		string Bad statement passed to print_statement.
		byte 0
	:str_semicolon_newline
		byte 59
		byte 10
		byte 0
	:print_stmt_label
		puts(dat1)
		putcln(':)
		return
	:print_stmt_expr
		print_expression(dat1)
		putcln(59)
		return
	:print_stmt_noop
		putcln(59)
		return
	:print_stmt_while
		puts(.str_stmt_while)
		print_expression(dat1)
		putcln(41)
		print_statement_with_depth(dat2, depth)
		return
		:str_stmt_while
			string while (
			byte 0
	:print_stmt_for
		puts(.str_stmt_for)
		if dat1 == 0 goto print_for_noexpr1
		print_expression(dat1)
		:print_for_noexpr1
		puts(.str_for_sep)
		if dat2 == 0 goto print_for_noexpr2
		print_expression(dat2)
		:print_for_noexpr2
		puts(.str_for_sep)
		if dat3 == 0 goto print_for_noexpr3
		print_expression(dat3)
		:print_for_noexpr3
		putcln(41)
		print_statement_with_depth(dat4, depth)
		return
		:str_stmt_for
			string for (
			byte 0
		:str_for_sep
			byte 59
			byte 32
			byte 0
	:print_stmt_switch
		puts(.str_stmt_switch)
		print_expression(dat1)
		putcln(41)
		print_statement_with_depth(dat2, depth)
		return
		:str_stmt_switch
			string switch (
			byte 0
	:print_stmt_do
		puts(.str_stmt_do)
		print_statement_with_depth(dat1, depth)
		print_indents(depth)
		puts(.str_stmt_while)
		print_expression(dat2)
		putc(41)
		putcln(59)
		return
		:str_stmt_do
			string do
			byte 10
			byte 0
	:print_stmt_if
		puts(.str_stmt_if)
		print_expression(dat1)
		putcln(41)
		print_statement_with_depth(dat2, depth)
		if dat3 == 0 goto return_0
		print_indents(depth)
		putsln(.str_else)
		print_statement_with_depth(dat3, depth)
		return
		:str_stmt_if
			string if (
			byte 0
	:print_stmt_break
		puts(.str_stmt_break)
		return
	:str_stmt_break
		string break
		byte 59 ; semicolon
		byte 10
		byte 0
	:print_stmt_continue
		puts(.str_stmt_continue)
		return
	:str_stmt_continue
		string continue
		byte 59 ; semicolon
		byte 10
		byte 0
	:print_stmt_return
		puts(.str_return)
		if dat1 == 0 goto print_ret_noexpr
		putc(32)
		print_expression(dat1)
		:print_ret_noexpr
		puts(.str_semicolon_newline)
		return
	:print_stmt_local_decl
		puts(.str_local_decl)
		putn(dat1)
		puts(.str_local_type)
		print_type(dat2)
		if dat3 != 0 goto print_stmt_local_initializer
		if dat4 != 0 goto print_stmt_local_copy_address
		:stmt_local_decl_finish
		puts(.str_semicolon_newline)
		return
		:print_stmt_local_initializer
			putc(32)
			putc(61) ; =
			putc(32)
			print_expression(dat3)
			goto stmt_local_decl_finish
		:print_stmt_local_copy_address
			puts(.str_local_copyfrom)
			putx32(dat4)
			goto stmt_local_decl_finish
	:str_local_decl
		string local variable at rbp-
		byte 0
	:str_local_type
		string  type
		byte 32
		byte 0
	:str_local_copyfrom
		string  copy from
		byte 32
		byte 0
	:print_stmt_block
		putcln('{)
		depth += 1
		:print_block_loop
			if *1dat1 == 0 goto print_block_loop_end
			print_statement_with_depth(dat1, depth)
			dat1 += 40
			goto print_block_loop
		:print_block_loop_end
		depth -= 1
		print_indents(depth)
		putcln('})
		return
	:print_stmt_goto
		puts(.str_goto)
		putc(32)
		puts(dat1)
		puts(.str_semicolon_newline)
		return
	:print_stmt_case
		puts(.str_case)
		putc(32)
		putn_signed(dat1)
		putcln(':)
		return
	:print_stmt_default
		puts(.str_default)
		putcln(':)
		return
; parse a global variable's initializer
; e.g.    int x[5] = {1+8, 2, 3, 4, 5};
; advances *p_token to the token right after the initializer
; if `type` refers to a sizeless array type (e.g. int x[] = {1,2,3};), it will be altered to the correct size
; outputs the initializer data to rwdata_end_addr, and advances it accordingly.
;    this aligns rwdata_end_addr before writing data, so if you want the initial value of rwdata_end_addr
;    to correspond to the address, ALIGN IT FIRST.
function parse_constant_initializer
	argument p_token
	argument type
	
	local addr0
	local subtype
	local token
	local depth
	local a
	local b
	local c
	local len
	local p
	local expr
	local value
	
	token = *8p_token
	
	p = types + type
	if *1p == TYPE_STRUCT goto parse_struct_initializer
	if *1p == TYPE_ARRAY goto parse_array_initializer
	if *1token == SYMBOL_LBRACE goto parse_braced_expression_initializer
	
	; an ordinary expression
	; first, find the end of the expression.
	local end
	depth = 0
	end = token
	; find the end of the initializer, i.e. the next comma or semicolon not inside braces,
	; square brackets, or parentheses:
	:find_init_end_loop
		c = *1end
		if c == TOKEN_EOF goto find_init_end_eof
		if c == SYMBOL_LPAREN goto find_init_end_incdepth
		if c == SYMBOL_LSQUARE goto find_init_end_incdepth
		if c == SYMBOL_LBRACE goto find_init_end_incdepth
		if c == SYMBOL_RPAREN goto find_init_end_decdepth
		if c == SYMBOL_RSQUARE goto find_init_end_decdepth
		if c == SYMBOL_RBRACE goto find_init_end_decdepth
		if depth > 0 goto find_init_cont
		if depth < 0 goto init_end_bad_brackets
		if c == SYMBOL_COMMA goto found_init_end
		if c == SYMBOL_SEMICOLON goto found_init_end
		:find_init_cont
		end += 16
		goto find_init_end_loop
		:find_init_end_incdepth
			depth += 1
			goto find_init_cont
		:find_init_end_decdepth
			depth -= 1
			if depth < 0 goto found_init_end
			goto find_init_cont
	:found_init_end
	
	p = types + type
	if *1p > TYPE_POINTER goto expression_initializer_for_nonscalar_type
	
	global 8000 dat_const_initializer
	expr = &dat_const_initializer
	parse_expression(token, end, expr)
	evaluate_constant_expression(token, expr, &value)
	if *1p == TYPE_FLOAT goto init_float_check
	if *1p == TYPE_DOUBLE goto init_double_check
	:init_good
	token = end
	c = type_sizeof(type)
	; align rwdata_end_addr to size of type
	rwdata_end_addr += c - 1
	rwdata_end_addr /= c
	rwdata_end_addr *= c
	p = output_file_data + rwdata_end_addr
	rwdata_end_addr += c
	if c == 1 goto write_initializer1
	if c == 2 goto write_initializer2
	if c == 4 goto write_initializer4
	if c == 8 goto write_initializer8
	die(.init_somethings_very_wrong)
	:init_somethings_very_wrong
		string Scalar type with a weird size. This shouldn't happen.
		byte 0
	:write_initializer1
		*1p = value
		goto const_init_ret
	:write_initializer2
		*2p = value
		goto const_init_ret
	:write_initializer4
		*4p = value
		goto const_init_ret
	:write_initializer8
		*8p = value
		goto const_init_ret
	:init_double_check
		; check if someone did    double x[] = {3};
		;  we would screw this up and set x[0] to the binary representation of 3 as an integer
		
		if value == 0 goto init_good ; 0 is fine
		
		; this isn't foolproof, but it should work most of the time
		if value [ 0x10000000000000 goto bad_float_initializer
		if value ] 0xfff0000000000000 goto bad_float_initializer
		
		goto init_good
	:init_float_check
		if value == 0 goto init_good
		goto bad_float_initializer
	:bad_float_initializer
		token_error(token, .str_bad_float_initializer)
	:str_bad_float_initializer
		string Bad floating-point initializer.
		byte 0
	:const_init_ret
	*8p_token = token
	return
	
	:parse_braced_expression_initializer
		; a scalar initializer may optionally be enclosed in braces, e.g.
		;   int x = {3};
		token += 16
		parse_constant_initializer(&token, type)
		if *1token != SYMBOL_RBRACE goto bad_scalar_initializer
		token += 16
		goto const_init_ret
		:bad_scalar_initializer
			token_error(token, .str_bad_scalar_initializer)
		:str_bad_scalar_initializer
			string Bad scalar initializer.
			byte 0
	:parse_array_initializer
		if *1token == TOKEN_STRING_LITERAL goto parse_string_array_initializer ; check for  char x[] = "hello";
		if *1token != SYMBOL_LBRACE goto array_init_no_lbrace ; only happens when recursing
		token += 16
		:array_init_no_lbrace
		addr0 = rwdata_end_addr
		
		len = types + type
		len += 1 ; skip TYPE_ARRAY
		len = *8len
		
		subtype = type + 9 ; skip TYPE_ARRAY and size
		:array_init_loop
			if *1token == TOKEN_EOF goto array_init_eof
			parse_constant_initializer(&token, subtype)
			len -= 1 ; kind of horrible hack.  -len will track the number of elements for sizeless arrays, and len will count down to 0 for sized arrays
			if len == 0 goto array_init_loop_end
			if *1token == SYMBOL_RBRACE goto array_init_loop_end
			if *1token != SYMBOL_COMMA goto bad_array_initializer
			token += 16 ; skip comma
			goto array_init_loop
		:array_init_loop_end
	
		if *1token != SYMBOL_RBRACE goto array_init_noskip
		p = *8p_token
		if *1p != SYMBOL_LBRACE goto array_init_noskip ; we don't want to skip the closing } because it doesn't belong to us.
		:array_init_skip
			token += 16 ; skip } or ,
		:array_init_noskip
		p = types + type
		p += 1 ; skip TYPE_ARRAY
		if *8p == 0 goto sizeless_array_initializer
			; sized array
			rwdata_end_addr = addr0
			c = type_sizeof(subtype)
			rwdata_end_addr += *8p * c ; e.g. int x[50] = {1,2};  advance rwdata_end_addr by 50*sizeof(int)
			goto const_init_ret
		:sizeless_array_initializer
			; sizeless array
			*8p = 0 - len
			goto const_init_ret
	:array_init_eof
		token_error(token, .str_array_init_eof)
	:str_array_init_eof
		string Array initializer does not end.
		byte 0
	:bad_array_initializer
		token_error(token, .str_bad_array_initializer)
	:str_bad_array_initializer
		string Bad array initializer.
		byte 0
	:parse_struct_initializer
		addr0 = rwdata_end_addr
		
		if *1token != SYMBOL_LBRACE goto struct_init_no_lbrace ; only happens when recursing
		token += 16
		:struct_init_no_lbrace
		
		a = type_alignof(type)
		; align rwdata_end_addr properly
		rwdata_end_addr += a - 1
		rwdata_end_addr /= a
		rwdata_end_addr *= a
		
		p = types + type
		p += 1
		b = structure_is_union(*8p)
		if b != 0 goto parse_union_initializer
		
		; struct initializer
		a = *8p
		:struct_init_loop
			if *1token == TOKEN_EOF goto struct_init_eof
			
			; skip name of member
			a = memchr(a, 0)
			a += 5 ; skip null terminator, offset
			subtype = *4a
			a += 4
			
			parse_constant_initializer(&token, subtype)
			if *1token == SYMBOL_RBRACE goto struct_init_loop_end
			if *1a == 0 goto struct_init_loop_end ; finished reading all the members of the struct
			if *1token != SYMBOL_COMMA goto bad_struct_initializer
			token += 16 ; skip comma
			goto struct_init_loop
		:struct_init_loop_end
		
		:struct_init_ret
		c = type_sizeof(type)
		rwdata_end_addr = addr0 + c ; add full size of struct/union to rwdata_end_addr, even if initialized member is smaller than that
		
		if *1token != SYMBOL_RBRACE goto struct_init_noskip
		p = *8p_token
		if *1p != SYMBOL_LBRACE goto struct_init_noskip ; we don't want to skip the closing } because it doesn't belong to us.
		token += 16 ; skip }
		:struct_init_noskip
		
		goto const_init_ret
		
	:parse_union_initializer
		a = ident_list_value_at_index(*8p, 0)
		subtype = a > 32 ; extract type
		parse_constant_initializer(&token, subtype)
		goto struct_init_ret
	
	:struct_init_eof
		token_error(token, .str_struct_init_eof)
	:str_struct_init_eof
		string struct initializer does not end.
		byte 0
	:bad_struct_initializer
		token_error(token, .str_bad_struct_initializer)
	:str_bad_struct_initializer
		string Bad struct initializer.
		byte 0
	
	:parse_string_array_initializer
		p = types + type
		p += 9
		if *1p != TYPE_CHAR goto string_literal_bad_type
		p -= 8
		c = *8p ; array size
		token += 8
		a = output_file_data + rwdata_end_addr ; destination (where to put the string data)
		b = output_file_data + *8token ; source (where the string data is now)
		token += 8
		if c == 0 goto string_literal_sizeless_initializer
		value = strlen(b)
		if c < value goto string_literal_init_too_long ; e.g. char x[3] = "hello";
		strcpy(a, b)
		rwdata_end_addr += c ; advance by c, which is possibly more than the length of the string--the remaining bytes will be 0s
		goto const_init_ret
	:string_literal_sizeless_initializer ; e.g. char x[] = "hello";
		c = strlen(b)
		c += 1 ; null terminator
		*8p = c ; set array size
		strcpy(a, b)
		rwdata_end_addr += c
		goto const_init_ret
	:string_literal_init_too_long
		token_error(token, .str_string_literal_init_too_long)
	:str_string_literal_init_too_long
		string String literal is too long to fit in array.
		byte 0 
	:stuff_after_string_literal
		token_error(token, .str_stuff_after_string_literal)
	:str_stuff_after_string_literal
		string Stuff after string literal in initializer.
		byte 0
	:string_literal_bad_type
		token_error(token, .str_string_literal_bad_type)
	:str_string_literal_bad_type
		string Bad type for string literal initializer (i.e. not char* or char[]).
		byte 0
	:find_init_end_eof
		token_error(token, .str_find_init_end_eof)
	:str_find_init_end_eof
		string Can't find end of initializer.
		byte 0
	:init_end_bad_brackets
		token_error(end, .str_init_end_bad_brackets)
	:str_init_end_bad_brackets
		string Too many closing brackets.
		byte 0
	:expression_initializer_for_nonscalar_type
		token_error(token, .str_expression_initializer_for_nonscalar_type)
	:str_expression_initializer_for_nonscalar_type
		string Expression initializer for non-scalar type.
		byte 0
	:floating_initializer_other_than_0
		token_error(token, .str_floating_initializer_other_than_0)
	:str_floating_initializer_other_than_0
		string Only 0 is supported as a constant floating-point initializer.
		byte 0
; *p_token should be pointing to a {, this will advance it to point to the matching }
function token_skip_to_matching_rbrace
	argument p_token
	local token
	local depth
	token = *8p_token
	depth = 0
	:skip_rbrace_loop
		if *1token == SYMBOL_LBRACE goto skip_rbrace_incdepth
		if *1token == SYMBOL_RBRACE goto skip_rbrace_decdepth
		if *1token == TOKEN_EOF goto skip_rbrace_eof
		:skip_rbrace_next
		token += 16
		goto skip_rbrace_loop
		:skip_rbrace_incdepth
			depth += 1
			goto skip_rbrace_next
		:skip_rbrace_decdepth
			depth -= 1
			if depth == 0 goto skip_rbrace_ret
			goto skip_rbrace_next
	:skip_rbrace_ret
	*8p_token = token
	return
	
	:skip_rbrace_eof
		token_error(*8p_token, .str_skip_rbrace_eof)
	:str_skip_rbrace_eof
		string Unmatched {
		byte 0

; *p_token should be pointing to a [, this will advance it to point to the matching ]
function token_skip_to_matching_rsquare
	argument p_token
	local token
	local depth
	token = *8p_token
	depth = 0
	:skip_square_loop
		if *1token == SYMBOL_LSQUARE goto skip_square_incdepth
		if *1token == SYMBOL_RSQUARE goto skip_square_decdepth
		if *1token == TOKEN_EOF goto skip_square_eof
		:skip_square_next
		token += 16
		goto skip_square_loop
		:skip_square_incdepth
			depth += 1
			goto skip_square_next
		:skip_square_decdepth
			depth -= 1
			if depth == 0 goto skip_square_ret
			goto skip_square_next
	:skip_square_ret
	*8p_token = token
	return
	
	:skip_square_eof
		token_error(*8p_token, .str_skip_square_eof)
	:str_skip_square_eof
		string Unmatched [
		byte 0

; token should be pointing to (, this returns the corresponding )
function token_matching_rparen
	argument token
	local token0
	local depth
	token0 = token
	depth = 0
	:matching_rparen_loop
		if *1token == SYMBOL_LPAREN goto matching_rparen_incdepth
		if *1token == SYMBOL_RPAREN goto matching_rparen_decdepth
		if *1token == TOKEN_EOF goto matching_rparen_eof
		:matching_rparen_next
		token += 16
		goto matching_rparen_loop
		:matching_rparen_incdepth
			depth += 1
			goto matching_rparen_next
		:matching_rparen_decdepth
			depth -= 1
			if depth == 0 goto matching_rparen_ret
			goto matching_rparen_next
	:matching_rparen_ret
	return token
	
	:matching_rparen_eof
		token_error(token0, .str_matching_rparen_eof)
	:str_matching_rparen_eof
		string Unmatched (
		byte 0

; *p_token should be on a ); this goes back to the corresponding (
;  THERE MUST ACTUALLY BE A MATCHING BRACKET, OTHERWISE THIS WILL DO BAD THINGS
function token_reverse_to_matching_lparen
	argument p_token
	local token
	local depth
	token = *8p_token
	depth = 0
	:reverse_paren_loop
		if *1token == SYMBOL_LPAREN goto reverse_paren_incdepth
		if *1token == SYMBOL_RPAREN goto reverse_paren_decdepth
		:reverse_paren_next
		token -= 16
		goto reverse_paren_loop
		:reverse_paren_incdepth
			depth += 1
			if depth == 0 goto reverse_paren_ret
			goto reverse_paren_next
		:reverse_paren_decdepth
			depth -= 1
			goto reverse_paren_next
	:reverse_paren_ret
	*8p_token = token
	return


; return the next semicolon not in parentheses, square brackets, or braces. 
function token_next_semicolon_not_in_brackets
	argument token0
	
	local token
	local depth
	local c
	
	depth = 0
	token = token0
	:next_semicolon_loop
		c = *1token
		if c == TOKEN_EOF goto next_semicolon_eof
		if depth != 0 goto next_semicolon_nocheck
		if c == SYMBOL_SEMICOLON goto next_semicolon_loop_end
		:next_semicolon_nocheck
		token += 16
		if c == SYMBOL_LPAREN goto next_semicolon_incdepth
		if c == SYMBOL_RPAREN goto next_semicolon_decdepth
		if c == SYMBOL_LSQUARE goto next_semicolon_incdepth
		if c == SYMBOL_RSQUARE goto next_semicolon_decdepth
		if c == SYMBOL_LBRACE goto next_semicolon_incdepth
		if c == SYMBOL_RBRACE goto next_semicolon_decdepth
		goto next_semicolon_loop
		:next_semicolon_incdepth
			depth += 1
			goto next_semicolon_loop
		:next_semicolon_decdepth
			depth -= 1
			goto next_semicolon_loop
	:next_semicolon_loop_end
	return token
	:next_semicolon_eof
		token_error(token0, .str_next_semicolon_eof)
		:str_next_semicolon_eof
			string End of file found while searching for semicolon.
			byte 0 


; return the next semicolon or comma not in parentheses, square brackets, or braces;
;   or the next unmatched right bracket (of any type) 
function token_next_semicolon_comma_rbracket
	argument token0
	
	local token
	local depth
	local c
	
	depth = 0
	token = token0
	:next_semicomma_loop
		c = *1token
		if c == TOKEN_EOF goto next_semicomma_eof
		if depth != 0 goto next_semicomma_nocheck
		if c == SYMBOL_SEMICOLON goto next_semicomma_loop_end
		if c == SYMBOL_COMMA goto next_semicomma_loop_end
		:next_semicomma_nocheck
		token += 16
		if c == SYMBOL_LPAREN goto next_semicomma_incdepth
		if c == SYMBOL_RPAREN goto next_semicomma_decdepth
		if c == SYMBOL_LSQUARE goto next_semicomma_incdepth
		if c == SYMBOL_RSQUARE goto next_semicomma_decdepth
		if c == SYMBOL_LBRACE goto next_semicomma_incdepth
		if c == SYMBOL_RBRACE goto next_semicomma_decdepth
		goto next_semicomma_loop
		:next_semicomma_incdepth
			depth += 1
			goto next_semicomma_loop
		:next_semicomma_decdepth
			depth -= 1
			if depth < 0 goto next_semicomma_loop_end_dectoken
			goto next_semicomma_loop
	:next_semicomma_loop_end_dectoken
	token -= 16
	:next_semicomma_loop_end
	return token
	:next_semicomma_eof
		token_error(token0, .str_next_semicomma_eof)
		:str_next_semicomma_eof
			string End of file found while searching for semicolon/comma/closing bracket.
			byte 0 



; we split types into base (B), prefix (P) and suffix (S)
;     struct Thing (*things[5])(void), *something_else[3];
;     BBBBBBBBBBBB PP      SSSSSSSSSS  P              SSS
; the following functions deal with figuring out where these parts are.

; return the end of the base for this type.
function type_get_base_end
	argument token
	local c
	c = *1token
	if c == KEYWORD_STRUCT goto skip_struct_union_enum
	if c == KEYWORD_UNION goto skip_struct_union_enum
	if c == KEYWORD_ENUM goto skip_struct_union_enum
	; skip the "base type"
	token += 16 ; importantly, this skips the typedef'd name if there is one (e.g. typedef int Foo; Foo x;)
	:skip_base_type_loop
		c = *1token
		if c == KEYWORD_UNSIGNED goto skip_base_type_loop_cont ;e.g. int unsigned x;
		if c == KEYWORD_CHAR goto skip_base_type_loop_cont ;e.g. unsigned char x;
		if c == KEYWORD_SHORT goto skip_base_type_loop_cont ;e.g. unsigned short x;
		if c == KEYWORD_INT goto skip_base_type_loop_cont ;e.g. unsigned int x;
		if c == KEYWORD_LONG goto skip_base_type_loop_cont ;e.g. unsigned long x;
		if c == KEYWORD_DOUBLE goto skip_base_type_loop_cont ;e.g. long double x;
		goto skip_base_type_loop_end
		:skip_base_type_loop_cont
			token += 16
			goto skip_base_type_loop
	
	:skip_base_type_loop_end
	return token
	
	:skip_struct_union_enum
		token += 16
		if *1token != TOKEN_IDENTIFIER goto skip_sue_no_name
			token += 16 ; struct *blah*
		:skip_sue_no_name
		if *1token != SYMBOL_LBRACE goto skip_base_type_loop_end ; e.g. struct Something x[5];
		; okay we have something like
		;   struct {
		;       int x, y;
		;   } test;
		token_skip_to_matching_rbrace(&token)
		token += 16
		goto skip_base_type_loop_end


; return the end of this type prefix
function type_get_prefix_end
	argument token
	local c
	:find_prefix_end_loop
		c = *1token
		if c == TOKEN_IDENTIFIER goto found_prefix_end
		if c == KEYWORD_UNSIGNED goto prefix_end_cont
		if c == KEYWORD_CHAR goto prefix_end_cont
		if c == KEYWORD_SHORT goto prefix_end_cont
		if c == KEYWORD_INT goto prefix_end_cont
		if c == KEYWORD_LONG goto prefix_end_cont
		if c == KEYWORD_FLOAT goto prefix_end_cont
		if c == KEYWORD_DOUBLE goto prefix_end_cont
		if c == SYMBOL_LPAREN goto prefix_end_cont
		if c == SYMBOL_TIMES goto prefix_end_cont
		if c == SYMBOL_LSQUARE goto found_prefix_end
		if c == SYMBOL_RPAREN goto found_prefix_end
		goto found_prefix_end
		
		:prefix_end_cont
			token += 16
			goto find_prefix_end_loop
	:found_prefix_end
	return token

; return the end of this type suffix
; NOTE: you must pass in the PREFIX.
; (In general, we can't find the end of the suffix without knowing the prefix.)
;    int (*x);
;            ^ suffix ends here
;    (int *)
;          ^ suffix ends here
function type_get_suffix_end
	argument prefix
	local depth
	local token
	local c
	
	; find end of suffix
	token = prefix
	depth = 0 ; parenthesis/square bracket depth
	:suffix_end_loop
		c = *1token
		if c == TOKEN_IDENTIFIER goto suffix_end_cont
		if c == SYMBOL_LSQUARE goto suffix_end_incdepth
		if c == SYMBOL_RSQUARE goto suffix_end_decdepth
		if c == SYMBOL_LPAREN goto suffix_end_incdepth
		if c == SYMBOL_RPAREN goto suffix_end_decdepth
		if c == SYMBOL_TIMES goto suffix_end_cont
		if depth == 0 goto suffix_end_found
		if c == TOKEN_EOF goto type_get_suffix_bad_type
		goto suffix_end_cont
		
		:suffix_end_incdepth
			depth += 1
			goto suffix_end_cont
		:suffix_end_decdepth
			depth -= 1
			if depth < 0 goto suffix_end_found
			goto suffix_end_cont
		:suffix_end_cont
			token += 16
			goto suffix_end_loop
	:suffix_end_found
	
	return token
	:type_get_suffix_bad_type
		token_error(prefix, .str_bad_type_suffix)
	:str_bad_type_suffix
		string Bad type suffix.
		byte 0


; writes to *(types + types_bytes_used), and updates types_bytes_used
function parse_type_declarators
	argument prefix
	argument prefix_end
	argument suffix
	argument suffix_end
	; function parameters will be stored here (null-byte separated) if not 0
	; make sure you check that it's actually a function type before reading this
	argument parameters
	local p
	local expr
	local n
	local c
	local depth
	local out
	local param_names_out
	param_names_out = parameters
	
	; main loop for parsing types
	:type_declarators_loop
		p = prefix_end - 16
		if *1suffix == SYMBOL_LSQUARE goto parse_array_type
		if *1suffix == SYMBOL_LPAREN goto parse_function_type
		if *1p == SYMBOL_TIMES goto parse_pointer_type
		if suffix == suffix_end goto type_declarators_loop_end
		if *1suffix == SYMBOL_RPAREN goto parse_type_remove_parentheses
		goto parse_typedecls_bad_type
		
		:parse_pointer_type
			param_names_out = 0
			out = types + types_bytes_used
			*1out = TYPE_POINTER
			types_bytes_used += 1
			prefix_end = p
			goto type_declarators_loop
		:parse_array_type
			param_names_out = 0
			out = types + types_bytes_used
			*1out = TYPE_ARRAY
			types_bytes_used += 1
			
			p = suffix
			token_skip_to_matching_rsquare(&p)
			suffix += 16 ; skip [
			if *1suffix == SYMBOL_RSQUARE goto array_no_size
	
			; little hack to avoid screwing up types like  double[sizeof(int)]
			;   temporarily pretend we're using a lot more of types
			local prev_types_bytes_used
			prev_types_bytes_used = types_bytes_used
			types_bytes_used += 4000
			
			expr = malloc(4000)
			parse_expression(suffix, p, expr)
			evaluate_constant_expression(prefix, expr, &n)
			if n < 0 goto bad_array_size
			free(expr)
			
			types_bytes_used = prev_types_bytes_used
			
			out = types + types_bytes_used
			*8out = n
			types_bytes_used += 8
			
			suffix = p + 16
			goto type_declarators_loop
			:bad_array_size
				token_error(suffix, .str_bad_array_size)
			:str_bad_array_size
				string Very large or negative array size.
				byte 0
			:array_no_size
				; e.g. int x[] = {1,2,3};
				out = types + types_bytes_used
				*8out = 0
				types_bytes_used += 8
				suffix += 16
				goto type_declarators_loop
		:parse_function_type
			local param_base_type
			local param_prefix
			local param_prefix_end
			local param_suffix
			local param_suffix_end
			local d
			
			if param_names_out == 0 goto skip_ftype_reset
			; this gets set to 1 if at least one parameter has no name.
			; we don't just error here, because we need to support declarations like:
			;   int f(int, int);
			function_param_has_no_name = 0
			:skip_ftype_reset
			
			out = types + types_bytes_used
			*1out = TYPE_FUNCTION
			types_bytes_used += 1
			
			p = suffix + 16
			if *1p == SYMBOL_RPAREN goto ftype_no_parameters ; e.g. int f() { return 17; }
			if *1p != KEYWORD_VOID goto ftype_has_parameters
			n = p + 16
			suffix += 16
			if *1n != SYMBOL_RPAREN goto ftype_has_parameters
			:ftype_no_parameters
			; special handling of function type with no parameters
			out = types + types_bytes_used
			*1out = 0
			types_bytes_used += 1
			suffix += 32
			goto type_declarators_loop
			
			
			:ftype_has_parameters
			:function_type_loop
				if *1p == SYMBOL_DOTDOTDOT goto ftype_varargs
				param_base_type = p
				param_prefix = type_get_base_end(param_base_type)
				param_prefix_end = type_get_prefix_end(param_prefix)
				param_suffix = param_prefix_end
				if *1param_suffix != TOKEN_IDENTIFIER goto functype_no_ident
				param_suffix += 16
				if param_names_out == 0 goto functype_had_ident
				param_suffix -= 8
				param_names_out = strcpy(param_names_out, *8param_suffix)
				param_names_out += 1
				param_suffix += 8
				goto functype_had_ident
				:functype_no_ident
				if param_names_out != 0 goto no_param_name
				:functype_had_ident
				param_suffix_end = type_get_suffix_end(param_prefix)
				c = types + types_bytes_used
				parse_type_declarators(param_prefix, param_prefix_end, param_suffix, param_suffix_end, 0)
				parse_base_type(param_base_type)
				if *1c != TYPE_ARRAY goto function_param_not_array
				; decay array into pointer
				*1c = TYPE_POINTER
				c += 1
				c -= types
				d = c + 8
				type_copy_ids(c, d)
				types_bytes_used -= 8
				:function_param_not_array
				p = param_suffix_end
				if *1p == SYMBOL_RPAREN goto function_type_loop_end
				if *1p != SYMBOL_COMMA goto parse_typedecls_bad_type
				p += 16
				goto function_type_loop
				:ftype_varargs
					; ignore varargs
					p += 16
					if *1p != SYMBOL_RPAREN goto stuff_after_ftype_varargs
					goto function_type_loop_end
			:function_type_loop_end
			if param_names_out == 0 goto ftype_skip_zpno
			*1param_names_out = 0
			; prevent lower-level parts of type from writing parameters 
			param_names_out = 0
			:ftype_skip_zpno
			
			out = types + types_bytes_used
			*1out = 0
			types_bytes_used += 1
			suffix = p + 16
			goto type_declarators_loop
			:no_param_name
				function_param_has_no_name = 1
				goto functype_had_ident
			:stuff_after_ftype_varargs
				token_error(p, .str_stuff_after_ftype_varargs)
			:str_stuff_after_ftype_varargs
				string Stuff after ... (varargs) in function type.
				byte 0
		:parse_type_remove_parentheses
			; interestingly:
			;   int (f(int x)) { return x * 2; }
			; seems perfectly legal.
			if *1p != SYMBOL_LPAREN goto parse_typedecls_bad_type
			prefix_end = p
			suffix += 16
			goto type_declarators_loop
	:type_declarators_loop_end
	return 0
	:parse_typedecls_bad_type
		token_error(prefix, .str_bad_type_declarators)
	:str_bad_type_declarators
		string Bad type declarators.
		byte 0
	
; writes to *(types + types_bytes_used), and updates types_bytes_used (no return value)
function parse_base_type
	argument base_type
	local out
	local flags
	local p
	local c
	local depth
	local is_struct
	is_struct = 0
	
	out = types + types_bytes_used
	
	c = *1base_type
	if c == TOKEN_IDENTIFIER goto base_type_typedef
	if c == KEYWORD_STRUCT goto base_type_struct
	if c == KEYWORD_UNION goto base_type_union
	if c == KEYWORD_ENUM goto base_type_enum
	if c == KEYWORD_FLOAT goto base_type_float
	if c == KEYWORD_VOID goto base_type_void
	
	; "normal" type like int, unsigned char, etc.
	; annoyingly, all of these are equivalent to `unsigned long`:
	;    unsigned long int
	;    long unsigned int
	;    int long unsigned
	;    etc.
	; so we represent these as  PARSETYPE_FLAG_UNSIGNED|PARSETYPE_FLAG_LONG|PARSETYPE_FLAG_INT.
#define PARSETYPE_FLAG_UNSIGNED 1
#define PARSETYPE_FLAG_CHAR 2
#define PARSETYPE_FLAG_SHORT 4
#define PARSETYPE_FLAG_INT 8
#define PARSETYPE_FLAG_LONG 16
#define PARSETYPE_FLAG_DOUBLE 32
	flags = 0
	p = base_type
	:base_type_normal_loop
		c = *1p
		p += 16
		if c == KEYWORD_CHAR goto base_type_flag_char
		if c == KEYWORD_SHORT goto base_type_flag_short
		if c == KEYWORD_INT goto base_type_flag_int
		if c == KEYWORD_LONG goto base_type_flag_long
		if c == KEYWORD_UNSIGNED goto base_type_flag_unsigned
		if c == KEYWORD_DOUBLE goto base_type_flag_double
		goto base_type_normal_loop_end
		:base_type_flag_char
			c = flags & PARSETYPE_FLAG_CHAR
			if c != 0 goto repeated_base_type
			flags |= PARSETYPE_FLAG_CHAR
			goto base_type_normal_loop
		:base_type_flag_short
			c = flags & PARSETYPE_FLAG_SHORT
			if c != 0 goto repeated_base_type
			flags |= PARSETYPE_FLAG_SHORT
			goto base_type_normal_loop
		:base_type_flag_int
			c = flags & PARSETYPE_FLAG_INT
			if c != 0 goto repeated_base_type
			flags |= PARSETYPE_FLAG_INT
			goto base_type_normal_loop
		:base_type_flag_long
			c = flags & PARSETYPE_FLAG_LONG
			;if c != 0 goto repeated_base_type ; allow "long long" ...and also "long long long" i guess
			flags |= PARSETYPE_FLAG_LONG
			goto base_type_normal_loop
		:base_type_flag_unsigned
			c = flags & PARSETYPE_FLAG_UNSIGNED
			if c != 0 goto repeated_base_type
			flags |= PARSETYPE_FLAG_UNSIGNED
			goto base_type_normal_loop
		:base_type_flag_double
			c = flags & PARSETYPE_FLAG_DOUBLE
			if c != 0 goto repeated_base_type
			flags |= PARSETYPE_FLAG_DOUBLE
			goto base_type_normal_loop
		:repeated_base_type
			token_error(p, .str_repeated_base_type)
		:str_repeated_base_type
			string Arithmetic type repeated (e.g. unsigned unsigned int).
			byte 0
	:base_type_normal_loop_end
	if flags == 8 goto base_type_int ; `int`
	if flags == 1 goto base_type_uint ; `unsigned`
	if flags == 9 goto base_type_uint ; `unsigned int` etc.
	if flags == 2 goto base_type_char ; `char`
	if flags == 3 goto base_type_uchar ; `unsigned char` etc.
	if flags == 4 goto base_type_short ; `short`
	if flags == 12 goto base_type_short `short int` etc.
	if flags == 5 goto base_type_ushort ; `unsigned short` etc.
	if flags == 13 goto base_type_ushort ; `unsigned short int` etc. 
	if flags == 16 goto base_type_long ; `long`
	if flags == 24 goto base_type_long ; `long int` etc.
	if flags == 17 goto base_type_ulong ; `unsigned long` etc.
	if flags == 25 goto base_type_ulong ; `unsigned long int` etc.
	if flags == 32 goto base_type_double ; `double`
	if flags == 48 goto base_type_double ; `long double` (we use the same type for double and long double)
	
	goto bad_base_type
	
	:base_type_char
		*1out = TYPE_CHAR
		out += 1
		goto base_type_done
	:base_type_uchar
		*1out = TYPE_UNSIGNED_CHAR
		out += 1
		goto base_type_done
	:base_type_short
		*1out = TYPE_SHORT
		out += 1
		goto base_type_done
	:base_type_ushort
		*1out = TYPE_UNSIGNED_SHORT
		out += 1
		goto base_type_done
	:base_type_int
		*1out = TYPE_INT
		out += 1
		goto base_type_done
	:base_type_uint
		*1out = TYPE_UNSIGNED_INT
		out += 1
		goto base_type_done
	:base_type_long
		*1out = TYPE_LONG
		out += 1
		goto base_type_done
	:base_type_ulong
		*1out = TYPE_UNSIGNED_LONG
		out += 1
		goto base_type_done
	:base_type_double
		*1out = TYPE_DOUBLE
		out += 1
		goto base_type_done
	
	:base_type_done
	types_bytes_used = out - types
	return 0
	
	:base_type_struct
		is_struct = 1
		; fallthrough
	:base_type_union
		local struct_name
		local struct
		struct_name = .empty_string
		p = base_type + 16
		if *1p != TOKEN_IDENTIFIER goto base_type_have_name
		p += 8
		struct_name = *8p
		p += 8
		:base_type_have_name
		c = ident_list_lookup(structures, struct_name)
		if *1p == SYMBOL_LBRACE goto base_type_struct_definition
		
		if c == 0 goto base_type_new_incomplete_struct
		:base_type_named_struct
			; e.g. struct Foo x;
			*1out = TYPE_STRUCT
			out += 1
			*8out = c
			out += 8
			goto base_type_done
		:base_type_new_incomplete_struct
			; create an ident list for the incomplete struct, with nothing in it yet
			struct = ident_list_create(8000)
			; add it to the table
			ident_list_add(structures, struct_name, struct)
			c = struct
			goto base_type_named_struct
		:base_type_struct_definition
			;  @NONSTANDARD: we don't handle bit-fields.
			local struct_location
			local member_base_type
			local member_prefix
			local member_prefix_end
			local member_suffix
			local member_suffix_end
			local member_name
			local member_type
			local member_align
			local member_size
			
			struct_location = token_get_location(p)
			
			if c == 0 goto completely_new_struct
			if *1c != 0 goto struct_maybe_redefinition
				; ok we're filling in an incomplete struct
				struct = c
				goto struct_definition_fill_in
			
			:completely_new_struct
				; a completely new struct; hasn't been used as an incomplete struct
				struct = ident_list_create(8000) ; note: maximum "* 127 members in a single structure or union" C89 § 2.2.4.1
				ident_list_add(structures, struct_name, struct)
			
			:struct_definition_fill_in
			
			*1out = TYPE_STRUCT
			out += 1
			*8out = struct
			out += 8
			types_bytes_used = out - types
			p += 16 ; skip opening {
			
			local offset
			offset = 0
			
			if *1struct_name == 0 goto struct_unnamed
			ident_list_add(structure_locations, struct_name, struct_location)
			:struct_unnamed
			
			:struct_defn_loop
				if *1p == SYMBOL_RBRACE goto struct_defn_loop_end
				member_base_type = p
				p = type_get_base_end(member_base_type)
				:struct_defn_decl_loop ; handle each element of  int x, y[5], *z;
					member_prefix = p
					member_prefix_end = type_get_prefix_end(member_prefix)
					if *1member_prefix_end != TOKEN_IDENTIFIER goto member_no_identifier
					member_name = member_prefix_end + 8
					member_name = *8member_name
					
					c = ident_list_lookup_check(struct, member_name, 0)
					if c == 1 goto duplicate_member
					
					member_suffix = member_prefix_end + 16
					member_suffix_end = type_get_suffix_end(member_prefix)
					member_type = types_bytes_used
					
					
					parse_type_declarators(member_prefix, member_prefix_end, member_suffix, member_suffix_end, 0)
					parse_base_type(member_base_type)
					
					; make sure struct member is aligned
					member_align = type_alignof(member_type)
					; offset = ceil(offset / align) * align
					offset += member_align - 1
					offset /= member_align
					offset *= member_align
					
					if offset ] 0xffffffff goto struct_too_large
					;putnln(offset)
					; data = (type << 32) | offset
					c = member_type < 32
					c |= offset
					ident_list_add(struct, member_name, c)
					
					member_size = type_sizeof(member_type)
					offset += member_size * is_struct ; keep offset as 0 if this is a union
					p = member_suffix_end
					if *1p == SYMBOL_SEMICOLON goto struct_defn_decl_loop_end
					if *1p != SYMBOL_COMMA goto struct_bad_declaration
					p += 16 ; skip comma
					goto struct_defn_decl_loop
					:duplicate_member
						token_error(p, .str_duplicate_member)
					:str_duplicate_member
						string Duplicate member in struct/union.
						byte 0
				:struct_defn_decl_loop_end
				p += 16 ; skip semicolon
				goto struct_defn_loop
			:struct_defn_loop_end
			out = types + types_bytes_used
			goto base_type_done
		:struct_maybe_redefinition
			local other_location
			other_location = ident_list_lookup(structure_locations, struct_name)
			if other_location != struct_location goto struct_redefinition ; actual struct redefinition
			; we don't want lines like this to cause problems:   struct A { int x,y; } a,b;
			*1out = TYPE_STRUCT
			out += 1
			*8out = c
			out += 8
			goto base_type_done
		:struct_redefinition
			token_error(p, .str_struct_redefinition)
		:str_struct_redefinition
			string struct redefinition.
			byte 0
		:struct_bad_declaration
			token_error(p, .str_struct_bad_declaration)
		:str_struct_bad_declaration
			string Bad declaration in struct.
			byte 0
		:struct_too_large
			token_error(p, .str_struct_too_large)
		:str_struct_too_large
			string struct too large (maximum is 4GB).
			byte 0
		:member_no_identifier
			; e.g. struct { int; };
			token_error(p, .str_member_no_identifier)
		:str_member_no_identifier
			string No identifier in struct member.
			byte 0
	:base_type_enum
		local q
		local expr
		
		*1out = TYPE_INT ; treat any enum as int
		out += 1
		types_bytes_used = out - types
		
		p = base_type + 16
		if *1p == SYMBOL_LBRACE goto enum_definition
		if *1p != TOKEN_IDENTIFIER goto bad_base_type ; e.g. enum int x;
		p += 16
		if *1p == SYMBOL_LBRACE goto enum_definition
		goto base_type_done ; just using an enum type, not defining it.
		:enum_definition
		local name
		local value
		value = -1 ; consider initial previous value as -1, because -1 + 1 = 0
		p += 16 ; skip opening {
		:enum_defn_loop
			if *1p == SYMBOL_RBRACE goto enum_defn_loop_end
			if *1p != TOKEN_IDENTIFIER goto bad_enum_definition
			p += 8
			name = *8p
			p += 8
			if *1p == SYMBOL_COMMA goto enum_defn_no_equals
			if *1p == SYMBOL_RBRACE goto enum_defn_no_equals
			if *1p != SYMBOL_EQ goto bad_enum_definition ; e.g. enum { X ! };
				; value provided, e.g. X = 5,
				p += 16
				depth = 0 ; parenthesis depth
				q = p
				; find matching comma/right brace 
				;  -- yes, a comma can appear in an enumerator expression, e.g.
				;     enum { X = sizeof(struct{int x, y;}) };
				; or  enum { X = (enum {A,B})3 };
				
				; find associated comma or right-brace
				:enum_comma_loop
					if depth > 0 goto enum_comma_deep
					if *1q == SYMBOL_COMMA goto enum_comma_loop_end
					if *1q == SYMBOL_RBRACE goto enum_comma_loop_end
					:enum_comma_deep
					if *1q == TOKEN_EOF goto bad_base_type
					c = *1q
					q += 16
					if c == SYMBOL_LPAREN goto enum_comma_incdepth
					if c == SYMBOL_RPAREN goto enum_comma_decdepth
					goto enum_comma_loop
					:enum_comma_incdepth
						depth += 1
						goto enum_comma_loop
					:enum_comma_decdepth
						depth -= 1
						goto enum_comma_loop
				:enum_comma_loop_end
				expr = malloc(4000)
				parse_expression(p, q, expr)
				evaluate_constant_expression(p, expr, &value)
				free(expr)
				if value < -0x80000000 goto bad_enumerator
				if value > 0x7fffffff goto bad_enumerator
				ident_list_add(enumerators, name, value)
				p = q
				if *1p == SYMBOL_RBRACE goto enum_defn_loop_end
				p += 16 ; skip ,
				goto enum_defn_loop
				:bad_enumerator
					token_error(p, .str_bad_enumerator)
				:str_bad_enumerator
					string Enumerators too large for int.
					byte 0
			:enum_defn_no_equals
				; no value provided, e.g. X,
				; the value of this enumerator is one more than the value of the last one
				value += 1
				ident_list_add(enumerators, name, value)
				if *1p == SYMBOL_RBRACE goto enum_defn_loop_end
				p += 16 ; skip ,
				goto enum_defn_loop
		:enum_defn_loop_end
		out = types + types_bytes_used ; fix stuff in case there were any types in the enumerator expressions
		goto base_type_done
		:bad_enum_definition
			token_error(p, .str_bad_enum_defn)
		:str_bad_enum_defn
			string Bad enum definition.
			byte 0
	:base_type_float
		*1out = TYPE_FLOAT
		out += 1
		goto base_type_done
	:base_type_void
		*1out = TYPE_VOID
		out += 1
		goto base_type_done	
	:base_type_typedef
		p = base_type + 8
		c = ident_list_lookup(typedefs, *8p)
		if c == 0 goto bad_base_type
		local len
		len = type_length(c)
		c += types
		memcpy(out, c, len)
		out += len
		goto base_type_done
	
	:bad_base_type
		token_error(base_type, .str_bad_base_type)
	:str_bad_base_type
		string Bad base type.
		byte 0

; how many bytes does it take to encode this type?
function type_length
	argument type
	local p
	local n
	p = types + type
	if *1p <= TYPE_DOUBLE goto return_1
	if *1p != TYPE_POINTER goto type_length_not_pointer
		type += 1
		n = type_length(type)
		return n + 1
	:type_length_not_pointer
	if *1p != TYPE_ARRAY goto type_length_not_array
		type += 9
		n = type_length(type)
		return n + 9
	:type_length_not_array
	if *1p == TYPE_STRUCT goto return_9
	if *1p != TYPE_FUNCTION goto type_length_not_function
		local start
		start = type
		type += 1
		:type_length_function_loop
			p = types + type
			if *1p == 0 goto type_length_function_loop_end
			type += type_length(type)
			goto type_length_function_loop
		:type_length_function_loop_end
		type += 1
		type += type_length(type)
		return type - start
	:type_length_not_function
	puts(.str_type_length_bad_type)
	exit(1)
	:str_type_length_bad_type
		string Bad type passed to type_length. This shouldn't happen.
		byte 10
		byte 0
	

; returns length of type	
function type_copy_ids
	argument dest
	argument src
	local n
	n = type_length(src)
	dest += types
	src += types
	memcpy(dest, src, n)
	return n

function type_create_copy
	argument type
	local copy
	copy = types_bytes_used
	types_bytes_used += type_copy_ids(types_bytes_used, type)
	return copy
	
function type_create_pointer
	argument type
	local id
	local p
	id = types_bytes_used
	p = types + id
	*1p = TYPE_POINTER
	types_bytes_used += 1
	p = id + 1
	types_bytes_used += type_copy_ids(p, type)
	return id

function expression_get_end
	argument expr
	local c
	c = *1expr
	if c == EXPRESSION_CONSTANT_INT goto exprend_8data
	if c == EXPRESSION_CONSTANT_FLOAT goto exprend_8data
	if c == EXPRESSION_LOCAL_VARIABLE goto exprend_8data
	if c == EXPRESSION_GLOBAL_VARIABLE goto exprend_8data
	if c == EXPRESSION_FUNCTION goto exprend_8data
	if c == EXPRESSION_SUBSCRIPT goto exprend_binary
	if c == EXPRESSION_CALL goto exprend_call
	if c == EXPRESSION_DOT goto exprend_member
	if c == EXPRESSION_ARROW goto exprend_member
	if c == EXPRESSION_POST_INCREMENT goto exprend_unary
	if c == EXPRESSION_POST_DECREMENT goto exprend_unary
	if c == EXPRESSION_PRE_INCREMENT goto exprend_unary
	if c == EXPRESSION_PRE_DECREMENT goto exprend_unary
	if c == EXPRESSION_ADDRESS_OF goto exprend_unary
	if c == EXPRESSION_DEREFERENCE goto exprend_unary
	if c == EXPRESSION_UNARY_PLUS goto exprend_unary
	if c == EXPRESSION_UNARY_MINUS goto exprend_unary
	if c == EXPRESSION_BITWISE_NOT goto exprend_unary
	if c == EXPRESSION_LOGICAL_NOT goto exprend_unary
	if c == EXPRESSION_CAST goto exprend_unary
	if c == EXPRESSION_MUL goto exprend_binary
	if c == EXPRESSION_DIV goto exprend_binary
	if c == EXPRESSION_REMAINDER goto exprend_binary
	if c == EXPRESSION_ADD goto exprend_binary
	if c == EXPRESSION_SUB goto exprend_binary
	if c == EXPRESSION_LSHIFT goto exprend_binary
	if c == EXPRESSION_RSHIFT goto exprend_binary
	if c == EXPRESSION_LT goto exprend_binary
	if c == EXPRESSION_GT goto exprend_binary
	if c == EXPRESSION_LEQ goto exprend_binary
	if c == EXPRESSION_GEQ goto exprend_binary
	if c == EXPRESSION_EQ goto exprend_binary
	if c == EXPRESSION_NEQ goto exprend_binary
	if c == EXPRESSION_BITWISE_AND goto exprend_binary
	if c == EXPRESSION_BITWISE_XOR goto exprend_binary
	if c == EXPRESSION_BITWISE_OR goto exprend_binary
	if c == EXPRESSION_LOGICAL_AND goto exprend_binary
	if c == EXPRESSION_LOGICAL_OR goto exprend_binary
	if c == EXPRESSION_CONDITIONAL goto exprend_conditional
	if c == EXPRESSION_ASSIGN goto exprend_binary
	if c == EXPRESSION_ASSIGN_ADD goto exprend_binary
	if c == EXPRESSION_ASSIGN_SUB goto exprend_binary
	if c == EXPRESSION_ASSIGN_MUL goto exprend_binary
	if c == EXPRESSION_ASSIGN_DIV goto exprend_binary
	if c == EXPRESSION_ASSIGN_REMAINDER goto exprend_binary
	if c == EXPRESSION_ASSIGN_LSHIFT goto exprend_binary
	if c == EXPRESSION_ASSIGN_RSHIFT goto exprend_binary
	if c == EXPRESSION_ASSIGN_AND goto exprend_binary
	if c == EXPRESSION_ASSIGN_XOR goto exprend_binary
	if c == EXPRESSION_ASSIGN_OR goto exprend_binary
	if c == EXPRESSION_COMMA goto exprend_binary
	
	:exprend_8data
		return expr + 16
	:exprend_unary
		expr += 8
		return expression_get_end(expr)
	:exprend_binary
		expr += 8
		expr = expression_get_end(expr)
		return expression_get_end(expr)
	:exprend_conditional
		expr += 8
		expr = expression_get_end(expr)
		expr = expression_get_end(expr)
		return expression_get_end(expr)
	:exprend_call
		expr += 8
		expr = expression_get_end(expr)
		:exprend_call_loop
			if *1expr == 0 goto exprend_call_loop_end
			expr = expression_get_end(expr)
			goto exprend_call_loop
		:exprend_call_loop_end
		return expr + 8
	:exprend_member
		expr += 8
		expr = expression_get_end(expr)
		return expr + 8
	
; returns pointer to end of expression data	
function parse_expression
	argument tokens
	argument tokens_end
	argument out
	local in
	local a
	local b
	local c
	local p
	local n
	local type
	local best
	local best_precedence
	local depth
	local value
	local first_token
	:parse_expression_top
	
	;print_tokens(tokens, tokens_end)
	
	type = out + 4
	
	if tokens == tokens_end goto empty_expression
	p = tokens + 16
	if p == tokens_end goto single_token_expression
	if *1tokens != SYMBOL_LPAREN goto parse_expression_not_entirely_in_parens
	p = tokens_end - 16
	if *1p != SYMBOL_RPAREN goto parse_expression_not_entirely_in_parens
	
	depth = 1 ; bracket depth
	p = tokens + 16
	a = tokens_end - 16 ; stop point
	:expr_paren_check_loop
		if p >= a goto expr_paren_check_loop_end
		c = *1p
		p += 16
		if c == SYMBOL_LPAREN goto expr_paren_check_loop_incdepth
		if c == SYMBOL_RPAREN goto expr_paren_check_loop_decdepth
		goto expr_paren_check_loop
		:expr_paren_check_loop_incdepth
			depth += 1
			goto expr_paren_check_loop
		:expr_paren_check_loop_decdepth
			depth -= 1
			if depth == 0 goto parse_expression_not_entirely_in_parens
			goto expr_paren_check_loop
	:expr_paren_check_loop_end
	
	; if we made it this far, the expression is entirely in parenthesis, e.g. (x+2)
	tokens += 16
	tokens_end -= 16
	goto parse_expression_top
	
	:parse_expression_not_entirely_in_parens
	
	; look for the operator with the lowest precedence not in brackets
	depth = 0 ; paren/square bracket depth
	p = tokens
	best = 0
	best_precedence = 1000
	:expr_find_operator_loop
		if p >= tokens_end goto expr_find_operator_loop_end
		n = p
		c = *1p
		p += 16
		if depth > 0 goto expr_findop_not_new_best
		if depth < 0 goto expr_too_many_closing_brackets
		a = operator_precedence(n, tokens)
		n = a
		if a == 0xe0 goto select_leftmost ; ensure that the leftmost unary operator is processed first
		b = operator_right_associative(c)
		if b != 0 goto select_leftmost ; ensure that the leftmost += / -= / etc. is processed first
		goto select_rightmost
		:select_leftmost
		n += 1
		; fallthrough
		:select_rightmost
		if n > best_precedence goto expr_findop_not_new_best
		; new best!
		best = p - 16
		;putc('O)
		;putc(':)
		;putn(*1best)
		;putc(32)
		;putc('P)
		;putc(':)
		;putnln(a)
		best_precedence = a
		:expr_findop_not_new_best
		if c == SYMBOL_LPAREN goto expr_findop_incdepth
		if c == SYMBOL_RPAREN goto expr_findop_decdepth
		if c == SYMBOL_LSQUARE goto expr_findop_incdepth
		if c == SYMBOL_RSQUARE goto expr_findop_decdepth
		goto expr_find_operator_loop
		
		:expr_findop_incdepth
			depth += 1
			goto expr_find_operator_loop
		:expr_findop_decdepth
			depth -= 1
			goto expr_find_operator_loop
	:expr_find_operator_loop_end
	
	if best == 0 goto unrecognized_expression
	
	n = best - tokens
	
	c = *1best
		
	if best == tokens goto parse_expr_unary
	
	; it's a binary expression.
	if c == SYMBOL_PLUS_PLUS goto parse_postincrement
	if c == SYMBOL_MINUS_MINUS goto parse_postdecrement
	if c == SYMBOL_QUESTION goto parse_conditional
	*1out = binop_symbol_to_expression_type(c)
	c = *1out
	out += 8
	if c == EXPRESSION_DOT goto parse_expr_member
	if c == EXPRESSION_ARROW goto parse_expr_member
	a = out + 4 ; type of first operand
	out = parse_expression(tokens, best, out) ; first operand
	a = *4a
	p = best + 16
	if c == EXPRESSION_CALL goto parse_call
	if c != EXPRESSION_SUBSCRIPT goto binary_not_subscript
	tokens_end -= 16
	if *1tokens_end != SYMBOL_RSQUARE goto unrecognized_expression
	:binary_not_subscript
	
	b = out + 4 ; type of second operand
	out = parse_expression(p, tokens_end, out) ; second operand
	b = *4b
	
	if c == EXPRESSION_LSHIFT goto type_shift
	if c == EXPRESSION_RSHIFT goto type_shift
	if c == EXPRESSION_SUBSCRIPT goto type_subscript
	if c == EXPRESSION_EQ goto type_int
	if c == EXPRESSION_NEQ goto type_int
	if c == EXPRESSION_LEQ goto type_int
	if c == EXPRESSION_GEQ goto type_int
	if c == EXPRESSION_LT goto type_int
	if c == EXPRESSION_GT goto type_int
	if c == EXPRESSION_COMMA goto type_binary_right
	if c == EXPRESSION_ASSIGN goto type_binary_left
	if c == EXPRESSION_ASSIGN_ADD goto type_binary_left
	if c == EXPRESSION_ASSIGN_SUB goto type_binary_left
	if c == EXPRESSION_ASSIGN_MUL goto type_binary_left
	if c == EXPRESSION_ASSIGN_DIV goto type_binary_left
	if c == EXPRESSION_ASSIGN_REMAINDER goto type_binary_left
	if c == EXPRESSION_ASSIGN_AND goto type_binary_left_integer
	if c == EXPRESSION_ASSIGN_XOR goto type_binary_left_integer
	if c == EXPRESSION_ASSIGN_OR goto type_binary_left_integer
	if c == EXPRESSION_ASSIGN_LSHIFT goto type_binary_left_integer
	if c == EXPRESSION_ASSIGN_RSHIFT goto type_binary_left_integer
	if c == EXPRESSION_LOGICAL_OR goto type_int
	if c == EXPRESSION_LOGICAL_AND goto type_int
	if c == EXPRESSION_BITWISE_AND goto type_binary_usual_integer
	if c == EXPRESSION_BITWISE_XOR goto type_binary_usual_integer
	if c == EXPRESSION_BITWISE_OR goto type_binary_usual_integer
	if c == EXPRESSION_ADD goto type_plus
	if c == EXPRESSION_SUB goto type_minus
	if c == EXPRESSION_MUL goto type_binary_usual
	if c == EXPRESSION_DIV goto type_binary_usual
	if c == EXPRESSION_REMAINDER goto type_binary_usual_integer
	
	puts(.str_binop_this_shouldnt_happen)
	exit(1)
	:str_binop_this_shouldnt_happen
		string Bad binop symbol (this shouldn't happen).
		byte 10
		byte 0	
	
	:type_plus
		type_decay_array_to_pointer_in_place(a)
		type_decay_array_to_pointer_in_place(b)
		p = types + a
		if *1p == TYPE_POINTER goto type_binary_left ; pointer plus integer
		p = types + b
		if *1p == TYPE_POINTER goto type_binary_right ; integer plus pointer
		goto type_binary_usual
	:type_minus
		type_decay_array_to_pointer_in_place(a)
		type_decay_array_to_pointer_in_place(b)
		p = types + a
		if *1p == TYPE_POINTER goto type_minus_left_ptr
		goto type_binary_usual
		:type_minus_left_ptr
		p = types + b
		if *1p == TYPE_POINTER goto type_long ; pointer difference
		goto type_binary_left ; pointer minus integer
	:type_subscript
		; @NONSTANDARD: technically 1["hello"] is legal. but why
		type_decay_array_to_pointer_in_place(a)
		p = types + b
		if *1p > TYPE_UNSIGNED_LONG goto subscript_non_integer
		p = types + a
		if *1p == TYPE_POINTER goto type_subscript_pointer
		goto subscript_bad_type
		:type_subscript_pointer
			b = a + 1
			*4type = type_create_copy(b)
			return out
		:subscript_bad_type
			token_error(tokens, .str_subscript_bad_type)
		:str_subscript_bad_type
			string Subscript of non-pointer type.
			byte 0
		:subscript_non_integer
			token_error(tokens, .str_subscript_non_integer)
		:str_subscript_non_integer
			string Subscript index is not an integer.
			byte 0
	; apply the "usual conversions"
	:type_binary_usual
		*4type = expr_binary_type_usual_conversions(tokens, a, b)
		return out
	; like type_binary_usual, but the operands must be integers
	:type_binary_usual_integer
		*4type = expr_binary_type_usual_conversions(tokens, a, b)
		p = types + *4type
		if *1p >= TYPE_FLOAT goto expr_binary_bad_types
		return out
	:type_binary_left_integer
		p = types + a
		if *1p >= TYPE_FLOAT goto expr_binary_bad_types
		p = types + b
		if *1p >= TYPE_FLOAT goto expr_binary_bad_types
		goto type_binary_left
	:type_binary_left
		*4type = a
		return out
	:type_binary_right
		*4type = b
		return out
	:type_shift
		p = types + a
		if *1p >= TYPE_FLOAT goto expr_binary_bad_types
		p = types + b
		if *1p >= TYPE_FLOAT goto expr_binary_bad_types
		*4type = type_promotion(a)
		return out
	; the type here is just int
	:type_int
		*4type = TYPE_INT
		return out
	:type_long
		*4type = TYPE_LONG
		return out
	:expr_binary_bad_types
		bad_types_to_operator(tokens, a, b)
	
	:parse_call
		local arg_type
		local param_type
		; type call
		b = types + a
		if *1b == TYPE_FUNCTION goto type_call_cont
		if *1b != TYPE_POINTER goto calling_nonfunction
		b += 1 ; handle calling function pointer
		if *1b != TYPE_FUNCTION goto calling_nonfunction
		:type_call_cont
		b -= types
		*4type = functype_return_type(b)
		param_type = b + 1
		
		:call_args_loop
			if *1p == SYMBOL_RPAREN goto call_args_loop_end
			n = token_next_semicolon_comma_rbracket(p)
			*1out = EXPRESSION_CAST ; generate cast to proper argument type
			arg_type = out + 4
			out += 8
			b = out + 4
			out = parse_expression(p, n, out)
			*4arg_type = type_create_copy(*4b)
			b = types + param_type
			if *1b == 0 goto arg_is_varargs ; reached the end of arguments (so presumably this function has varargs)
			; set argument type to parameter type. this is necessary because:
			;  float f(float t) { return 2*t; } 
			;  float g(int x) { return f(x); } <- x passed as a float
			*4arg_type = param_type
			param_type += type_length(param_type)
			goto call_arg_type_cont
			:arg_is_varargs
			type_promote_float_to_double(*4arg_type)
			type_decay_array_to_pointer_in_place(*4arg_type)
			:call_arg_type_cont
			
			p = n
			if *1p == SYMBOL_RPAREN goto call_args_loop_end
			if *1p != SYMBOL_COMMA goto bad_call
			p += 16
			goto call_args_loop
		:call_args_loop_end
		p += 16
		if p != tokens_end goto stuff_after_call
		
		
		*8out = 0
		out += 8
		return out
		:calling_nonfunction
			token_error(p, .str_calling_nonfunction)
		:str_calling_nonfunction
			string Calling non-function.
			byte 0
		:bad_call
			token_error(p, .str_bad_call)
		:str_bad_call
			string Bad function call.
			byte 0
		:stuff_after_call
			token_error(p, .str_stuff_after_call)
		:str_stuff_after_call
			string Unexpected stuff after function call.
			byte 0
	:parse_expr_unary
		if c == KEYWORD_SIZEOF goto parse_sizeof
		*1out = unary_op_to_expression_type(c)
		c = *1out
		if c == EXPRESSION_CAST goto parse_cast
		out += 8
		a = out + 4 ; type of operand
		p = tokens + 16
		out = parse_expression(p, tokens_end, out)
		a = *4a
		if c == EXPRESSION_BITWISE_NOT goto unary_type_integral
		if c == EXPRESSION_UNARY_PLUS goto unary_type_promote
		if c == EXPRESSION_UNARY_MINUS goto unary_type_promote
		if c == EXPRESSION_LOGICAL_NOT goto unary_type_logical_not
		if c == EXPRESSION_ADDRESS_OF goto unary_address_of
		if c == EXPRESSION_DEREFERENCE goto unary_dereference
		if c == EXPRESSION_PRE_INCREMENT goto unary_type_scalar_nopromote
		if c == EXPRESSION_PRE_DECREMENT goto unary_type_scalar_nopromote
		puts(.str_unop_this_shouldnt_happen)
		exit(1)
		:str_unop_this_shouldnt_happen
			string Bad unary symbol (this shouldn't happen).
			byte 10
			byte 0
	:parse_cast
		local cast_base_type
		local cast_prefix
		local cast_suffix
		local cast_suffix_end
		
		cast_base_type = best + 16
		cast_prefix = type_get_base_end(cast_base_type)
		cast_suffix = type_get_prefix_end(cast_prefix)
		cast_suffix_end = type_get_suffix_end(cast_prefix)
		
		a = types_bytes_used
		
		parse_type_declarators(cast_prefix, cast_suffix, cast_suffix, cast_suffix_end, 0)
		parse_base_type(cast_base_type)
		
		p = cast_suffix_end
		
		if *1p != SYMBOL_RPAREN goto bad_cast ; e.g. (int ,)5
		out += 4
		*4out = a
		out += 4
		p += 16
		a = out + 4 ; pointer to casted expression type
		out = parse_expression(p, tokens_end, out)
		type_decay_array_to_pointer_in_place(*4a)
		return out
		:bad_cast
			token_error(tokens, .str_bad_cast)
		:str_bad_cast
			string Bad cast.
			byte 0		
	:unary_address_of
		*4type = type_create_pointer(a)
		return out
	:unary_dereference
		type_decay_array_to_pointer_in_place(a)
		p = types + a
		if *2p == TYPE2_FUNCTION_POINTER goto type_deref_fpointer
		if *1p != TYPE_POINTER goto unary_bad_type
		b = a + 1
		*4type = type_create_copy(b)
		return out
		:type_deref_fpointer
		*4type = a
		return out
	:unary_type_logical_not
		type_decay_array_to_pointer_in_place(a)
		p = types + a
		if *1p > TYPE_POINTER goto unary_bad_type
		*4type = TYPE_INT
		return out
	:unary_type_integral
		p = types + a
		if *1p >= TYPE_FLOAT goto unary_bad_type
		goto unary_type_promote
	:unary_type_promote
		p = types + a
		if *1p > TYPE_DOUBLE goto unary_bad_type
		*4type = type_promotion(a)
		return out
	:unary_type_scalar_nopromote
		p = types + a
		if *1p > TYPE_POINTER goto unary_bad_type
		*4type = a
		return out
	:unary_bad_type
		print_token_location(tokens)
		puts(.str_unary_bad_type)
		print_type(a)
		putc(10)
		exit(1)
	:str_unary_bad_type
		string : Bad type for unary operator:
		byte 32
		byte 0
	
	:parse_sizeof
		local sizeof_base_type
		local sizeof_prefix
		local sizeof_suffix
		local sizeof_suffix_end
		
		*1out = EXPRESSION_CONSTANT_INT
		out += 4
		*1out = TYPE_UNSIGNED_LONG
		out += 4
		p = best + 16
		if *1p != SYMBOL_LPAREN goto parse_sizeof_expr
		p += 16
		b = token_is_type(p)
		if b == 0 goto parse_sizeof_expr
			; it's a type, e.g. sizeof(int)
			sizeof_base_type = p
			sizeof_prefix = type_get_base_end(sizeof_base_type)
			sizeof_suffix = type_get_prefix_end(sizeof_prefix)
			sizeof_suffix_end = type_get_suffix_end(sizeof_prefix)
			p = sizeof_suffix_end
			a = types_bytes_used
			parse_type_declarators(sizeof_prefix, sizeof_suffix, sizeof_suffix, sizeof_suffix_end, 0)
			parse_base_type(sizeof_base_type)
			if *1p != SYMBOL_RPAREN goto bad_expression ; e.g. sizeof(int ,)
			p += 16
			if p != tokens_end goto stuff_after_sizeof_type
			*8out = type_sizeof(a)
			goto parse_sizeof_finish
			:stuff_after_sizeof_type
				token_error(sizeof_suffix_end, .str_stuff_after_sizeof_type)
			:str_stuff_after_sizeof_type
				string Unrecognized stuff after sizeof(T).
				byte 0
		:parse_sizeof_expr
			; it's an expression, e.g. sizeof(x+3)
			local temp
			temp = malloc(4000)
			p = best + 16
			parse_expression(p, tokens_end, temp)
			p = temp + 4
			*8out = type_sizeof(*4p)
			free(temp)
		:parse_sizeof_finish
		out += 8
		return out
		
	:parse_expr_member ; -> or .
		p = best + 16
		if *1p != TOKEN_IDENTIFIER goto bad_expression
		
		a = out + 4 ; pointer to type ID
		out = parse_expression(tokens, best, out)
		a = types + *4a
		if c == EXPRESSION_DOT goto type_dot
			if *1a != TYPE_POINTER goto arrow_non_pointer
			a += 1
		:type_dot
		if *1a != TYPE_STRUCT goto member_non_struct
		a += 1
		a = *8a ; pointer to struct data
		if *1a == 0 goto use_of_incomplete_struct
		p += 8
		c = ident_list_lookup(a, *8p)
		if c == 0 goto member_not_in_struct
		
		*4type = c > 32 ; type
		*4type = type_create_copy(*4type)
		*4out = c & 0xffffffff ; offset
		out += 4
		*4out = type_is_array(*4type)
		out += 4
		p += 8
		
		if p != tokens_end goto bad_expression ; e.g. foo->bar hello
		return out
		:use_of_incomplete_struct
			token_error(p, .str_use_of_incomplete_struct)
		:str_use_of_incomplete_struct
			string Use of incomplete struct.
			byte 0
		:arrow_non_pointer
			token_error(p, .str_arrow_non_pointer)
		:str_arrow_non_pointer
			string Trying to use -> operator on a non-pointer type.
			byte 0
		:member_non_struct
			token_error(p, .str_member_non_struct)
		:str_member_non_struct
			string Trying to access member of something other than a (complete) structure/union.
			byte 0
		:member_not_in_struct
			p -= 8
			token_error(p, .str_member_not_in_struct)
		:str_member_not_in_struct
			string Trying to access non-existent member of structure or union.
			byte 0
	:parse_conditional
		depth = 0 ; bracket depth
		n = 0 ; ? : depth
		; find : associated with this ?
		p = best + 16
		:parse_conditional_loop
			if p >= tokens_end goto bad_expression
			if *1p == SYMBOL_QUESTION goto parse_cond_incn
			if *1p == SYMBOL_COLON goto parse_cond_decn
			if *1p == SYMBOL_LPAREN goto parse_cond_incdepth
			if *1p == SYMBOL_RPAREN goto parse_cond_decdepth
			if *1p == SYMBOL_LSQUARE goto parse_cond_incdepth
			if *1p == SYMBOL_RSQUARE goto parse_cond_decdepth
			:parse_cond_cont
			p += 16
			goto parse_conditional_loop
			
			:parse_cond_incdepth
				depth += 1
				goto parse_cond_cont
			:parse_cond_decdepth
				depth -= 1
				goto parse_cond_cont
			:parse_cond_incn
				n += 1
				goto parse_cond_cont
			:parse_cond_decn
				n -= 1
				if n >= 0 goto parse_cond_cont
				if depth > 0 goto parse_cond_cont
		; okay, q now points to the :
		*1out = EXPRESSION_CONDITIONAL
		out += 8
		a = out + 4
		out = parse_expression(tokens, best, out)
		type_decay_array_to_pointer_in_place(*4a)
		
		; check type of "condition"
		b = types + *4a
		if *1b > TYPE_POINTER goto bad_condition_type
		
		a = out + 4 ; type of left branch of conditional
		best += 16
		out = parse_expression(best, p, out)
		type_decay_array_to_pointer_in_place(*4a)
		b = out + 4 ; type of right branch of conditional
		p += 16
		out = parse_expression(p, tokens_end, out)
		type_decay_array_to_pointer_in_place(*4b)
		p = types + *4a
		if *1p == TYPE_STRUCT goto parse_cond_ltype
		if *1p == TYPE_VOID goto parse_cond_ltype
		if *1p == TYPE_POINTER goto parse_cond_ltype ; @NONSTANDARD: we don't handle  sizeof *(0 ? (void*)0 : "hello")  correctly--it should be 1 (a standard-compliant implementation is annoyingly complicated)
		*4type = expr_binary_type_usual_conversions(tokens, *4a, *4b)
		return out
		:parse_cond_ltype
		; no conversions
		*4type = *4a
		return out
		:bad_condition_type
			token_error(tokens, .str_bad_condition_type)
		:str_bad_condition_type
			string Bad condition type for conditional operator (? :).
			byte 0
	:parse_postincrement
		*1out = EXPRESSION_POST_INCREMENT
		p = tokens_end - 16
		if *1p != SYMBOL_PLUS_PLUS goto bad_expression ; e.g. a ++ b
		out += 8
		a = out + 4 ; type of operand 
		out = parse_expression(tokens, p, out)
		*4type = *4a ; this expression's type is the operand's type (yes even for types smaller than int)
		return out
		
	:parse_postdecrement
		*1out = EXPRESSION_POST_DECREMENT
		p = tokens_end - 16
		if *1p != SYMBOL_MINUS_MINUS goto bad_expression ; e.g. a -- b
		out += 8
		a = out + 4 ; type of operand 
		out = parse_expression(tokens, p, out)
		*4type = *4a ; type of this = type of operand
		return out
	
	:single_token_expression
		in = tokens
		c = *1in
		if c == TOKEN_CONSTANT_INT goto expression_integer
		if c == TOKEN_CONSTANT_CHAR goto expression_integer ; character constants are basically the same as integer constants
		if c == TOKEN_CONSTANT_FLOAT goto expression_float
		if c == TOKEN_STRING_LITERAL goto expression_string_literal
		if c == TOKEN_IDENTIFIER goto expression_identifier
		goto unrecognized_expression
		
	:expression_identifier
		in += 8
		a = *8in
		in += 8
		; check if it's an enumerator
		c = ident_list_lookup_check(enumerators, a, &n)
		if c == 0 goto not_enumerator
			; it is an enumerator
			*1out = EXPRESSION_CONSTANT_INT
			out += 4
			*4out = TYPE_INT
			out += 4
			*8out = n
			out += 8
			return out
		:not_enumerator
		
		n = block_depth
		:var_lookup_loop
			; check if it's a block static variable
			p = block_static_variables
			p += n < 3
			c = ident_list_lookup(*8p, a)
			if c != 0 goto found_global_variable
			p = local_variables
			p += n < 3
			c = ident_list_lookup(*8p, a)
			if c != 0 goto found_local_variable
			n -= 1
			if n >= 0 goto var_lookup_loop
		
		; check if it's a global
		c = ident_list_lookup(global_variables, a)
		if c == 0 goto not_global
		:found_global_variable
			; it is a global variable
			*1out = EXPRESSION_GLOBAL_VARIABLE
			out += 4
			a = c > 32 ; extract type
			*4out = type_create_copy(a)
			out += 4
			*4out = c & 0xffffffff ; extract address
			out += 4
			*4out = type_is_array(a)
			out += 4
			return out
		:not_global
		
		; it must be a function
		c = ident_list_lookup(function_types, a)
		if c == 0 goto undeclared_variable
		*1out = EXPRESSION_FUNCTION
		out += 4
		*4out = type_create_pointer(c)
		out += 4
		*8out = a
		out += 8
		return out
		:undeclared_variable
			; @NONSTANDARD: C89 allows calling functions without declaring them
			print_token_location(in)
			puts(.str_undeclared_variable)
			putsln(a)
			exit(1)
		:str_undeclared_variable
			string : Undeclared variable:
			byte 32
			byte 0
		
		:found_local_variable
			; it's a local variable
			*1out = EXPRESSION_LOCAL_VARIABLE
			out += 4
			a = c > 32 ; extract type
			*4out = type_create_copy(a)
			out += 4
			c &= 0xffffffff ; extract rbp offset
			*4out = c
			out += 4
			*4out = type_is_array(a)
			out += 4
			return out
	:expression_integer
		*1out = EXPRESSION_CONSTANT_INT
		p = in + 8
		value = *8p
		p = out + 8
		*8p = value
		
		p = in + 1
		a = int_suffix_to_type(*1p) ; what the suffix says the type should be
		b = int_value_to_type(value) ; what the value says the type should be (if the value is too large to fit in int)
		a = max_signed(a, b) ; take the maximum of the two types
		; make sure that if the integer has a u suffix, the type will be unsigned
		a &= b | 0xfe
		p = out + 4
		*4p = a
		in += 16
		out += 16
		return out
	
	:expression_float
		*1out = EXPRESSION_CONSTANT_FLOAT
		p = in + 8
		value = *8p
		p = out + 8
		*8p = value
		
		p = in + 1
		a = float_suffix_to_type(*1p)
		
		p = out + 4
		*4p = a
		
		in += 16
		out += 16
		return out
		
	:expression_string_literal
		*1out = EXPRESSION_CONSTANT_INT
		p = in + 8
		value = *8p
		p = out + 8
		*8p = value
		
		; the type of this is array of n chars, where n = strlen(s)+1
		type = types + types_bytes_used
		*1type = TYPE_ARRAY
		type += 1
		p = output_file_data + value
		*8type = strlen(p)
		*8type += 1
		type += 8
		*1type = TYPE_CHAR
		
		p = out + 4
		*4p = types_bytes_used
		types_bytes_used += 10 ; TYPE_ARRAY + length + TYPE_CHAR
		
		in += 16
		out += 16
		return out
	
	
	:empty_expression
		token_error(tokens, .str_empty_expression)
	:str_empty_expression
		string Empty expression.
		byte 0
	:bad_expression
		token_error(tokens, .str_bad_expression)
	:str_bad_expression
		string Bad expression.
		byte 0
	:unrecognized_expression
		token_error(tokens, .str_unrecognized_expression)
	:str_unrecognized_expression
		string Unrecognized expression.
		byte 0
	:expr_too_many_closing_brackets
		token_error(tokens, .str_too_many_closing_brackets)
	:str_too_many_closing_brackets
		string Too many closing brackets.
		byte 0
:return_type_int
	return TYPE_INT
:return_type_long
	return TYPE_LONG
:return_type_unsigned_int
	return TYPE_UNSIGNED_INT
:return_type_unsigned_long
	return TYPE_UNSIGNED_LONG
:return_type_float
	return TYPE_FLOAT
:return_type_double
	return TYPE_DOUBLE

; if type is an array type, turn it into a pointer.
;   e.g.
;    char s[] = "hello";
;    char *t = s + 3; /* s "decays" into a pointer */
function type_decay_array_to_pointer_in_place
	argument type
	local dest
	local src
	src = types + type
	if *1src != TYPE_ARRAY goto return_0
	dest = types + type
	*1dest = TYPE_POINTER
	src = type + 9 ; skip TYPE_ARRAY and size
	dest = type + 1 ; skip TYPE_POINTER
	type_copy_ids(dest, src)
	return
	

; change type to `double` if it's `float`
; in C, float arguments have to be passed as double for varargs
;  there is also a rule that char/short/int are passed as ints, but we don't need to worry about it since we're passing everything as >=8 bytes.
function type_promote_float_to_double
	argument type
	local p
	p = types + type
	if *1p != TYPE_FLOAT goto return_0
	*1p = TYPE_DOUBLE
	return

function type_sizeof
	argument type
	local p
	local c
	p = types + type
	c = *1p
	if c == TYPE_CHAR goto return_1
	if c == TYPE_UNSIGNED_CHAR goto return_1
	if c == TYPE_SHORT goto return_2
	if c == TYPE_UNSIGNED_SHORT goto return_2
	if c == TYPE_INT goto return_4
	if c == TYPE_UNSIGNED_INT goto return_4
	if c == TYPE_LONG goto return_8
	if c == TYPE_UNSIGNED_LONG goto return_8
	if c == TYPE_FLOAT goto return_4
	if c == TYPE_DOUBLE goto return_8
	; void has a size of 1 for good reasons:
	;   - void pointer addition isn't standard, but it makes most sense to treat it the same as char* addition
	;   - code generation reasons
	if c == TYPE_VOID goto return_1
	if c == TYPE_POINTER goto return_8
	if c == TYPE_FUNCTION goto return_1
	if c == TYPE_ARRAY goto sizeof_array
	if c == TYPE_STRUCT goto sizeof_struct
	
	puts(.str_sizeof_bad)
	exit(1)
	:str_sizeof_bad
		string type_sizeof bad type.
		byte 10
		byte 0
	
	:sizeof_array
		local n
		p += 1
		n = *8p
		p += 8
		p -= types
		c = type_sizeof(p)
		return n * c

	:sizeof_struct
		; size of struct is  offset of last member + size of last member,
		;  rounded up to fit alignment
		local align
		local offset
		local member
		align = type_alignof(type)
		p += 1
		member = *8p
		:sizeof_struct_loop
			if *1member == 0 goto sizeof_struct_loop_end
			member = memchr(member, 0) ; don't care about name
			member += 1 ; skip null terminator
			c = *8member
			member += 8
			offset = c & 0xffffffff
			c >= 32 ; extract type
			offset += type_sizeof(c)
			goto sizeof_struct_loop
		:sizeof_struct_loop_end
		
		offset += align - 1
		offset /= align
		offset *= align
		
		return offset

function type_alignof
	argument type
	local p
	local c
	p = types + type
	c = *1p
	if c == TYPE_CHAR goto return_1
	if c == TYPE_UNSIGNED_CHAR goto return_1
	if c == TYPE_SHORT goto return_2
	if c == TYPE_UNSIGNED_SHORT goto return_2
	if c == TYPE_INT goto return_4
	if c == TYPE_UNSIGNED_INT goto return_4
	if c == TYPE_LONG goto return_8
	if c == TYPE_UNSIGNED_LONG goto return_8
	if c == TYPE_FLOAT goto return_4
	if c == TYPE_DOUBLE goto return_8
	if c == TYPE_VOID goto return_1
	if c == TYPE_POINTER goto return_8
	if c == TYPE_FUNCTION goto return_8
	if c == TYPE_ARRAY goto alignof_array
	if c == TYPE_STRUCT goto alignof_struct
	
	puts(.str_alignof_bad)
	exit(1)
	:str_alignof_bad
		string type_alignof bad type.
		byte 10
		byte 0
	:alignof_struct
		; alignment of struct is max alignment of members
		local align
		local member
		local a
		align = 1
		p += 1
		member = *8p
		:alignof_struct_loop
			if *1member == 0 goto alignof_struct_loop_end
			member = memchr(member, 0) ; don't care about name
			member += 1 ; skip null terminator
			c = *8member
			member += 8
			c >= 32 ; ignore offset
			a = type_alignof(c)
			if a <= align goto alignof_struct_loop
			align = a
			goto alignof_struct_loop
		:alignof_struct_loop_end
		return align
	:alignof_array
		p = type + 9 ; skip TYPE_ARRAY and size
		return type_alignof(p)

; evaluate an expression which can be the size of an array, e.g.
;    enum { A, B, C };
;    int x[A * sizeof(float) + 3 << 5];
; @NONSTANDARD: only allows double-precision floating-point literals or 0; otherwise floats aren't allowed in constant expressions.
;                     this means you can't do
;                 e.g. float x[] = {1,2,3};  or   double x[] = {1.5+2.3, 5.5*6.4};
; this is also used for #if evaluation
; token is used for error messages (e.g. if this "constant" expression is *x or something)
; NOTE: this returns the end of the expression, not the value (which is stored in *8p_value)
function evaluate_constant_expression
	argument token
	argument expr
	argument p_value
	local a
	local b
	local c
	local p
	local mask
	local type
	
	type = expr + 4
	type = *4type
	
	c = *1expr
	
	if c == EXPRESSION_CONSTANT_INT goto eval_constant_int
	p = types + type
	if *1p == TYPE_FLOAT goto bad_constexpr
	if c == EXPRESSION_CONSTANT_FLOAT goto eval_constant_float
	if c == EXPRESSION_UNARY_PLUS goto eval_unary_plus
	if c == EXPRESSION_UNARY_MINUS goto eval_unary_minus
	
	; only 0 and floating-point constants are supported as double initializers
	if *1p == TYPE_DOUBLE goto bad_constexpr
	
	if c == EXPRESSION_BITWISE_NOT goto eval_bitwise_not
	if c == EXPRESSION_LOGICAL_NOT goto eval_logical_not
	if c == EXPRESSION_CAST goto eval_cast
	if c == EXPRESSION_ADD goto eval_add
	if c == EXPRESSION_SUB goto eval_sub
	if c == EXPRESSION_MUL goto eval_mul
	if c == EXPRESSION_DIV goto eval_div
	if c == EXPRESSION_REMAINDER goto eval_remainder
	if c == EXPRESSION_LSHIFT goto eval_lshift
	if c == EXPRESSION_RSHIFT goto eval_rshift
	if c == EXPRESSION_EQ goto eval_eq
	if c == EXPRESSION_NEQ goto eval_neq
	if c == EXPRESSION_LT goto eval_lt
	if c == EXPRESSION_GT goto eval_gt
	if c == EXPRESSION_LEQ goto eval_leq
	if c == EXPRESSION_GEQ goto eval_geq
	if c == EXPRESSION_BITWISE_AND goto eval_bitwise_and
	if c == EXPRESSION_BITWISE_OR goto eval_bitwise_or
	if c == EXPRESSION_BITWISE_XOR goto eval_bitwise_xor
	if c == EXPRESSION_LOGICAL_AND goto eval_logical_and
	if c == EXPRESSION_LOGICAL_OR goto eval_logical_or
	if c == EXPRESSION_CONDITIONAL goto eval_conditional
	
	:bad_constexpr
	token_error(token, .str_eval_bad_exprtype)
	
	:str_eval_bad_exprtype
		string Can't evaluate constant expression.
		byte 0
	:eval_cast
		p = types + type
		c = *1p
		if c == TYPE_VOID goto eval_cast_bad_type
		if c == TYPE_FLOAT goto eval_cast_bad_type
		if c == TYPE_DOUBLE goto eval_cast_bad_type
		if c > TYPE_POINTER goto eval_cast_bad_type
		expr += 8
		expr = evaluate_constant_expression(token, expr, p_value)
		goto eval_fit_to_type
		:eval_cast_bad_type
			token_error(token, .str_eval_cast_bad_type)
		:str_eval_cast_bad_type
			string Bad type for constant cast (note: floating-point casts are not supported even though they are standard).
			byte 0
	:eval_constant_float
		expr += 8
		*8p_value = *8expr
		expr += 8
		return expr
	:eval_constant_int
		expr += 8
		*8p_value = *8expr
		expr += 8
		return expr
	:eval_unary_plus
		expr += 8
		expr = evaluate_constant_expression(token, expr, p_value)
		return expr
	:eval_unary_minus
		expr += 8
		expr = evaluate_constant_expression(token, expr, &a)
		p = types + type
		if *1p == TYPE_DOUBLE goto eval_minus_double
		*8p_value = 0 - a
		goto eval_fit_to_type
		:eval_minus_double
			*8p_value = 0x8000000000000000 ^ a
			return expr
	:eval_bitwise_not
		expr += 8
		expr = evaluate_constant_expression(token, expr, &a)
		*8p_value = ~a
		goto eval_fit_to_type
	:eval_logical_not
		expr += 8
		expr = evaluate_constant_expression(token, expr, &a)
		if a == 0 goto eval_value_1
		goto eval_value_0
	:eval_add
		expr += 8
		expr = evaluate_constant_expression(token, expr, &a)
		expr = evaluate_constant_expression(token, expr, &b)
		*8p_value = a + b
		goto eval_fit_to_type
	:eval_sub
		expr += 8
		expr = evaluate_constant_expression(token, expr, &a)
		expr = evaluate_constant_expression(token, expr, &b)
		*8p_value = a - b
		goto eval_fit_to_type
	:eval_mul
		expr += 8
		expr = evaluate_constant_expression(token, expr, &a)
		expr = evaluate_constant_expression(token, expr, &b)
		*8p_value = a * b
		goto eval_fit_to_type
	:eval_div
		expr += 8
		expr = evaluate_constant_expression(token, expr, &a)
		expr = evaluate_constant_expression(token, expr, &b)
		p = types + type
		if *1p == TYPE_UNSIGNED_LONG goto eval_div_unsigned
			; division is signed or uses a small type, so we can use 64-bit signed division
			*8p_value = a / b
			goto eval_fit_to_type
		:eval_div_unsigned
			; must use unsigned division
			divmod_unsigned(a, b, p_value, &a)
			goto eval_fit_to_type
	:eval_remainder
		expr += 8
		expr = evaluate_constant_expression(token, expr, &a)
		expr = evaluate_constant_expression(token, expr, &b)
		p = types + type
		if *1p == TYPE_UNSIGNED_LONG goto eval_rem_unsigned
			*8p_value = a % b
			goto eval_fit_to_type
		:eval_rem_unsigned
			divmod_unsigned(a, b, &a, p_value)
			goto eval_fit_to_type
	:eval_lshift
		expr += 8
		expr = evaluate_constant_expression(token, expr, &a)
		expr = evaluate_constant_expression(token, expr, &b)
		*8p_value = a < b
		goto eval_fit_to_type
	:eval_rshift
		expr += 8
		expr = evaluate_constant_expression(token, expr, &a)
		expr = evaluate_constant_expression(token, expr, &b)
		p = types + type
		p = *1p
		p &= 1 ; signed types are odd
		if p == 1 goto eval_signed_rshift
			*8p_value = a > b
			goto eval_fit_to_type
		:eval_signed_rshift
			local v
			mask = a > 63 ; sign bit
			
			; sign extension
			mask <= b
			mask -= 1
			mask <= 64 - b
			
			v = a > b
			v += mask
			*8p_value = v
			goto eval_fit_to_type
	
	; comparison masks:
	;   1 = less than
	;   2 = equal to
	;   4 = greater than
	; e.g. not-equal is 1|4 = 5 because not equal = less than or greater than
	:eval_eq
		mask = 2
		goto eval_comparison
	:eval_neq
		mask = 5
		goto eval_comparison
	:eval_lt
		mask = 1
		goto eval_comparison
	:eval_gt
		mask = 4
		goto eval_comparison
	:eval_leq
		mask = 3
		goto eval_comparison
	:eval_geq
		mask = 6
		goto eval_comparison
	:eval_comparison
		expr += 8
		expr = evaluate_constant_expression(token, expr, &a)
		expr = evaluate_constant_expression(token, expr, &b)
		
		p = types + type
		p = *1p
		p &= 1
		
		if a == b goto eval_comparison_eq
		
		; for checking < and >, we care about whether a and b are signed
		if p == 1 goto eval_signed_comparison
			if a ] b goto eval_comparison_gt
			goto eval_comparison_lt
		:eval_signed_comparison
			if a > b goto eval_comparison_gt
			goto eval_comparison_lt
		
		:eval_comparison_eq
			; a == b
			mask &= 2
			goto eval_comparison_done
		:eval_comparison_lt
			; a < b
			mask &= 1
			goto eval_comparison_done
		:eval_comparison_gt
			; a > b
			mask &= 4
			goto eval_comparison_done
		:eval_comparison_done
			if mask != 0 goto eval_value_1
			goto eval_value_0
	:eval_bitwise_and
		expr += 8
		expr = evaluate_constant_expression(token, expr, &a)
		expr = evaluate_constant_expression(token, expr, &b)
		*8p_value = a & b
		goto eval_fit_to_type
	:eval_bitwise_or
		expr += 8
		expr = evaluate_constant_expression(token, expr, &a)
		expr = evaluate_constant_expression(token, expr, &b)
		*8p_value = a | b
		goto eval_fit_to_type
	:eval_bitwise_xor
		expr += 8
		expr = evaluate_constant_expression(token, expr, &a)
		expr = evaluate_constant_expression(token, expr, &b)
		*8p_value = a ^ b
		goto eval_fit_to_type
	:eval_logical_and
		expr += 8
		expr = evaluate_constant_expression(token, expr, &a)
		if a == 0 goto eval_value_0
		expr = evaluate_constant_expression(token, expr, &b)
		if b == 0 goto eval_value_0
		goto eval_value_1
	:eval_logical_or
		expr += 8
		expr = evaluate_constant_expression(token, expr, &a)
		if a != 0 goto eval_value_1
		expr = evaluate_constant_expression(token, expr, &b)
		if b != 0 goto eval_value_1
		goto eval_value_0
	:eval_conditional
		expr += 8
		expr = evaluate_constant_expression(token, expr, &mask)
		expr = evaluate_constant_expression(token, expr, &a)
		expr = evaluate_constant_expression(token, expr, &b)
		if mask == 0 goto eval_conditional_b
			*8p_value = a
			goto eval_fit_to_type
		:eval_conditional_b
			*8p_value = b
			goto eval_fit_to_type
		
	:eval_fit_to_type
	*8p_value = fit_to_type(*8p_value, type)
	return expr
	:eval_value_0
	*8p_value = 0
	return expr
	:eval_value_1
	*8p_value = 1
	return expr
	

; value is the output of some arithmetic expression; correct it to be within the range of type.
function fit_to_type
	argument value
	argument type
	local c
	local s
	c = types + type
	c = *1c
	if c == TYPE_CHAR goto fit_to_type_char
	if c == TYPE_UNSIGNED_CHAR goto fit_to_type_uchar
	if c == TYPE_SHORT goto fit_to_type_short
	if c == TYPE_UNSIGNED_SHORT goto fit_to_type_ushort
	if c == TYPE_INT goto fit_to_type_int
	if c == TYPE_UNSIGNED_INT goto fit_to_type_uint
	if c == TYPE_LONG goto fit_to_type_long
	if c == TYPE_UNSIGNED_LONG goto fit_to_type_ulong
	if c == TYPE_POINTER goto fit_to_type_ulong
	puts(.str_bad_fit_to_type)
	exit(1)
	:str_bad_fit_to_type
		string Bad type passed to fit_to_type.
		byte 10
		byte 0
	; yes, signed integer overflow is undefined behavior and
	; casting to a signed integer is implementation-defined;
	;   i'm going to play it safe and implement it properly
	:fit_to_type_char
		value &= 0xff
		s = value > 7 ; sign bit
		value += s * 0xffffffffffffff00 ; sign-extend
		return value
	:fit_to_type_uchar
		value &= 0xff
		return value
	:fit_to_type_short
		value &= 0xffff
		s = value > 15 ; sign bit
		value += s * 0xffffffffffff0000 ; sign-extend
		return value
	:fit_to_type_ushort
		value &= 0xffff
		return value
	:fit_to_type_int
		value &= 0xffffffff
		s = value > 31 ; sign bit
		value += s * 0xffffffff00000000 ; sign-extend
		return value
	:fit_to_type_uint
		value &= 0xffffffff
		return value
	:fit_to_type_long
	:fit_to_type_ulong
		return value
; the "usual conversions" for binary operators, as the C standard calls it
function expr_binary_type_usual_conversions
	argument token ; for errors
	argument type1
	argument type2
	
	local ptype1
	local ptype2
	local kind1
	local kind2
	
	if type1 == 0 goto return_0
	if type2 == 0 goto return_0
	
	ptype1 = types + type1
	ptype2 = types + type2
	
	kind1 = *1ptype1
	kind2 = *1ptype2
	
	if kind1 > TYPE_DOUBLE goto usual_bad_types_to_operator
	if kind2 > TYPE_DOUBLE goto usual_bad_types_to_operator
	
	; "if either operand has type double, the other operand is converted to double"
	if kind1 == TYPE_DOUBLE goto return_type_double
	if kind2 == TYPE_DOUBLE goto return_type_double
	; "if either operand has type float, the other operand is converted to float"
	if kind1 == TYPE_FLOAT goto return_type_float
	if kind2 == TYPE_FLOAT goto return_type_float
	; "If either operand has type unsigned long int, the other operand is converted to unsigned long int"
	if kind1 == TYPE_UNSIGNED_LONG goto return_type_unsigned_long
	if kind2 == TYPE_UNSIGNED_LONG goto return_type_unsigned_long
	; "if either operand has type long int, the other operand is converted to long int"
	if kind1 == TYPE_LONG goto return_type_long
	if kind2 == TYPE_LONG goto return_type_long
	; "if either operand has type unsigned int, the other operand is converted to unsigned int."
	if kind1 == TYPE_UNSIGNED_INT goto return_type_unsigned_int
	if kind2 == TYPE_UNSIGNED_INT goto return_type_unsigned_int
	; "Otherwise, both operands have type int."
	goto return_type_int
	
	:str_space_and_space
		string  and
		byte 32
		byte 0
	:usual_bad_types_to_operator
		bad_types_to_operator(token, type1, type2)

function bad_types_to_operator
	argument token
	argument type1
	argument type2
	
	print_token_location(token)
	puts(.str_bad_types_to_operator)
	print_type(type1)
	puts(.str_space_and_space)
	print_type(type2)
	putc(10)
	exit(1)
	:str_bad_types_to_operator
		string : Bad types to operator:
		byte 32
		byte 0

function type_promotion
	argument type
	local p
	p = types + type
	if *1p < TYPE_INT goto return_type_int
	return type

; return precedence of given operator token, or 0xffff if not an operator
function operator_precedence
	argument token
	argument first_token
	local p_op
	local op
	local b
	
	if token == first_token goto operator_precedence_unary
	
	; if an operator is preceded by another, it must be a unary operator, e.g.
	;   in `5 + *x`, * is a unary operator
	p_op = token - 16
	:figure_out_arity
	op = *1p_op
	if op == SYMBOL_RPAREN goto figre_out_rparen_arity
	if op == SYMBOL_PLUS_PLUS goto figure_out_bimodal_arity
	if op == SYMBOL_MINUS_MINUS goto figure_out_bimodal_arity
	op = is_operator(op)
	
	; if an operator is immediately followed by another (including lparen), the second must be 
	; unary.
	if op != 0 goto operator_precedence_unary
	
	:operator_precedence_binary
	op = *1token
	
	; see "C OPERATOR PRECEDENCE" in constants.b
	if op == SYMBOL_COMMA goto return_0x10
	if op == SYMBOL_EQ goto return_0x20
	if op == SYMBOL_PLUS_EQ goto return_0x20
	if op == SYMBOL_MINUS_EQ goto return_0x20
	if op == SYMBOL_TIMES_EQ goto return_0x20
	if op == SYMBOL_DIV_EQ goto return_0x20
	if op == SYMBOL_PERCENT_EQ goto return_0x20
	if op == SYMBOL_LSHIFT_EQ goto return_0x20
	if op == SYMBOL_RSHIFT_EQ goto return_0x20
	if op == SYMBOL_AND_EQ goto return_0x20
	if op == SYMBOL_OR_EQ goto return_0x20
	if op == SYMBOL_XOR_EQ goto return_0x20
	if op == SYMBOL_QUESTION goto return_0x30
	if op == SYMBOL_OR_OR goto return_0x40
	if op == SYMBOL_AND_AND goto return_0x50
	if op == SYMBOL_OR goto return_0x60
	if op == SYMBOL_XOR goto return_0x70
	if op == SYMBOL_AND goto return_0x80
	if op == SYMBOL_EQ_EQ goto return_0x90
	if op == SYMBOL_NOT_EQ goto return_0x90
	if op == SYMBOL_LT goto return_0xa0
	if op == SYMBOL_GT goto return_0xa0
	if op == SYMBOL_LT_EQ goto return_0xa0
	if op == SYMBOL_GT_EQ goto return_0xa0
	if op == SYMBOL_LSHIFT goto return_0xb0
	if op == SYMBOL_RSHIFT goto return_0xb0
	if op == SYMBOL_PLUS goto return_0xc0
	if op == SYMBOL_MINUS goto return_0xc0
	if op == SYMBOL_TIMES goto return_0xd0
	if op == SYMBOL_DIV goto return_0xd0
	if op == SYMBOL_PERCENT goto return_0xd0
	if op == SYMBOL_ARROW goto return_0xf0
	if op == SYMBOL_DOT goto return_0xf0
	if op == SYMBOL_LPAREN goto return_0xf0 ; function call
	if op == SYMBOL_LSQUARE goto return_0xf0 ; subscript
	if op == SYMBOL_PLUS_PLUS goto return_0xf0
	if op == SYMBOL_MINUS_MINUS goto return_0xf0
	
	return 0xffff
	
	:operator_precedence_unary
	op = *1token
	
	
	
	if op == KEYWORD_SIZEOF goto return_0xe0
	if op == SYMBOL_PLUS_PLUS goto return_0xe0
	if op == SYMBOL_MINUS_MINUS goto return_0xe0
	if op == SYMBOL_AND goto return_0xe0
	if op == SYMBOL_TIMES goto return_0xe0
	if op == SYMBOL_PLUS goto return_0xe0
	if op == SYMBOL_MINUS goto return_0xe0
	if op == SYMBOL_TILDE goto return_0xe0
	if op == SYMBOL_NOT goto return_0xe0
	if op == SYMBOL_LPAREN goto cast_precedence
	
	return 0xffff
	:cast_precedence
	; make sure this actually is a cast
	;   this is necessary to handle both
	;         - (x)->something
	;     and - (int)x->something
	;   correctly (in the first case, the arrow is the top-level operator, but in the second, the cast is)
	token += 16
	b = token_is_type(token)
	if b == 0 goto return_0xffff
	goto return_0xe0 ; it's a cast
	
	:figure_out_bimodal_arity
	; ++ and -- can act either as unary or binary operators.
	if p_op == first_token goto operator_precedence_unary ; e.g. ++*x
	; reverse one further to figure out which it is.
	p_op -= 16
	goto figure_out_arity
	
	
	:figre_out_rparen_arity
	; given that the token before this one is a right-parenthesis, figure out if
	; this is a unary or binary operator. this is (annoyingly) necessary, because:
	;  (y)-x;     /* subtraction processed first */
	;  (int)-x;   /* cast processed first */
	;  sizeof(int)-x;  /* subtraction processed first */
	local p
	p = token - 16
	token_reverse_to_matching_lparen(&p)
	p += 16
	b = token_is_type(p)
	if b != 0 goto rparen_might_be_cast
	goto operator_precedence_binary ; e.g. (y)-x;
	:rparen_might_be_cast
		p -= 32
		if *1p != KEYWORD_SIZEOF goto operator_precedence_unary ; e.g. (int)-x
		goto operator_precedence_binary ; e.g. sizeof(int)-x
	
function unary_op_to_expression_type
	argument op
	if op == SYMBOL_PLUS_PLUS goto return_EXPRESSION_PRE_INCREMENT
	if op == SYMBOL_MINUS_MINUS goto return_EXPRESSION_PRE_DECREMENT
	if op == SYMBOL_AND goto return_EXPRESSION_ADDRESS_OF
	if op == SYMBOL_TIMES goto return_EXPRESSION_DEREFERENCE
	if op == SYMBOL_PLUS goto return_EXPRESSION_UNARY_PLUS
	if op == SYMBOL_MINUS goto return_EXPRESSION_UNARY_MINUS
	if op == SYMBOL_TILDE goto return_EXPRESSION_BITWISE_NOT
	if op == SYMBOL_NOT goto return_EXPRESSION_LOGICAL_NOT
	if op == SYMBOL_LPAREN goto return_EXPRESSION_CAST
	return 0

:return_EXPRESSION_PRE_INCREMENT
	return EXPRESSION_PRE_INCREMENT
:return_EXPRESSION_PRE_DECREMENT
	return EXPRESSION_PRE_DECREMENT
:return_EXPRESSION_ADDRESS_OF
	return EXPRESSION_ADDRESS_OF
:return_EXPRESSION_DEREFERENCE
	return EXPRESSION_DEREFERENCE
:return_EXPRESSION_UNARY_PLUS
	return EXPRESSION_UNARY_PLUS
:return_EXPRESSION_UNARY_MINUS
	return EXPRESSION_UNARY_MINUS
:return_EXPRESSION_BITWISE_NOT
	return EXPRESSION_BITWISE_NOT
:return_EXPRESSION_LOGICAL_NOT
	return EXPRESSION_LOGICAL_NOT
:return_EXPRESSION_CAST
	return EXPRESSION_CAST

; is this operator right-associative? most C operators are left associative,
; but += / -= / etc. are not
function operator_right_associative
	argument op
	if op == SYMBOL_QUESTION goto return_1
	if op < SYMBOL_EQ goto return_0
	if op > SYMBOL_OR_EQ goto return_0
	goto return_1

:binop_table
	byte SYMBOL_COMMA
	byte EXPRESSION_COMMA
	byte SYMBOL_EQ
	byte EXPRESSION_ASSIGN
	byte SYMBOL_PLUS_EQ
	byte EXPRESSION_ASSIGN_ADD
	byte SYMBOL_MINUS_EQ
	byte EXPRESSION_ASSIGN_SUB
	byte SYMBOL_TIMES_EQ
	byte EXPRESSION_ASSIGN_MUL
	byte SYMBOL_DIV_EQ
	byte EXPRESSION_ASSIGN_DIV
	byte SYMBOL_PERCENT_EQ
	byte EXPRESSION_ASSIGN_REMAINDER
	byte SYMBOL_LSHIFT_EQ
	byte EXPRESSION_ASSIGN_LSHIFT
	byte SYMBOL_RSHIFT_EQ
	byte EXPRESSION_ASSIGN_RSHIFT
	byte SYMBOL_AND_EQ
	byte EXPRESSION_ASSIGN_AND
	byte SYMBOL_OR_EQ
	byte EXPRESSION_ASSIGN_OR
	byte SYMBOL_XOR_EQ
	byte EXPRESSION_ASSIGN_XOR
	byte SYMBOL_OR_OR
	byte EXPRESSION_LOGICAL_OR
	byte SYMBOL_AND_AND
	byte EXPRESSION_LOGICAL_AND
	byte SYMBOL_OR
	byte EXPRESSION_BITWISE_OR
	byte SYMBOL_XOR
	byte EXPRESSION_BITWISE_XOR
	byte SYMBOL_AND
	byte EXPRESSION_BITWISE_AND
	byte SYMBOL_EQ_EQ
	byte EXPRESSION_EQ
	byte SYMBOL_NOT_EQ
	byte EXPRESSION_NEQ
	byte SYMBOL_LT
	byte EXPRESSION_LT
	byte SYMBOL_GT
	byte EXPRESSION_GT
	byte SYMBOL_LT_EQ
	byte EXPRESSION_LEQ
	byte SYMBOL_GT_EQ
	byte EXPRESSION_GEQ
	byte SYMBOL_LSHIFT
	byte EXPRESSION_LSHIFT
	byte SYMBOL_RSHIFT
	byte EXPRESSION_RSHIFT
	byte SYMBOL_PLUS
	byte EXPRESSION_ADD
	byte SYMBOL_MINUS
	byte EXPRESSION_SUB
	byte SYMBOL_TIMES
	byte EXPRESSION_MUL
	byte SYMBOL_DIV
	byte EXPRESSION_DIV
	byte SYMBOL_PERCENT
	byte EXPRESSION_REMAINDER
	byte SYMBOL_ARROW
	byte EXPRESSION_ARROW
	byte SYMBOL_DOT
	byte EXPRESSION_DOT
	byte SYMBOL_LSQUARE
	byte EXPRESSION_SUBSCRIPT
	byte SYMBOL_LPAREN
	byte EXPRESSION_CALL
	byte 0
	byte 0

function binop_symbol_to_expression_type
	argument op
	local p
	p = .binop_table
	:binop_symbol_to_expression_type_loop
		if *1p == op goto binop_symbol_to_expression_type_found
		p += 2
		if *1p != 0 goto binop_symbol_to_expression_type_loop
	return 0
	:binop_symbol_to_expression_type_found
		p += 1
		return *1p

function is_operator
	argument token_type
	local b
	b = binop_symbol_to_expression_type(token_type)
	if b != 0 goto return_1
	b = unary_op_to_expression_type(token_type)
	if b != 0 goto return_1
	if token_type == KEYWORD_SIZEOF goto return_1
	goto return_0

function binop_expression_type_to_symbol
	argument exprtype
	local p
	p = .binop_table
	:binop_expr2symb_type_loop
		p += 1
		if *1p == exprtype goto binop_expr2symb_type_found
		p += 1
		if *1p != 0 goto binop_expr2symb_type_loop
	return 0
	:binop_expr2symb_type_found
		p -= 1
		return *1p



function int_suffix_to_type
	argument suffix
	if suffix == NUMBER_SUFFIX_L goto return_type_long
	if suffix == NUMBER_SUFFIX_U goto return_type_unsigned_int
	if suffix == NUMBER_SUFFIX_UL goto return_type_unsigned_long
	goto return_type_int

function float_suffix_to_type
	argument suffix
	if suffix == NUMBER_SUFFIX_F goto return_type_float
	goto return_type_double

; smallest integer type which can fit this value, only using unsigned if necessary
function int_value_to_type
	argument value
	if value [ 0x80000000 goto return_type_int
	if value [ 0x8000000000000000 goto return_type_long
	goto return_type_unsigned_long

; returns pointer to end of expression
function print_expression
	argument expression
	local c
	local b
	local p
	p = expression + 4
	if *4p == 0 goto print_expr_skip_type
	putc(40)
	print_type(*4p)
	putc(41)
	:print_expr_skip_type
	c = *1expression
	
	if c == EXPRESSION_FUNCTION goto print_expr_function
	if c == EXPRESSION_LOCAL_VARIABLE goto print_local_variable
	if c == EXPRESSION_GLOBAL_VARIABLE goto print_global_variable
	if c == EXPRESSION_CONSTANT_INT goto print_expr_int
	if c == EXPRESSION_CONSTANT_FLOAT goto print_expr_float
	if c == EXPRESSION_POST_INCREMENT goto print_post_increment
	if c == EXPRESSION_POST_DECREMENT goto print_post_decrement
	if c == EXPRESSION_DOT goto print_expr_dot
	if c == EXPRESSION_ARROW goto print_expr_arrow
	if c == EXPRESSION_PRE_INCREMENT goto print_pre_increment
	if c == EXPRESSION_PRE_DECREMENT goto print_pre_decrement
	if c == EXPRESSION_ADDRESS_OF goto print_address_of
	if c == EXPRESSION_DEREFERENCE goto print_dereference
	if c == EXPRESSION_UNARY_PLUS goto print_unary_plus
	if c == EXPRESSION_UNARY_MINUS goto print_unary_minus
	if c == EXPRESSION_BITWISE_NOT goto print_bitwise_not
	if c == EXPRESSION_LOGICAL_NOT goto print_logical_not
	if c == EXPRESSION_CAST goto print_cast
	if c == EXPRESSION_CALL goto print_call
	if c == EXPRESSION_CONDITIONAL goto print_conditional

	b = binop_expression_type_to_symbol(c)
	if b != 0 goto print_expr_binop
	
	puts(.str_print_bad_expr)
	exit(1)
	
	:str_print_bad_expr
		string Bad expression passed to print_expression.
		byte 10
		byte 0
	:str_global_at
		string global@
		byte 0
	:print_local_variable
		puts(.str_local_prefix)
		expression += 8
		b = sign_extend_32_to_64(*4expression)
		putn_with_sign(b)
		putc('])
		expression += 8
		return expression
	:str_local_prefix
		string [rbp
		byte 0
	:print_expr_function
		expression += 8
		puts(*8expression)
		expression += 8
		return expression
	:print_global_variable
		puts(.str_global_at)
		expression += 8
		putx32(*4expression)
		expression += 8
		return expression
	:print_cast
		; we've already printed the type
		expression += 8
		expression = print_expression(expression)
		return expression
	:print_expr_int
		expression += 8
		putn_signed(*8expression)
		expression += 8
		return expression
	:print_expr_float
		expression += 8
		putx64(*8expression)
		expression += 8
		return expression
	:print_expr_binop
		putc(40)
		expression += 8
		expression = print_expression(expression) ; 1st operand
		b = get_keyword_str(b)
		puts(b)
		expression = print_expression(expression) ; 2nd operand
		putc(41)
		return expression
	:print_conditional
		putc(40)
		expression += 8
		expression = print_expression(expression)
		putc(32)
		putc('?)
		putc(32)
		expression = print_expression(expression)
		putc(32)
		putc(':)
		putc(32)
		expression = print_expression(expression)
		putc(41)
		return expression
	:print_expr_dot
		putc(40)
		expression += 8
		expression = print_expression(expression)
		puts(.str_dot)
		putn(*4expression)
		expression += 8
		putc(41)
		return expression
	:print_expr_arrow
		putc(40)
		expression += 8
		expression = print_expression(expression)
		puts(.str_arrow)
		putn(*4expression)
		expression += 8
		putc(41)
		return expression
	:print_post_increment
		putc(40)
		expression += 8
		expression = print_expression(expression)
		putc('+)
		putc('+)
		putc(41)
		return expression
	:print_post_decrement
		putc(40)
		expression += 8
		expression = print_expression(expression)
		putc('-)
		putc('-)
		putc(41)
		return expression
	:print_pre_increment
		putc(40)
		putc('+)
		putc('+)
		expression += 8
		expression = print_expression(expression)
		putc(41)
		return expression
	:print_pre_decrement
		putc(40)
		putc('-)
		putc('-)
		expression += 8
		expression = print_expression(expression)
		putc(41)
		return expression
	:print_address_of
		putc(40)
		putc('&)
		expression += 8
		expression = print_expression(expression)
		putc(41)
		return expression
	:print_dereference
		putc(40)
		putc('*)
		expression += 8
		expression = print_expression(expression)
		putc(41)
		return expression
	:print_unary_plus
		putc(40)
		putc('+)
		expression += 8
		expression = print_expression(expression)
		putc(41)
		return expression
	:print_unary_minus
		putc(40)
		putc('-)
		expression += 8
		expression = print_expression(expression)
		putc(41)
		return expression
	:print_bitwise_not
		putc(40)
		putc('~)
		expression += 8
		expression = print_expression(expression)
		putc(41)
		return expression
	:print_logical_not
		putc(40)
		putc('!)
		expression += 8
		expression = print_expression(expression)
		putc(41)
		return expression
	:print_call
		putc(40)
		expression += 8
		expression = print_expression(expression) ; function name
		putc(40)
		:print_call_loop
			if *1expression == 0 goto print_call_loop_end
			expression = print_expression(expression)
			putc(44)
			goto print_call_loop
		:print_call_loop_end
		putc(41)
		expression += 8
		putc(41)
		return expression

; NOTE: to make things easier, the format which this outputs isn't the same as C's, specifically we have
;    *int for pointer to int and [5]int for array of 5 ints
function print_type
	argument type
	local c
	:print_type_top
	c = types + type
	c = *1c
	if c == TYPE_VOID goto print_type_void
	if c == TYPE_CHAR goto print_type_char
	if c == TYPE_UNSIGNED_CHAR goto print_type_unsigned_char
	if c == TYPE_SHORT goto print_type_short
	if c == TYPE_UNSIGNED_SHORT goto print_type_unsigned_short
	if c == TYPE_INT goto print_type_int
	if c == TYPE_UNSIGNED_INT goto print_type_unsigned_int
	if c == TYPE_LONG goto print_type_long
	if c == TYPE_UNSIGNED_LONG goto print_type_unsigned_long
	if c == TYPE_FLOAT goto print_type_float
	if c == TYPE_DOUBLE goto print_type_double
	if c == TYPE_POINTER goto print_type_pointer
	if c == TYPE_ARRAY goto print_type_array
	if c == TYPE_STRUCT goto print_type_struct
	if c == TYPE_FUNCTION goto print_type_function
	puts(.str_bad_print_type)
	putnln(type)
	putnln(c)
	putnln(types_bytes_used)
	exit(1)
	:str_bad_print_type
		string Bad type passed to print_type:
		byte 32
		byte 0
	:print_type_void
		return puts(.str_void)
	:print_type_char
		return puts(.str_char)
	:print_type_unsigned_char
		return puts(.str_unsigned_char)
	:print_type_short
		return puts(.str_short)
	:print_type_unsigned_short
		return puts(.str_unsigned_short)
	:print_type_int
		return puts(.str_int)
	:print_type_unsigned_int
		return puts(.str_unsigned_int)
	:print_type_long
		return puts(.str_long)
	:print_type_unsigned_long
		return puts(.str_unsigned_long)
	:print_type_float
		return puts(.str_float)
	:print_type_double
		return puts(.str_double)
	:print_type_pointer
		putc('*)
		type += 1
		goto print_type_top
	:print_type_array
		putc('[)
		type += 1
		c = types + type
		putn(*8c)
		putc('])
		type += 8
		goto print_type_top
	:print_type_struct
		puts(.str_struct)
		c = types + type
		c += 1
		putc('@)
		putx64(*8c)
		return
	:print_type_function
		type += 1
		putc(40)
		putc(40)
		:print_type_function_loop
			c = types + type
			if *1c == 0 goto print_type_function_loop_end
			print_type(type)
			putc(44)
			type += type_length(type)
			goto print_type_function_loop
		:print_type_function_loop_end
		type += 1 ; 0 terminator
		putc(41)
		putc(32)
		putc('-)
		putc('>)
		putc(32)
		print_type(type)
		putc(41)
		return
