mirror of
https://github.com/c3lang/c3c.git
synced 2026-02-27 12:01:16 +00:00
3421 lines
97 KiB
C
3421 lines
97 KiB
C
// Copyright (c) 2019-2025 Christoffer Lerno. All rights reserved.
|
||
// Use of this source code is governed by the GNU LGPLv3.0 license
|
||
// a copy of which can be found in the LICENSE file.
|
||
|
||
#include "compiler_internal.h"
|
||
#include "parser_internal.h"
|
||
|
||
typedef enum FunctionParse_
|
||
{
|
||
FUNC_PARSE_REGULAR,
|
||
FUNC_PARSE_C3I,
|
||
FUNC_PARSE_EXTERN,
|
||
FUNC_PARSE_INTERFACE,
|
||
} FunctionParse;
|
||
|
||
static inline Decl *parse_func_definition(ParseContext *c, AstId contracts, FunctionParse parse_kind);
|
||
static inline bool parse_bitstruct_body(ParseContext *c, Decl *decl);
|
||
static inline bool parse_enum_param_list(ParseContext *c, Decl*** parameters_ref, ArrayIndex *inline_index);
|
||
static Decl *parse_ct_include(ParseContext *c);
|
||
static Decl *parse_exec(ParseContext *c);
|
||
static bool parse_attributes_for_global(ParseContext *c, Decl *decl);
|
||
INLINE bool parse_decl_initializer(ParseContext *c, Decl *decl);
|
||
INLINE Decl *decl_new_var_current(ParseContext *c, TypeInfo *type, VarDeclKind kind);
|
||
static bool parse_contracts(ParseContext *c, AstId *contracts_ref);
|
||
|
||
INLINE Decl *decl_new_var_current(ParseContext *c, TypeInfo *type, VarDeclKind kind)
|
||
{
|
||
return decl_new_var(symstr(c), c->span, type, kind);
|
||
}
|
||
|
||
/**
|
||
* Walk until we find the first top level construct, the current heuristic is this:
|
||
* public, typedef, struct, import, union, extern, enum, generic, attribute, define
|
||
* are *always* sync points.
|
||
*
|
||
* func, any type, CT_IDENT, CT_TYPE_IDENT, $if, $for, $switch, generic,
|
||
* doc comment start, asm, typeof, TYPE_IDENT, const, IDENT
|
||
* - are sync points only if they appear in the first column.
|
||
*/
|
||
void recover_top_level(ParseContext *c)
|
||
{
|
||
advance(c);
|
||
while (!tok_is(c, TOKEN_EOF))
|
||
{
|
||
switch (c->tok)
|
||
{
|
||
case TOKEN_IMPORT:
|
||
case TOKEN_EXTERN:
|
||
case TOKEN_ENUM:
|
||
case TOKEN_ALIAS:
|
||
case TOKEN_TYPEDEF:
|
||
case TOKEN_ATTRDEF:
|
||
case TOKEN_FAULTDEF:
|
||
return;
|
||
case TOKEN_CONST:
|
||
case TOKEN_ASM:
|
||
case TOKEN_CT_ASSERT:
|
||
case TOKEN_CT_ERROR:
|
||
case TOKEN_DOCS_START:
|
||
case TOKEN_CT_IDENT:
|
||
case TOKEN_CT_IF:
|
||
case TOKEN_CT_FOR:
|
||
case TOKEN_CT_SWITCH:
|
||
case TOKEN_FN:
|
||
case TOKEN_STRUCT:
|
||
case TOKEN_UNION:
|
||
case TOKEN_BITSTRUCT:
|
||
case TOKEN_STATIC:
|
||
case TYPELIKE_TOKENS:
|
||
// Only recover if this is in the first col.
|
||
if (c->span.col == 1) return;
|
||
advance(c);
|
||
break;
|
||
default:
|
||
advance(c);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
INLINE bool parse_decl_initializer(ParseContext *c, Decl *decl)
|
||
{
|
||
ASSIGN_EXPR_OR_RET(decl->var.init_expr, parse_expr(c), false);
|
||
return true;
|
||
}
|
||
|
||
|
||
// --- Parse paths
|
||
|
||
/**
|
||
* module_path ::= IDENT (SCOPE IDENT)*
|
||
*
|
||
* @param c
|
||
* @return path or null if parsing failed.
|
||
*/
|
||
static inline Path *parse_module_path(ParseContext *c)
|
||
{
|
||
ASSERT(tok_is(c, TOKEN_IDENT));
|
||
scratch_buffer_clear();
|
||
SourceSpan span = c->span;
|
||
int max_exceeded = 0;
|
||
while (1)
|
||
{
|
||
const char *string = symstr(c);
|
||
size_t len = c->data.lex_len;
|
||
if (!try_consume(c, TOKEN_IDENT))
|
||
{
|
||
if (token_is_keyword_ident(c->tok))
|
||
{
|
||
PRINT_ERROR_HERE("The module path cannot contain a reserved keyword, try another name.");
|
||
return NULL;
|
||
}
|
||
if (token_is_some_ident(c->tok))
|
||
{
|
||
PRINT_ERROR_HERE("The elements of a module path must consist of only lower case letters, 0-9 and '_'.");
|
||
return NULL;
|
||
}
|
||
PRINT_ERROR_HERE("Each '::' must be followed by a regular lower case sub module name.");
|
||
return NULL;
|
||
}
|
||
if (len > MAX_MODULE_NAME)
|
||
{
|
||
PRINT_ERROR_LAST("The module name is too long, it's %d characters (%d more than the maximum allowed %d characters).", (int)len, (int)len - MAX_MODULE_NAME, MAX_MODULE_NAME);
|
||
return NULL;
|
||
}
|
||
if (max_exceeded)
|
||
{
|
||
max_exceeded += len;
|
||
}
|
||
else
|
||
{
|
||
scratch_buffer_append(string);
|
||
if (scratch_buffer.len > MAX_MODULE_PATH)
|
||
{
|
||
max_exceeded = scratch_buffer.len;
|
||
}
|
||
}
|
||
if (!try_consume(c, TOKEN_SCOPE))
|
||
{
|
||
span = extend_span_with_token(span, c->prev_span);
|
||
break;
|
||
}
|
||
if (max_exceeded)
|
||
{
|
||
max_exceeded += 2;
|
||
}
|
||
else
|
||
{
|
||
scratch_buffer_append("::");
|
||
}
|
||
}
|
||
// This way we can highlight the entire span.
|
||
if (max_exceeded)
|
||
{
|
||
print_error_at(extend_span_with_token(span, c->prev_span), "The full module path is too long, it's %lld characters (%lld more than the maximum allowed %lld characters).", (long long int)max_exceeded, (long long int)max_exceeded - MAX_MODULE_PATH, (long long int)MAX_MODULE_PATH);
|
||
}
|
||
return path_create_from_string(scratch_buffer_to_string(), scratch_buffer.len, span);
|
||
}
|
||
|
||
|
||
// --- Parse import and module
|
||
|
||
/**
|
||
*
|
||
* module_param
|
||
* : TYPE_IDENT
|
||
* | CONST_IDENT
|
||
* ;
|
||
*
|
||
* module_params ::= '{' module_param (',' module_param)* '}'
|
||
*/
|
||
static inline bool parse_optional_module_params(ParseContext *c, const char ***tokens_ref)
|
||
{
|
||
|
||
*tokens_ref = NULL;
|
||
|
||
if (!try_consume(c, TOKEN_LBRACE)) return true;
|
||
if (try_consume(c, TOKEN_RBRACE)) RETURN_PRINT_ERROR_HERE("The generic parameter list cannot be empty, it needs at least one element.");
|
||
|
||
// No params
|
||
while (1)
|
||
{
|
||
switch (c->tok)
|
||
{
|
||
case TOKEN_TYPE_IDENT:
|
||
case TOKEN_CONST_IDENT:
|
||
break;
|
||
case TOKEN_COMMA:
|
||
RETURN_PRINT_ERROR_HERE("Unexpected ','");
|
||
case TOKEN_IDENT:
|
||
RETURN_PRINT_ERROR_HERE("The module parameter must be a type or a constant.");
|
||
case TOKEN_CT_IDENT:
|
||
case TOKEN_CT_TYPE_IDENT:
|
||
RETURN_PRINT_ERROR_HERE("The module parameter cannot be a $-prefixed name.");
|
||
default:
|
||
RETURN_PRINT_ERROR_HERE("Only generic parameters are allowed here as parameters to the module.");
|
||
}
|
||
vec_add(*tokens_ref, symstr(c));
|
||
advance(c);
|
||
if (!try_consume(c, TOKEN_COMMA))
|
||
{
|
||
if (!consume(c, TOKEN_RBRACE, "Expected '}'.")) return false;
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
/**
|
||
* module ::= MODULE module_path ('{' module_params '}')? (@public|@private|@local|@test|@export|@extern) EOS
|
||
*/
|
||
bool parse_module(ParseContext *c, AstId contracts)
|
||
{
|
||
if (tok_is(c, TOKEN_STRING))
|
||
{
|
||
RETURN_PRINT_ERROR_HERE("'module' should be followed by a plain identifier, not a string. Did you accidentally put the module name between \"\"?");
|
||
}
|
||
|
||
if (!tok_is(c, TOKEN_IDENT))
|
||
{
|
||
if (token_is_keyword_ident(c->tok))
|
||
{
|
||
RETURN_PRINT_ERROR_HERE("The module name cannot contain a reserved keyword, try another name.");
|
||
}
|
||
if (token_is_some_ident(c->tok))
|
||
{
|
||
RETURN_PRINT_ERROR_HERE("The module name must consist of only lower case letters, 0-9 and '_'.");
|
||
}
|
||
RETURN_PRINT_ERROR_HERE("'module' should be followed by a module name.");
|
||
}
|
||
|
||
Path *path = parse_module_path(c);
|
||
|
||
// Expect the module name
|
||
if (!path)
|
||
{
|
||
path = CALLOCS(Path);
|
||
path->len = (unsigned)strlen("#invalid");
|
||
path->module = "#invalid";
|
||
path->span = INVALID_SPAN;
|
||
context_set_module(c, path, NULL);
|
||
recover_top_level(c);
|
||
return false;
|
||
}
|
||
|
||
// Is this a generic module?
|
||
const char **generic_parameters = NULL;
|
||
if (!parse_optional_module_params(c, &generic_parameters))
|
||
{
|
||
if (!context_set_module(c, path, NULL)) return false;
|
||
recover_top_level(c);
|
||
if (contracts) RETURN_PRINT_ERROR_AT(false, astptr(contracts), "Contracts cannot be use with non-generic modules.");
|
||
return true;
|
||
}
|
||
if (!context_set_module(c, path, generic_parameters)) return false;
|
||
if (contracts)
|
||
{
|
||
AstId old_contracts = c->unit->module->contracts;
|
||
if (old_contracts)
|
||
{
|
||
Ast *last = ast_last(astptr(old_contracts));
|
||
last->next = contracts;
|
||
}
|
||
else
|
||
{
|
||
c->unit->module->contracts = contracts;
|
||
}
|
||
while (contracts)
|
||
{
|
||
Ast *current = astptr(contracts);
|
||
contracts = current->next;
|
||
ASSERT(current->ast_kind == AST_CONTRACT);
|
||
switch (current->contract_stmt.kind)
|
||
{
|
||
case CONTRACT_UNKNOWN:
|
||
case CONTRACT_PURE:
|
||
case CONTRACT_PARAM:
|
||
case CONTRACT_OPTIONALS:
|
||
case CONTRACT_ENSURE:
|
||
break;
|
||
case CONTRACT_REQUIRE:
|
||
case CONTRACT_COMMENT:
|
||
continue;
|
||
}
|
||
RETURN_PRINT_ERROR_AT(false, current, "Invalid constraint - only '@require' is valid for modules.");
|
||
}
|
||
}
|
||
Visibility visibility = VISIBLE_PUBLIC;
|
||
Attr** attrs = NULL;
|
||
bool is_cond;
|
||
if (!parse_attributes(c, &attrs, &visibility, NULL, &is_cond)) return false;
|
||
FOREACH(Attr *, attr, attrs)
|
||
{
|
||
if (attr->is_custom) RETURN_PRINT_ERROR_AT(false, attr, "Custom attributes cannot be used with 'module'.");
|
||
switch (attr->attr_kind)
|
||
{
|
||
case ATTRIBUTE_LINK:
|
||
{
|
||
unsigned args = vec_size(attr->exprs);
|
||
if (args < 1) RETURN_PRINT_ERROR_AT(false, attr, "'@link' needs at least 1 argument.");
|
||
vec_add(c->unit->attr_links, attr);
|
||
continue;
|
||
}
|
||
case ATTRIBUTE_IF:
|
||
if (c->unit->if_attr) RETURN_PRINT_ERROR_AT(false, attr, "'@if' appeared more than once.");
|
||
c->unit->if_attr = attr;
|
||
continue;
|
||
case ATTRIBUTE_BENCHMARK:
|
||
c->unit->benchmark_by_default = true;
|
||
continue;
|
||
case ATTRIBUTE_TEST:
|
||
c->unit->test_by_default = true;
|
||
continue;
|
||
case ATTRIBUTE_EXPORT:
|
||
if (attr->exprs) RETURN_PRINT_ERROR_AT(false, attr, "Expected no arguments to '@export'");
|
||
if (c->unit->export_by_default)
|
||
RETURN_PRINT_ERROR_AT(false, attr, "'@export' appeared more than once.");
|
||
c->unit->export_by_default = true;
|
||
continue;
|
||
case ATTRIBUTE_EXTERN:
|
||
{
|
||
if (vec_size(attr->exprs) != 1)
|
||
{
|
||
RETURN_PRINT_ERROR_AT(false, attr, "Expected 1 argument to '@extern(..), not %d'.",
|
||
vec_size(attr->exprs));
|
||
}
|
||
Expr *expr = attr->exprs[0];
|
||
if (!expr_is_const_string(expr)) RETURN_PRINT_ERROR_AT(false, expr, "Expected a constant string.");
|
||
if (c->unit->module->extname)
|
||
{
|
||
RETURN_PRINT_ERROR_AT(false, attr,
|
||
"External name for the module may only be declared in one location.");
|
||
}
|
||
c->unit->module->extname = expr->const_expr.bytes.ptr;
|
||
continue;
|
||
}
|
||
default:
|
||
break;
|
||
}
|
||
RETURN_PRINT_ERROR_AT(false, attr, "'%s' cannot be used after a module declaration.", attr->name);
|
||
}
|
||
c->unit->default_visibility = visibility;
|
||
CONSUME_EOS_OR_RET(false);
|
||
return true;
|
||
}
|
||
|
||
|
||
static bool consume_type_name(ParseContext *c, const char* type)
|
||
{
|
||
if (tok_is(c, TOKEN_IDENT) || token_is_keyword_ident(c->tok))
|
||
{
|
||
RETURN_PRINT_ERROR_HERE("Names of %ss must start with an uppercase letter.", type);
|
||
}
|
||
if (tok_is(c, TOKEN_CONST_IDENT))
|
||
{
|
||
RETURN_PRINT_ERROR_HERE("Names of %ss cannot be all uppercase.", type);
|
||
}
|
||
return consume(c, TOKEN_TYPE_IDENT, "'%s' should be followed by the name of the %s.", type, type);
|
||
}
|
||
|
||
bool consume_const_name(ParseContext *c, const char* type)
|
||
{
|
||
if (tok_is(c, TOKEN_IDENT) || tok_is(c, TOKEN_TYPE_IDENT) || token_is_keyword_ident(c->tok))
|
||
{
|
||
RETURN_PRINT_ERROR_HERE("Names of %ss must be all uppercase.", type);
|
||
}
|
||
return consume(c, TOKEN_CONST_IDENT, "A constant name was expected here, did you forget it?");
|
||
}
|
||
|
||
/**
|
||
* Parse an optional foo::bar::
|
||
*/
|
||
bool parse_path_prefix(ParseContext *c, Path** path_ref)
|
||
{
|
||
*path_ref = NULL;
|
||
if (!tok_is(c, TOKEN_IDENT) || peek(c) != TOKEN_SCOPE) return true;
|
||
|
||
Path *path = CALLOCS(Path);
|
||
path->span = c->span;
|
||
scratch_buffer_clear();
|
||
scratch_buffer_append(symstr(c));
|
||
SourceSpan last_loc = c->span;
|
||
advance(c);
|
||
advance(c);
|
||
while (tok_is(c, TOKEN_IDENT) && peek(c) == TOKEN_SCOPE)
|
||
{
|
||
last_loc = c->span;
|
||
scratch_buffer_append("::");
|
||
scratch_buffer_append_len(symstr(c), c->data.lex_len);
|
||
advance(c); advance(c);
|
||
}
|
||
|
||
TokenType type = TOKEN_IDENT;
|
||
path->span = extend_span_with_token(path->span, last_loc);
|
||
path->module = scratch_buffer_interned_as(&type);
|
||
if (type != TOKEN_IDENT)
|
||
{
|
||
RETURN_PRINT_ERROR_AT(false, path, "A module name was expected here.");
|
||
}
|
||
path->len = scratch_buffer.len;
|
||
*path_ref = path;
|
||
return true;
|
||
}
|
||
|
||
// --- Type parsing
|
||
|
||
/**
|
||
* base_type
|
||
* : VOID
|
||
* | BOOL
|
||
* | ICHAR
|
||
* | CHAR
|
||
* | SHORT
|
||
* | USHORT
|
||
* | INT
|
||
* | UINT
|
||
* | LONG
|
||
* | ULONG
|
||
* | INT128
|
||
* | UINT128
|
||
* | FLOAT
|
||
* | DOUBLE
|
||
* | FLOAT16
|
||
* | FLOAT128
|
||
* | TYPE_IDENT
|
||
* | path_prefix TYPE_IDENT
|
||
* | CT_TYPE_IDENT
|
||
* | CT_TYPEOF '(' expr ')'
|
||
* | CT_TYPEFROM '(' expr ')'
|
||
* | CT_VATYPE '(' expr ')'
|
||
* | CT_EVALTYPE '(' expr ')'
|
||
* ;
|
||
*
|
||
* Assume prev_token is the type.
|
||
* @return TypeInfo (poisoned if fails)
|
||
*/
|
||
static inline TypeInfo *parse_base_type(ParseContext *c)
|
||
{
|
||
if (try_consume(c, TOKEN_CT_TYPEFROM))
|
||
{
|
||
TypeInfo *type_info = type_info_new(TYPE_INFO_TYPEFROM, c->prev_span);
|
||
CONSUME_OR_RET(TOKEN_LPAREN, poisoned_type_info);
|
||
ASSIGN_EXPR_OR_RET(type_info->unresolved_type_expr, parse_expr(c), poisoned_type_info);
|
||
CONSUME_OR_RET(TOKEN_RPAREN, poisoned_type_info);
|
||
RANGE_EXTEND_PREV(type_info);
|
||
return type_info;
|
||
}
|
||
if (try_consume(c, TOKEN_CT_TYPEOF))
|
||
{
|
||
TypeInfo *type_info = type_info_new(TYPE_INFO_TYPEOF, c->prev_span);
|
||
CONSUME_OR_RET(TOKEN_LPAREN, poisoned_type_info);
|
||
ASSIGN_EXPR_OR_RET(type_info->unresolved_type_expr, parse_expr(c), poisoned_type_info);
|
||
CONSUME_OR_RET(TOKEN_RPAREN, poisoned_type_info);
|
||
RANGE_EXTEND_PREV(type_info);
|
||
return type_info;
|
||
}
|
||
if (try_consume(c, TOKEN_CT_VATYPE))
|
||
{
|
||
TypeInfo *type_info = type_info_new(TYPE_INFO_VATYPE, c->prev_span);
|
||
CONSUME_OR_RET(TOKEN_LBRACKET, poisoned_type_info);
|
||
ASSIGN_EXPR_OR_RET(type_info->unresolved_type_expr, parse_expr(c), poisoned_type_info);
|
||
CONSUME_OR_RET(TOKEN_RBRACKET, poisoned_type_info);
|
||
RANGE_EXTEND_PREV(type_info);
|
||
return type_info;
|
||
}
|
||
if (try_consume(c, TOKEN_CT_EVALTYPE))
|
||
{
|
||
TypeInfo *type_info = type_info_new(TYPE_INFO_EVALTYPE, c->prev_span);
|
||
CONSUME_OR_RET(TOKEN_LPAREN, poisoned_type_info);
|
||
ASSIGN_EXPR_OR_RET(type_info->unresolved_type_expr, parse_expr(c), poisoned_type_info);
|
||
CONSUME_OR_RET(TOKEN_RPAREN, poisoned_type_info);
|
||
RANGE_EXTEND_PREV(type_info);
|
||
return type_info;
|
||
}
|
||
SourceSpan range = c->span;
|
||
Path *path;
|
||
if (!parse_path_prefix(c, &path)) return poisoned_type_info;
|
||
if (path)
|
||
{
|
||
TypeInfo *type_info = type_info_new(TYPE_INFO_IDENTIFIER, range);
|
||
type_info->unresolved.path = path;
|
||
type_info->unresolved.name = symstr(c);
|
||
if (!consume_type_name(c, "type")) return poisoned_type_info;
|
||
RANGE_EXTEND_PREV(type_info);
|
||
return type_info;
|
||
}
|
||
|
||
TypeInfo *type_info = NULL;
|
||
switch (c->tok)
|
||
{
|
||
case TOKEN_TYPE_IDENT:
|
||
type_info = type_info_new_curr(c, TYPE_INFO_IDENTIFIER);
|
||
type_info->unresolved.name = symstr(c);
|
||
break;
|
||
case TOKEN_CT_TYPE_IDENT:
|
||
type_info = type_info_new_curr(c, TYPE_INFO_CT_IDENTIFIER);
|
||
type_info->unresolved.name = symstr(c);
|
||
break;
|
||
case TYPE_TOKENS:
|
||
type_info = type_info_new_curr(c, TYPE_INFO_IDENTIFIER);
|
||
type_info->resolve_status = RESOLVE_DONE;
|
||
type_info->type = type_from_token(c->tok);
|
||
break;
|
||
default:
|
||
if (c->tok == TOKEN_IDENT)
|
||
{
|
||
if (peek(c) == TOKEN_IDENT)
|
||
{
|
||
PRINT_ERROR_HERE("The name of a type must start with uppercase and contain at least one lowercase letter.");
|
||
}
|
||
else
|
||
{
|
||
PRINT_ERROR_HERE("A type name was expected, but this looks a variable or function name (as it doesn't start with an uppercase letter).");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
PRINT_ERROR_HERE("A type name was expected here.");
|
||
}
|
||
return poisoned_type_info;
|
||
}
|
||
advance(c);
|
||
RANGE_EXTEND_PREV(type_info);
|
||
return type_info;
|
||
}
|
||
|
||
/**
|
||
* generic_type ::= type generic_parameters
|
||
*/
|
||
static inline TypeInfo *parse_generic_type(ParseContext *c, TypeInfo *type)
|
||
{
|
||
ASSERT(type_info_ok(type));
|
||
TypeInfo *generic_type = type_info_new(TYPE_INFO_GENERIC, type->span);
|
||
if (!parse_generic_expr_list(c, &generic_type->generic.params)) return poisoned_type_info;
|
||
generic_type->generic.base = type;
|
||
return generic_type;
|
||
}
|
||
|
||
/**
|
||
* array_type_index ::= '[' (constant_expression | '*')? ']'
|
||
*
|
||
* @param c the parse context
|
||
* @param type the type to wrap, may not be poisoned.
|
||
* @return type (poisoned if fails)
|
||
*/
|
||
static inline TypeInfo *parse_array_type_index(ParseContext *c, TypeInfo *type)
|
||
{
|
||
ASSERT(type_info_ok(type));
|
||
|
||
advance_and_verify(c, TOKEN_LBRACKET);
|
||
if (try_consume(c, TOKEN_STAR))
|
||
{
|
||
CONSUME_OR_RET(TOKEN_RBRACKET, poisoned_type_info);
|
||
TypeInfo *inferred_array = type_info_new(TYPE_INFO_INFERRED_ARRAY, type->span);
|
||
inferred_array->array.base = type;
|
||
RANGE_EXTEND_PREV(inferred_array);
|
||
return inferred_array;
|
||
}
|
||
if (try_consume(c, TOKEN_RBRACKET))
|
||
{
|
||
bool is_resolved = type->resolve_status == RESOLVE_DONE;
|
||
if (is_resolved && !type_is_valid_for_array(type->type)) goto DIRECT_SLICE;
|
||
switch (type->subtype)
|
||
{
|
||
case TYPE_COMPRESSED_NONE:
|
||
type->subtype = TYPE_COMPRESSED_SUB;
|
||
break;
|
||
case TYPE_COMPRESSED_PTR:
|
||
type->subtype = TYPE_COMPRESSED_PTRSUB;
|
||
break;
|
||
case TYPE_COMPRESSED_SUB:
|
||
type->subtype = TYPE_COMPRESSED_SUBSUB;
|
||
break;
|
||
default:
|
||
goto DIRECT_SLICE;
|
||
}
|
||
if (is_resolved)
|
||
{
|
||
type->type = type_get_slice(type->type);
|
||
}
|
||
RANGE_EXTEND_PREV(type);
|
||
return type;
|
||
DIRECT_SLICE:;
|
||
TypeInfo *slice = type_info_new(TYPE_INFO_SLICE, type->span);
|
||
slice->array.base = type;
|
||
slice->array.len = NULL;
|
||
RANGE_EXTEND_PREV(slice);
|
||
return slice;
|
||
}
|
||
TypeInfo *array = type_info_new(TYPE_INFO_ARRAY, type->span);
|
||
array->array.base = type;
|
||
ASSIGN_EXPR_OR_RET(array->array.len, parse_expr(c), poisoned_type_info);
|
||
CONSUME_OR_RET(TOKEN_RBRACKET, poisoned_type_info);
|
||
RANGE_EXTEND_PREV(array);
|
||
return array;
|
||
}
|
||
|
||
/**
|
||
* vector_type_index ::= '[<' (constant_expression | '*') '>]'
|
||
*
|
||
* @param c the parse context.
|
||
* @param type the type to wrap, may not be poisoned.
|
||
* @return type (poisoned if fails)
|
||
*/
|
||
static inline TypeInfo *parse_vector_type_index(ParseContext *c, TypeInfo *type)
|
||
{
|
||
ASSERT(type_info_ok(type));
|
||
|
||
advance_and_verify(c, TOKEN_LVEC);
|
||
TypeInfo *vector = type_info_new(TYPE_INFO_VECTOR, type->span);
|
||
vector->array.base = type;
|
||
if (try_consume(c, TOKEN_STAR))
|
||
{
|
||
CONSUME_OR_RET(TOKEN_RVEC, poisoned_type_info);
|
||
vector->kind = TYPE_INFO_INFERRED_VECTOR;
|
||
}
|
||
else
|
||
{
|
||
ASSIGN_EXPR_OR_RET(vector->array.len, parse_expr(c), poisoned_type_info);
|
||
CONSUME_OR_RET(TOKEN_RVEC, poisoned_type_info);
|
||
}
|
||
RANGE_EXTEND_PREV(vector);
|
||
return vector;
|
||
}
|
||
/**
|
||
* type ::= base_type ('*' | array_type_index | vector_type_index | generic_parameters)*
|
||
*
|
||
* Assume already stepped into.
|
||
* @return Type, poisoned if parsing is invalid.
|
||
*/
|
||
static inline TypeInfo *parse_type_with_base_maybe_generic(ParseContext *c, TypeInfo *type_info, bool allow_generic)
|
||
{
|
||
while (type_info_ok(type_info))
|
||
{
|
||
switch (c->tok)
|
||
{
|
||
case TOKEN_LVEC:
|
||
type_info = parse_vector_type_index(c, type_info);
|
||
break;
|
||
case TOKEN_LBRACKET:
|
||
type_info = parse_array_type_index(c, type_info);
|
||
break;
|
||
case TOKEN_LBRACE:
|
||
if (!allow_generic) return type_info;
|
||
type_info = parse_generic_type(c, type_info);
|
||
break;
|
||
case TOKEN_STAR:
|
||
advance(c);
|
||
{
|
||
switch (type_info->subtype)
|
||
{
|
||
case TYPE_COMPRESSED_NONE:
|
||
type_info->subtype = TYPE_COMPRESSED_PTR;
|
||
break;
|
||
case TYPE_COMPRESSED_PTR:
|
||
type_info->subtype = TYPE_COMPRESSED_PTRPTR;
|
||
break;
|
||
case TYPE_COMPRESSED_SUB:
|
||
type_info->subtype = TYPE_COMPRESSED_SUBPTR;
|
||
break;
|
||
default:
|
||
{
|
||
TypeInfo *ptr_type = type_info_new(TYPE_INFO_POINTER, type_info->span);
|
||
ASSERT(type_info);
|
||
ptr_type->pointer = type_info;
|
||
type_info = ptr_type;
|
||
RANGE_EXTEND_PREV(type_info);
|
||
break;
|
||
}
|
||
}
|
||
if (type_info->resolve_status == RESOLVE_DONE)
|
||
{
|
||
ASSERT(type_info->type);
|
||
type_info->type = type_get_ptr(type_info->type);
|
||
}
|
||
RANGE_EXTEND_PREV(type_info);
|
||
break;
|
||
}
|
||
break;
|
||
default:
|
||
return type_info;
|
||
}
|
||
}
|
||
return type_info;
|
||
}
|
||
|
||
TypeInfo *parse_type_with_base(ParseContext *c, TypeInfo *type_info)
|
||
{
|
||
return parse_type_with_base_maybe_generic(c, type_info, true);
|
||
}
|
||
/**
|
||
* type ::= base_type modifiers
|
||
*
|
||
* Assume already stepped into.
|
||
* @return Type, poisoned if parsing is invalid.
|
||
*/
|
||
TypeInfo *parse_type(ParseContext *c)
|
||
{
|
||
ASSIGN_TYPE_OR_RET(TypeInfo *base, parse_base_type(c), poisoned_type_info);
|
||
return parse_type_with_base(c, base);
|
||
}
|
||
|
||
typedef enum DiscardedSubscript_
|
||
{
|
||
DISCARD_ERR,
|
||
DISCARD_WILDCARD,
|
||
DISCARD_SLICE,
|
||
DISCARD_EXPR
|
||
} DiscardedSubscript;
|
||
|
||
static DiscardedSubscript parse_discarded_subscript(ParseContext *c, TokenType end)
|
||
{
|
||
if (end == TOKEN_RBRACKET && try_consume(c, end)) return DISCARD_SLICE;
|
||
if (try_consume(c, TOKEN_STAR) || try_consume(c, TOKEN_QUESTION))
|
||
{
|
||
CONSUME_OR_RET(end, DISCARD_ERR);
|
||
return DISCARD_WILDCARD;
|
||
}
|
||
ASSIGN_EXPR_OR_RET(Expr *ex, parse_expr(c), DISCARD_ERR);
|
||
(void)ex;
|
||
CONSUME_OR_RET(end, DISCARD_ERR);
|
||
return DISCARD_EXPR;
|
||
}
|
||
|
||
INLINE bool parse_rethrow_bracket(ParseContext *c, SourceSpan start)
|
||
{
|
||
if (try_consume(c, TOKEN_LBRACKET))
|
||
{
|
||
switch (parse_discarded_subscript(c, TOKEN_RBRACKET))
|
||
{
|
||
case DISCARD_ERR:
|
||
return false;
|
||
case DISCARD_WILDCARD:
|
||
print_error_at(extend_span_with_token(start, c->prev_span), "When declaring an optional array, the '[*]' should appear before the '?', e.g 'Foo[*]?'.");
|
||
return false;
|
||
case DISCARD_SLICE:
|
||
print_error_at(extend_span_with_token(start, c->prev_span),
|
||
"When declaring an optional slice the '[]' should appear before the '?', e.g 'Foo[]?'.");
|
||
return false;
|
||
case DISCARD_EXPR:
|
||
print_error_at(extend_span_with_token(start, c->prev_span), "When declaring an optional array, the '[...]' should appear before the '?', e.g 'Foo[4]?'.");
|
||
return false;
|
||
}
|
||
UNREACHABLE
|
||
}
|
||
if (try_consume(c, TOKEN_LVEC))
|
||
{
|
||
switch (parse_discarded_subscript(c, TOKEN_RVEC))
|
||
{
|
||
case DISCARD_ERR:
|
||
return false;
|
||
case DISCARD_WILDCARD:
|
||
print_error_at(extend_span_with_token(start, c->span), "When declaring an optional vector, the '[<*>]' should appear before the '?', e.g 'Foo[<*>]?'.");
|
||
return false;
|
||
case DISCARD_SLICE:
|
||
UNREACHABLE
|
||
case DISCARD_EXPR:
|
||
print_error_at(extend_span_with_token(start, c->span), "When declaring an optional vector, the '[<...>]' should appear before the '?', e.g 'Foo[<4>]?'.");
|
||
return false;
|
||
}
|
||
UNREACHABLE
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* optional_type ::= type '!'?
|
||
* @param c
|
||
* @param allow_generic should generic be allowed
|
||
* @return The resulting type
|
||
*/
|
||
static inline TypeInfo *parse_optional_type_maybe_generic(ParseContext *c, bool allow_generic)
|
||
{
|
||
ASSIGN_TYPE_OR_RET(TypeInfo *info, parse_base_type(c), poisoned_type_info);
|
||
ASSIGN_TYPE_OR_RET(info, parse_type_with_base_maybe_generic(c, info, allow_generic), poisoned_type_info);
|
||
if (try_consume(c, TOKEN_QUESTION))
|
||
{
|
||
if (!parse_rethrow_bracket(c, info->span)) return poisoned_type_info;
|
||
ASSERT(!info->optional);
|
||
info->optional = true;
|
||
if (info->resolve_status == RESOLVE_DONE)
|
||
{
|
||
info->type = type_get_optional(info->type);
|
||
}
|
||
RANGE_EXTEND_PREV(info);
|
||
}
|
||
return info;
|
||
}
|
||
|
||
/**
|
||
* optional_type ::= type '!'?
|
||
* @param c
|
||
* @return
|
||
*/
|
||
TypeInfo *parse_optional_type(ParseContext *c)
|
||
{
|
||
return parse_optional_type_maybe_generic(c, true);
|
||
}
|
||
|
||
TypeInfo *parse_optional_type_no_generic(ParseContext *c)
|
||
{
|
||
return parse_optional_type_maybe_generic(c, false);
|
||
}
|
||
|
||
|
||
// --- Decl parsing
|
||
|
||
/**
|
||
* interface_impls ::= '(' (type (',' type)* ','? )? ')'
|
||
*
|
||
* @param c the context
|
||
* @param interfaces_ref the list to add interfaces to
|
||
* @return false if the parsing failed
|
||
*/
|
||
bool parse_interface_impls(ParseContext *c, TypeInfo ***interfaces_ref)
|
||
{
|
||
if (!try_consume(c, TOKEN_LPAREN)) return true;
|
||
TypeInfo **interfaces = NULL;
|
||
while (!try_consume(c, TOKEN_RPAREN))
|
||
{
|
||
ASSIGN_TYPE_OR_RET(TypeInfo *interface, parse_type(c), false);
|
||
vec_add(interfaces, interface);
|
||
if (!try_consume(c, TOKEN_COMMA))
|
||
{
|
||
CONSUME_OR_RET(TOKEN_RPAREN, false);
|
||
break;
|
||
}
|
||
}
|
||
*interfaces_ref = interfaces;
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* after_type ::= (CT_IDENT | IDENT) attributes? ('=' decl_initializer)?
|
||
*/
|
||
Decl *parse_local_decl_after_type(ParseContext *c, TypeInfo *type)
|
||
{
|
||
if (tok_is(c, TOKEN_LPAREN))
|
||
{
|
||
PRINT_ERROR_HERE("Expected '{'.");
|
||
return poisoned_decl;
|
||
}
|
||
|
||
if (tok_is(c, TOKEN_CT_IDENT))
|
||
{
|
||
Decl *decl = decl_new_var_current(c, type, VARDECL_LOCAL_CT);
|
||
advance(c);
|
||
if (try_consume(c, TOKEN_EQ))
|
||
{
|
||
if (!parse_decl_initializer(c, decl)) return poisoned_decl;
|
||
}
|
||
return decl;
|
||
}
|
||
|
||
EXPECT_IDENT_FOR_OR("variable name", poisoned_decl);
|
||
|
||
Decl *decl = decl_new_var_current(c, type, VARDECL_LOCAL);
|
||
advance(c);
|
||
|
||
bool is_cond;
|
||
if (!parse_attributes(c, &decl->attributes, NULL, NULL, &is_cond)) return poisoned_decl;
|
||
decl->is_cond = is_cond;
|
||
if (tok_is(c, TOKEN_EQ))
|
||
{
|
||
if (!decl)
|
||
{
|
||
PRINT_ERROR_HERE("Expected an identifier before '='.");
|
||
return poisoned_decl;
|
||
}
|
||
advance_and_verify(c, TOKEN_EQ);
|
||
if (!parse_decl_initializer(c, decl)) return poisoned_decl;
|
||
}
|
||
return decl;
|
||
}
|
||
|
||
/**
|
||
* decl_or_expr ::= var_decl | type local_decl_after_type | expression
|
||
*/
|
||
Expr *parse_decl_or_expr(ParseContext *c)
|
||
{
|
||
// var-initialization is done separately.
|
||
Decl *decl;
|
||
if (tok_is(c, TOKEN_VAR))
|
||
{
|
||
ASSIGN_DECL_OR_RET(decl, parse_var_decl(c), poisoned_expr);
|
||
goto DECL;
|
||
}
|
||
Expr *expr = parse_expr(c);
|
||
|
||
// If it's not a type info, we assume an expr.
|
||
if (expr->expr_kind != EXPR_TYPEINFO) return expr;
|
||
|
||
switch (c->tok)
|
||
{
|
||
case TOKEN_RPAREN:
|
||
case TOKEN_RBRACKET:
|
||
case TOKEN_RBRACE:
|
||
case TOKEN_RVEC:
|
||
return expr;
|
||
default:
|
||
break;
|
||
}
|
||
// Otherwise we expect a declaration.
|
||
ASSIGN_DECL_OR_RET(decl, parse_local_decl_after_type(c, expr->type_expr), poisoned_expr);
|
||
DECL:
|
||
assert(decl);
|
||
expr = expr_new(EXPR_DECL, decl->span);
|
||
expr->decl_expr = decl;
|
||
return expr;
|
||
}
|
||
|
||
|
||
/**
|
||
* const_decl ::= 'const' type? CONST_IDENT attributes? '=' const_expr
|
||
*/
|
||
Decl *parse_const_declaration(ParseContext *c, bool is_global, bool is_extern)
|
||
{
|
||
advance_and_verify(c, TOKEN_CONST);
|
||
|
||
TypeInfo *type_info = NULL;
|
||
|
||
// If not a const ident, assume we want the type
|
||
if (c->tok != TOKEN_CONST_IDENT)
|
||
{
|
||
ASSIGN_TYPE_OR_RET(type_info, parse_optional_type(c), poisoned_decl);
|
||
}
|
||
|
||
// Create the decl
|
||
Decl *decl = decl_new_var(symstr(c), c->span, type_info, VARDECL_CONST);
|
||
|
||
// Check the name
|
||
if (!consume_const_name(c, "const")) return poisoned_decl;
|
||
|
||
// Differentiate between global and local attributes, global have visibility
|
||
if (is_global)
|
||
{
|
||
if (!parse_attributes_for_global(c, decl)) return false;
|
||
}
|
||
else
|
||
{
|
||
bool is_cond;
|
||
if (!parse_attributes(c, &decl->attributes, NULL, NULL, &is_cond)) return poisoned_decl;
|
||
decl->is_cond = is_cond;
|
||
}
|
||
|
||
if (is_extern) return decl;
|
||
// Required initializer
|
||
CONSUME_OR_RET(TOKEN_EQ, poisoned_decl);
|
||
if (!parse_decl_initializer(c, decl)) return poisoned_decl;
|
||
RANGE_EXTEND_PREV(decl);
|
||
|
||
return decl;
|
||
}
|
||
|
||
/**
|
||
* var_decl ::= VAR (IDENT '=' expression) | (CT_IDENT ('=' expression)?) | (CT_TYPE_IDENT ('=' expression)?)
|
||
*/
|
||
Decl *parse_var_decl(ParseContext *c)
|
||
{
|
||
// CT variants will naturally be macro only, for runtime variables it is enforced in the semantic
|
||
// analyser. The runtime variables must have an initializer unlike the CT ones.
|
||
advance_and_verify(c, TOKEN_VAR);
|
||
Decl *decl;
|
||
bool is_cond;
|
||
SourceSpan span;
|
||
switch (c->tok)
|
||
{
|
||
case TOKEN_CONST_IDENT:
|
||
PRINT_ERROR_HERE("Constants must be declared using 'const' not 'var'.");
|
||
return poisoned_decl;
|
||
case TOKEN_IDENT:
|
||
decl = decl_new_var_current(c, NULL, VARDECL_LOCAL);
|
||
advance(c);
|
||
if (!parse_attributes(c, &decl->attributes, NULL, NULL, &is_cond)) return poisoned_decl;
|
||
decl->is_cond = is_cond;
|
||
if (!tok_is(c, TOKEN_EQ))
|
||
{
|
||
PRINT_ERROR_HERE("'var' must always have an initial value, or the type cannot be inferred.");
|
||
return poisoned_decl;
|
||
}
|
||
advance_and_verify(c, TOKEN_EQ);
|
||
ASSIGN_EXPR_OR_RET(decl->var.init_expr, parse_expr(c), poisoned_decl);
|
||
break;
|
||
case TOKEN_CT_IDENT:
|
||
case TOKEN_CT_TYPE_IDENT:
|
||
decl = decl_new_var_current(c, NULL, c->tok == TOKEN_CT_IDENT ? VARDECL_LOCAL_CT : VARDECL_LOCAL_CT_TYPE);
|
||
advance(c);
|
||
span = c->span;
|
||
if (!parse_attributes(c, &decl->attributes, NULL, NULL, &is_cond)) return poisoned_decl;
|
||
if (is_cond || decl->attributes)
|
||
{
|
||
print_error_at(span, "Attributes are not allowed on compile time variables.");
|
||
return poisoned_decl;
|
||
}
|
||
if (try_consume(c, TOKEN_EQ))
|
||
{
|
||
ASSIGN_EXPR_OR_RET(decl->var.init_expr, parse_expr(c), poisoned_decl);
|
||
}
|
||
break;
|
||
default:
|
||
PRINT_ERROR_HERE("Expected a compile time variable name ('$Foo' or '$foo').");
|
||
return poisoned_decl;
|
||
}
|
||
return decl;
|
||
}
|
||
|
||
|
||
|
||
// --- Parse parameters & throws & attributes
|
||
|
||
static Expr *parse_overload_from_token(ParseContext *c, TokenType token)
|
||
{
|
||
OperatorOverload overload;
|
||
switch (token)
|
||
{
|
||
case TOKEN_PLUS:
|
||
overload = OVERLOAD_PLUS;
|
||
break;
|
||
case TOKEN_PLUS_ASSIGN:
|
||
overload = OVERLOAD_PLUS_ASSIGN;
|
||
break;
|
||
case TOKEN_MINUS:
|
||
overload = OVERLOAD_MINUS;
|
||
break;
|
||
case TOKEN_MINUS_ASSIGN:
|
||
overload = OVERLOAD_MINUS_ASSIGN;
|
||
break;
|
||
case TOKEN_STAR:
|
||
overload = OVERLOAD_MULTIPLY;
|
||
break;
|
||
case TOKEN_MULT_ASSIGN:
|
||
overload = OVERLOAD_MULTIPLY_ASSIGN;
|
||
break;
|
||
case TOKEN_DIV:
|
||
overload = OVERLOAD_DIVIDE;
|
||
break;
|
||
case TOKEN_DIV_ASSIGN:
|
||
overload = OVERLOAD_DIVIDE_ASSIGN;
|
||
break;
|
||
case TOKEN_MOD:
|
||
overload = OVERLOAD_REMINDER;
|
||
break;
|
||
case TOKEN_MOD_ASSIGN:
|
||
overload = OVERLOAD_REMINDER_ASSIGN;
|
||
break;
|
||
case TOKEN_AMP:
|
||
overload = OVERLOAD_AND;
|
||
break;
|
||
case TOKEN_BIT_AND_ASSIGN:
|
||
overload = OVERLOAD_AND_ASSIGN;
|
||
break;
|
||
case TOKEN_BIT_OR:
|
||
overload = OVERLOAD_OR;
|
||
break;
|
||
case TOKEN_BIT_OR_ASSIGN:
|
||
overload = OVERLOAD_OR_ASSIGN;
|
||
break;
|
||
case TOKEN_BIT_XOR:
|
||
overload = OVERLOAD_XOR;
|
||
break;
|
||
case TOKEN_BIT_XOR_ASSIGN:
|
||
overload = OVERLOAD_XOR_ASSIGN;
|
||
break;
|
||
case TOKEN_SHL:
|
||
overload = OVERLOAD_SHL;
|
||
break;
|
||
case TOKEN_SHL_ASSIGN:
|
||
overload = OVERLOAD_SHL_ASSIGN;
|
||
break;
|
||
case TOKEN_SHR:
|
||
overload = OVERLOAD_SHR;
|
||
break;
|
||
case TOKEN_SHR_ASSIGN:
|
||
overload = OVERLOAD_SHR_ASSIGN;
|
||
break;
|
||
case TOKEN_BIT_NOT:
|
||
overload = OVERLOAD_NEGATE;
|
||
break;
|
||
case TOKEN_EQEQ:
|
||
overload = OVERLOAD_EQUAL;
|
||
break;
|
||
case TOKEN_NOT_EQUAL:
|
||
overload = OVERLOAD_NOT_EQUAL;
|
||
break;
|
||
default:
|
||
UNREACHABLE
|
||
}
|
||
Expr *expr = EXPR_NEW_TOKEN(EXPR_OPERATOR_CHARS);
|
||
expr->resolve_status = RESOLVE_DONE;
|
||
expr->overload_expr = overload;
|
||
advance(c);
|
||
RANGE_EXTEND_PREV(expr);
|
||
return expr;
|
||
}
|
||
/**
|
||
* attribute ::= (AT_IDENT | path_prefix? AT_TYPE_IDENT) attr_params?
|
||
* attr_params ::= '(' attr_param (',' attr_param)* ')'
|
||
* attr_param ::= const_expr | '&' '[' ']' | '[' ']' '='? | '-' | '+' | '/' | '%' | '==' | '<=>' | '<<' | '>>' | '|' | '&' | '^' | '~'
|
||
*/
|
||
bool parse_attribute(ParseContext *c, Attr **attribute_ref, bool expect_eos)
|
||
{
|
||
SourceSpan start_span = c->prev_span;
|
||
Path *path;
|
||
if (!parse_path_prefix(c, &path)) return false;
|
||
|
||
// Something not starting with `@` in attribute position:
|
||
if (!tok_is(c, TOKEN_AT_IDENT) && !tok_is(c, TOKEN_AT_TYPE_IDENT))
|
||
{
|
||
// Started a path? If so hard error
|
||
if (path)
|
||
{
|
||
if (expect_eos)
|
||
{
|
||
print_error_after(start_span, "Expected a ';' here.");
|
||
return false;
|
||
}
|
||
RETURN_PRINT_ERROR_HERE("Expected an attribute name.");
|
||
}
|
||
|
||
// Otherwise assume no attributes
|
||
*attribute_ref = NULL;
|
||
return true;
|
||
}
|
||
|
||
// Create an attribute
|
||
Attr *attr = CALLOCS(Attr);
|
||
attr->name = symstr(c);
|
||
attr->span = c->span;
|
||
attr->path = path;
|
||
|
||
// Pre-defined idents
|
||
if (tok_is(c, TOKEN_AT_IDENT))
|
||
{
|
||
// Error for foo::bar::@inline
|
||
if (path) RETURN_PRINT_ERROR_HERE("Only user-defined attribute names can have a module path prefix.");
|
||
|
||
// Check attribute it exists, theoretically we could defer this to semantic analysis
|
||
AttributeType type = attribute_by_name(attr->name);
|
||
if (type == ATTRIBUTE_NONE) RETURN_PRINT_ERROR_HERE("This is not a known valid attribute name.");
|
||
attr->attr_kind = type;
|
||
}
|
||
else
|
||
{
|
||
attr->is_custom = true;
|
||
}
|
||
advance(c);
|
||
Expr **list = NULL;
|
||
|
||
// Consume the optional (expr | attr_param, ...)
|
||
if (try_consume(c, TOKEN_LPAREN))
|
||
{
|
||
while (1)
|
||
{
|
||
Expr *expr;
|
||
bool next_is_rparen = c->lexer.token_type == TOKEN_RPAREN;
|
||
switch (c->tok)
|
||
{
|
||
case TOKEN_PLUS:
|
||
case TOKEN_MINUS:
|
||
case TOKEN_STAR:
|
||
case TOKEN_DIV:
|
||
case TOKEN_MOD:
|
||
case TOKEN_BIT_NOT:
|
||
case TOKEN_BIT_OR:
|
||
case TOKEN_BIT_XOR:
|
||
case TOKEN_SHL:
|
||
case TOKEN_SHR:
|
||
case TOKEN_EQEQ:
|
||
case TOKEN_NOT_EQUAL:
|
||
case TOKEN_BIT_AND_ASSIGN:
|
||
case TOKEN_BIT_OR_ASSIGN:
|
||
case TOKEN_BIT_XOR_ASSIGN:
|
||
case TOKEN_PLUS_ASSIGN:
|
||
case TOKEN_MINUS_ASSIGN:
|
||
case TOKEN_MULT_ASSIGN:
|
||
case TOKEN_DIV_ASSIGN:
|
||
case TOKEN_MOD_ASSIGN:
|
||
case TOKEN_SHL_ASSIGN:
|
||
case TOKEN_SHR_ASSIGN:
|
||
if (!next_is_rparen) goto PARSE_EXPR;
|
||
expr = parse_overload_from_token(c, c->tok);
|
||
break;
|
||
case TOKEN_AMP:
|
||
if (next_is_rparen)
|
||
{
|
||
expr = parse_overload_from_token(c, c->tok);
|
||
break;
|
||
}
|
||
// &[]
|
||
expr = EXPR_NEW_TOKEN(EXPR_OPERATOR_CHARS);
|
||
expr->resolve_status = RESOLVE_DONE;
|
||
advance(c);
|
||
CONSUME_OR_RET(TOKEN_LBRACKET, false);
|
||
CONSUME_OR_RET(TOKEN_RBRACKET, false);
|
||
expr->overload_expr = OVERLOAD_ELEMENT_REF;
|
||
RANGE_EXTEND_PREV(expr);
|
||
break;
|
||
case TOKEN_LBRACKET:
|
||
// [] and []=
|
||
expr = EXPR_NEW_TOKEN(EXPR_OPERATOR_CHARS);
|
||
expr->resolve_status = RESOLVE_DONE;
|
||
advance(c);
|
||
CONSUME_OR_RET(TOKEN_RBRACKET, false);
|
||
expr->overload_expr = try_consume(c, TOKEN_EQ) ? OVERLOAD_ELEMENT_SET : OVERLOAD_ELEMENT_AT;
|
||
RANGE_EXTEND_PREV(expr);
|
||
break;
|
||
default:
|
||
PARSE_EXPR:
|
||
expr = parse_constant_expr(c);
|
||
if (!expr_ok(expr)) return false;
|
||
break;
|
||
}
|
||
vec_add(list, expr);
|
||
if (try_consume(c, TOKEN_RPAREN)) break;
|
||
CONSUME_OR_RET(TOKEN_COMMA, false);
|
||
}
|
||
}
|
||
|
||
attr->exprs = list;
|
||
*attribute_ref = attr;
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* Parse global attributes, setting visibility defaults from the parent unit.
|
||
*/
|
||
static bool parse_attributes_for_global(ParseContext *c, Decl *decl)
|
||
{
|
||
Visibility visibility = c->unit->default_visibility;
|
||
// Transfer the test / benchmark properties
|
||
if (decl->decl_kind == DECL_FUNC && !decl->func_decl.attr_interface_method && !decl->func_decl.type_parent)
|
||
{
|
||
decl->func_decl.attr_test = c->unit->test_by_default;
|
||
decl->func_decl.attr_benchmark = c->unit->benchmark_by_default;
|
||
}
|
||
decl->is_export = c->unit->export_by_default;
|
||
bool is_builtin = false;
|
||
bool is_cond;
|
||
if (!parse_attributes(c, &decl->attributes, &visibility, decl_needs_prefix(decl) ? &is_builtin : NULL, &is_cond)) return false;
|
||
decl->is_cond = is_cond;
|
||
decl->is_autoimport = is_builtin;
|
||
decl->visibility = visibility;
|
||
return true;
|
||
}
|
||
|
||
static inline bool parse_attribute_list(ParseContext *c, Attr ***attributes_ref, Visibility *visibility_ref, bool *builtin_ref, bool *cond_ref, bool use_comma)
|
||
{
|
||
Visibility visibility = -1; // NOLINT
|
||
if (cond_ref) *cond_ref = false;
|
||
while (1)
|
||
{
|
||
Attr *attr;
|
||
if (!parse_attribute(c, &attr, false)) return false;
|
||
if (!attr) return true;
|
||
Visibility parsed_visibility = -1; // NOLINT
|
||
if (!attr->is_custom)
|
||
{
|
||
// This is important: if we would allow user defined attributes,
|
||
// ordering between visibility of attributes would be complex.
|
||
// since there is little
|
||
bool parsed_builtin = false;
|
||
switch (attr->attr_kind)
|
||
{
|
||
case ATTRIBUTE_PUBLIC:
|
||
parsed_visibility = VISIBLE_PUBLIC;
|
||
break;
|
||
case ATTRIBUTE_PRIVATE:
|
||
parsed_visibility = VISIBLE_PRIVATE;
|
||
break;
|
||
case ATTRIBUTE_LOCAL:
|
||
parsed_visibility = VISIBLE_LOCAL;
|
||
break;
|
||
case ATTRIBUTE_BUILTIN:
|
||
parsed_builtin = true;
|
||
break;
|
||
case ATTRIBUTE_IF:
|
||
if (!cond_ref) RETURN_PRINT_ERROR_AT(false, attr, "'%s' cannot be used here.", attr->name);
|
||
*cond_ref = true;
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
if (parsed_builtin)
|
||
{
|
||
if (!builtin_ref) RETURN_PRINT_ERROR_AT(false, attr, "'@builtin' cannot be used here.");
|
||
*builtin_ref = true;
|
||
continue;
|
||
}
|
||
if (parsed_visibility != -1)
|
||
{
|
||
if (!visibility_ref) RETURN_PRINT_ERROR_AT(false, attr, "'%s' cannot be used here.", attr->name);
|
||
if (visibility != -1) RETURN_PRINT_ERROR_AT(false, attr, "Only a single visibility attribute may be added.");
|
||
*visibility_ref = visibility = parsed_visibility;
|
||
continue;
|
||
}
|
||
if (attr->attr_kind == ATTRIBUTE_TAG) goto ADD;
|
||
}
|
||
const char *name = attr->name;
|
||
FOREACH(Attr *, other_attr, *attributes_ref)
|
||
{
|
||
if (other_attr->name == name) RETURN_PRINT_ERROR_AT(false, attr, "Repeat of attribute '%s' here.", name);
|
||
}
|
||
ADD:
|
||
vec_add(*attributes_ref, attr);
|
||
if (use_comma && !try_consume(c, TOKEN_COMMA)) break;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* attribute_list ::= attribute*
|
||
*
|
||
* Patch visibility and builtin attributes immediately.
|
||
*
|
||
* @return true if parsing succeeded, false if recovery is needed
|
||
*/
|
||
bool parse_attributes(ParseContext *c, Attr ***attributes_ref, Visibility *visibility_ref, bool *builtin_ref, bool *cond_ref)
|
||
{
|
||
return parse_attribute_list(c, attributes_ref, visibility_ref, builtin_ref, cond_ref, false);
|
||
}
|
||
|
||
/**
|
||
* global_declaration ::= TLOCAL? optional_type IDENT (('=' expression)? | (',' IDENT)* opt_attributes) ';'
|
||
*
|
||
* @return true if parsing succeeded
|
||
*/
|
||
static inline Decl *parse_global_declaration(ParseContext *c)
|
||
{
|
||
bool threadlocal = try_consume(c, TOKEN_TLOCAL);
|
||
|
||
ASSIGN_TYPE_OR_RET(TypeInfo *type, parse_optional_type(c), poisoned_decl);
|
||
|
||
if (tok_is(c, TOKEN_CONST_IDENT))
|
||
{
|
||
PRINT_ERROR_HERE("This looks like a constant variable, did you forget 'const'?");
|
||
return poisoned_decl;
|
||
}
|
||
|
||
Decl *decl;
|
||
Decl **decls = NULL;
|
||
// Parse IDENT (',' IDENT)*
|
||
while (true)
|
||
{
|
||
decl = decl_new_var_current(c, type, VARDECL_GLOBAL);
|
||
// Update threadlocal setting
|
||
decl->var.is_threadlocal = threadlocal;
|
||
if (!try_consume(c, TOKEN_IDENT))
|
||
{
|
||
if (token_is_some_ident(c->tok))
|
||
{
|
||
if ((c->tok == TOKEN_TYPE_IDENT || c->tok == TOKEN_CONST_IDENT) &&
|
||
c->lexer.token_type == TOKEN_LPAREN)
|
||
{
|
||
PRINT_ERROR_HERE("This looks like the beginning of a C style function declaration. "
|
||
"Unfortunately it seems to be missing the initial 'fn' and "
|
||
"the function name does not start with a lower case.");
|
||
return poisoned_decl;
|
||
}
|
||
PRINT_ERROR_HERE("I expected a variable name here, but global variables need to start with lower case.");
|
||
return poisoned_decl;
|
||
}
|
||
PRINT_ERROR_HERE("The name of a global variable was expected here");
|
||
return poisoned_decl;
|
||
}
|
||
if (!try_consume(c, TOKEN_COMMA)) break;
|
||
vec_add(decls, decl);
|
||
}
|
||
// Add the last, or we miss it.
|
||
if (decls) vec_add(decls, decl);
|
||
|
||
if (!parse_attributes_for_global(c, decl)) return poisoned_decl;
|
||
|
||
// If we get '=' we can't have multiple idents.
|
||
if (try_consume(c, TOKEN_EQ))
|
||
{
|
||
if (decls)
|
||
{
|
||
PRINT_ERROR_HERE("Initialization is not allowed with multiple declarations.");
|
||
return poisoned_decl;
|
||
}
|
||
if (!parse_decl_initializer(c, decl)) return poisoned_decl;
|
||
}
|
||
else if (!decl->attributes)
|
||
{
|
||
if (tok_is(c, TOKEN_LPAREN) && !threadlocal)
|
||
{
|
||
// Guess we forgot `fn`? -> improve error reporting.
|
||
print_error_at(type->span, "This looks like the beginning of a function declaration but it's missing the initial `fn`. Did you forget it?");
|
||
return poisoned_decl;
|
||
}
|
||
if (tok_is(c, TOKEN_LBRACKET))
|
||
{
|
||
// Maybe we were doing int foo[4] = ...
|
||
PRINT_ERROR_HERE("This looks like a declaration of the format 'int foo[4]' "
|
||
"which is c-style array declaration. In C3, you need to use something like 'int[4] foo' instead.");
|
||
return poisoned_decl;
|
||
}
|
||
}
|
||
CONSUME_EOS_OR_RET(poisoned_decl);
|
||
Attr **attributes = decl->attributes;
|
||
// Copy the attributes to the other variables.
|
||
if (attributes)
|
||
{
|
||
FOREACH(Decl *, d, decls)
|
||
{
|
||
if (d == decl) continue;
|
||
d->attributes = copy_attributes_single(attributes);
|
||
}
|
||
}
|
||
// If we have multiple decls, then we return that as a bundled decl_globals
|
||
if (decls)
|
||
{
|
||
decl = decl_calloc();
|
||
decl->decl_kind = DECL_GROUP;
|
||
decl->decls = decls;
|
||
return decl;
|
||
}
|
||
return decl;
|
||
}
|
||
|
||
|
||
|
||
/**
|
||
* enum_param_decl ::= type IDENT attributes?
|
||
*/
|
||
static inline bool parse_enum_param_decl(ParseContext *c, Decl*** parameters)
|
||
{
|
||
ASSIGN_TYPE_OR_RET(TypeInfo *type, parse_optional_type(c), false);
|
||
if (type->optional) RETURN_PRINT_ERROR_AT(false, type, "Parameters may not be optional.");
|
||
Decl *param = decl_new_var_current(c, type, VARDECL_PARAM);
|
||
if (!try_consume(c, TOKEN_IDENT))
|
||
{
|
||
if (token_is_keyword_ident(c->tok)) RETURN_PRINT_ERROR_HERE("Keywords cannot be used as member names.");
|
||
if (token_is_some_ident(c->tok)) RETURN_PRINT_ERROR_HERE("Expected a name starting with a lower-case letter.");
|
||
RETURN_PRINT_ERROR_HERE("Expected a member name here.");
|
||
}
|
||
if (!parse_attributes(c, ¶m->attributes, NULL, NULL, NULL)) return false;
|
||
vec_add(*parameters, param);
|
||
RANGE_EXTEND_PREV(param);
|
||
return true;
|
||
}
|
||
|
||
static bool parse_next_is_typed_parameter(ParseContext *c, ParameterParseKind parse_kind)
|
||
{
|
||
switch (c->tok)
|
||
{
|
||
case TOKEN_IDENT:
|
||
{
|
||
return peek(c) == TOKEN_SCOPE;
|
||
}
|
||
case TYPE_TOKENS:
|
||
case TOKEN_TYPE_IDENT:
|
||
case TOKEN_CT_EVALTYPE:
|
||
case TOKEN_CT_TYPEOF:
|
||
case TOKEN_CT_TYPEFROM:
|
||
return true;
|
||
case TOKEN_CT_VATYPE:
|
||
return parse_kind == PARAM_PARSE_LAMBDA || parse_kind == PARAM_PARSE_CALL;
|
||
case TOKEN_CT_TYPE_IDENT:
|
||
if (parse_kind == PARAM_PARSE_LAMBDA) return true;
|
||
if (parse_kind != PARAM_PARSE_CALL) return false;
|
||
switch (peek(c))
|
||
{
|
||
case TOKEN_IDENT:
|
||
case TOKEN_HASH_IDENT:
|
||
case TOKEN_CT_IDENT:
|
||
return true;
|
||
default:
|
||
return false;
|
||
}
|
||
default:
|
||
return false;
|
||
}
|
||
}
|
||
|
||
INLINE bool is_end_of_param_list(ParseContext *c)
|
||
{
|
||
return tok_is(c, TOKEN_EOS) || tok_is(c, TOKEN_RPAREN);
|
||
}
|
||
/**
|
||
* parameters ::= (parameter (',' parameter)*)?
|
||
* non_type_ident = IDENT | HASH_IDENT | CT_IDENT
|
||
* parameter ::= type ELLIPSIS? (non_type_ident ('=' expr))?
|
||
* | ELLIPSIS (CT_TYPE_IDENT | non_type_ident ('=' expr)?)?
|
||
*/
|
||
bool parse_parameters(ParseContext *c, Decl ***params_ref, Variadic *variadic, int *vararg_index_ref, ParameterParseKind parse_kind)
|
||
{
|
||
Decl** params = NULL;
|
||
bool var_arg_found = false;
|
||
while (!is_end_of_param_list(c))
|
||
{
|
||
bool ellipsis = try_consume(c, TOKEN_ELLIPSIS);
|
||
|
||
// Check for "raw" variadic arguments. This is allowed on C functions and macros.
|
||
if (ellipsis)
|
||
{
|
||
// In the future maybe this
|
||
if (!is_end_of_param_list(c) && !tok_is(c, TOKEN_COMMA))
|
||
{
|
||
PRINT_ERROR_HERE("Expected ')' here.");
|
||
return false;
|
||
}
|
||
// Variadics might not be allowed
|
||
if (!variadic)
|
||
{
|
||
PRINT_ERROR_LAST("Variadic parameters are not allowed.");
|
||
return false;
|
||
}
|
||
// Check that we only have one variadic parameter.
|
||
if (var_arg_found)
|
||
{
|
||
PRINT_ERROR_LAST("Only a single variadic parameter is allowed.");
|
||
return false;
|
||
}
|
||
// Set the variadic type and insert a dummy argument.
|
||
*variadic = VARIADIC_RAW;
|
||
*vararg_index_ref = (int)vec_size(params);
|
||
var_arg_found = true;
|
||
vec_add(params, NULL);
|
||
if (!try_consume(c, TOKEN_COMMA)) break;
|
||
continue;
|
||
}
|
||
|
||
// Now we have the following possibilities: "foo", "Foo foo", "Foo... foo", "foo...", "Foo"
|
||
TypeInfo *type = NULL;
|
||
if (parse_next_is_typed_parameter(c, parse_kind))
|
||
{
|
||
// Parse the type,
|
||
ASSIGN_TYPE_OR_RET(type, parse_optional_type(c), false);
|
||
ellipsis = try_consume(c, TOKEN_ELLIPSIS);
|
||
// We might have Foo...
|
||
if (ellipsis)
|
||
{
|
||
if (!variadic)
|
||
{
|
||
PRINT_ERROR_HERE("Variadic arguments are not allowed.");
|
||
return false;
|
||
}
|
||
if (var_arg_found)
|
||
{
|
||
print_error_at(extend_span_with_token(type->span, c->prev_span), "Only a single variadic parameter is allowed.");
|
||
return false;
|
||
}
|
||
*variadic = VARIADIC_TYPED;
|
||
}
|
||
}
|
||
// We have parsed the optional type, next get the optional variable name
|
||
VarDeclKind param_kind;
|
||
const char *name = NULL;
|
||
SourceSpan span = c->span;
|
||
bool no_name = false;
|
||
bool ref = false;
|
||
switch (c->tok)
|
||
{
|
||
case TOKEN_CONST_IDENT:
|
||
case TOKEN_CT_CONST_IDENT:
|
||
// We reserve upper case constants for globals.
|
||
PRINT_ERROR_HERE("Parameter names may not be all uppercase.");
|
||
return false;
|
||
case TOKEN_IDENT:
|
||
// normal "foo"
|
||
name = symstr(c);
|
||
param_kind = VARDECL_PARAM;
|
||
advance_and_verify(c, TOKEN_IDENT);
|
||
// Check for "foo..." which defines an implicit "any" vararg
|
||
if (try_consume(c, TOKEN_ELLIPSIS))
|
||
{
|
||
// Did we get Foo... foo...
|
||
if (ellipsis)
|
||
{
|
||
PRINT_ERROR_LAST("Unexpected '...' following a vararg declaration.");
|
||
return false;
|
||
}
|
||
ellipsis = true;
|
||
if (!variadic)
|
||
{
|
||
print_error_at(extend_span_with_token(span, c->span), "Variadic parameters are not allowed.");
|
||
return false;
|
||
}
|
||
// Did we get Foo foo...? If so then that's an error.
|
||
if (type)
|
||
{
|
||
PRINT_ERROR_LAST("For typed varargs '...', needs to appear after the type.");
|
||
return false;
|
||
}
|
||
// This is "foo..."
|
||
*variadic = VARIADIC_ANY;
|
||
// We generate the type as type_any
|
||
type = type_info_new_base(type_any, c->span);
|
||
}
|
||
break;
|
||
case TOKEN_CT_IDENT:
|
||
// ct_var $foo
|
||
name = symstr(c);
|
||
advance_and_verify(c, TOKEN_CT_IDENT);
|
||
// This will catch Type... $foo and $foo..., neither is allowed.
|
||
if (ellipsis || tok_is(c, TOKEN_ELLIPSIS))
|
||
{
|
||
PRINT_ERROR_LAST("Compile time parameters may not be varargs, use untyped macro varargs '...' instead.");
|
||
return false;
|
||
}
|
||
param_kind = VARDECL_PARAM_CT;
|
||
break;
|
||
case TOKEN_AMP:
|
||
// reference &foo
|
||
advance_and_verify(c, TOKEN_AMP);
|
||
name = symstr(c);
|
||
if (!try_consume(c, TOKEN_IDENT))
|
||
{
|
||
PRINT_ERROR_HERE("A regular variable name, e.g. 'foo' was expected after the '&'.");
|
||
return false;
|
||
}
|
||
if (vec_size(params) > 0)
|
||
{
|
||
PRINT_ERROR_HERE("Only the first parameter may use '&'.");
|
||
return false;
|
||
}
|
||
// This will catch Type... &foo and &foo..., neither is allowed.
|
||
if (ellipsis || tok_is(c, TOKEN_ELLIPSIS))
|
||
{
|
||
PRINT_ERROR_LAST("'&foo' parameters may not be followed by '...’");
|
||
return false;
|
||
}
|
||
|
||
// Span includes the "&"
|
||
span = extend_span_with_token(span, c->span);
|
||
ref = true;
|
||
param_kind = VARDECL_PARAM;
|
||
break;
|
||
case TOKEN_HASH_IDENT:
|
||
// expression #foo
|
||
name = symstr(c);
|
||
advance_and_verify(c, TOKEN_HASH_IDENT);
|
||
if (ellipsis || tok_is(c, TOKEN_ELLIPSIS))
|
||
{
|
||
PRINT_ERROR_LAST("Expression parameters may not be varargs, use untyped macro varargs '...' instead.");
|
||
return false;
|
||
}
|
||
param_kind = VARDECL_PARAM_EXPR;
|
||
break;
|
||
// Compile time type $Type
|
||
case TOKEN_CT_TYPE_IDENT:
|
||
name = symstr(c);
|
||
advance_and_verify(c, TOKEN_CT_TYPE_IDENT);
|
||
if (ellipsis || tok_is(c, TOKEN_ELLIPSIS))
|
||
{
|
||
PRINT_ERROR_LAST("Expression parameters may not be varargs, use untyped macro varargs '...' instead.");
|
||
return false;
|
||
}
|
||
param_kind = VARDECL_PARAM_CT_TYPE;
|
||
break;
|
||
case TOKEN_COMMA:
|
||
case TOKEN_EOS:
|
||
case TOKEN_RPAREN:
|
||
// Handle "Type..." and "Type"
|
||
if (!type && !ellipsis)
|
||
{
|
||
print_error_after(c->prev_span, "Expected a parameter.");
|
||
return false;
|
||
}
|
||
no_name = true;
|
||
span = c->prev_span;
|
||
param_kind = VARDECL_PARAM;
|
||
break;
|
||
default:
|
||
if (token_is_keyword(c->tok))
|
||
{
|
||
RETURN_PRINT_ERROR_HERE("'%s' is a keyword and cannot be used as a parameter name.", symstr(c));
|
||
}
|
||
RETURN_PRINT_ERROR_HERE("Expected a parameter.");
|
||
}
|
||
if (type && type->optional)
|
||
{
|
||
RETURN_PRINT_ERROR_AT(false, type, "Parameters may not be optional.");
|
||
}
|
||
Decl *param = decl_new_var(name, span, type, param_kind);
|
||
param->var.type_info = type ? type_infoid(type) : 0;
|
||
param->var.self_addr = ref;
|
||
if (!parse_attributes(c, ¶m->attributes, NULL, NULL, NULL)) return false;
|
||
if (!no_name)
|
||
{
|
||
if (try_consume(c, TOKEN_EQ))
|
||
{
|
||
if (try_consume(c, TOKEN_ELLIPSIS))
|
||
{
|
||
if (parse_kind != PARAM_PARSE_MACRO)
|
||
{
|
||
PRINT_ERROR_HERE("Optional arguments with '...' is only allowed as macro arguments.");
|
||
return poisoned_decl;
|
||
}
|
||
param->var.no_init = true;
|
||
}
|
||
else
|
||
{
|
||
if (!parse_decl_initializer(c, param)) return poisoned_decl;
|
||
}
|
||
}
|
||
}
|
||
if (ellipsis)
|
||
{
|
||
var_arg_found = true;
|
||
param->var.vararg = ellipsis;
|
||
*vararg_index_ref = (int)vec_size(params);
|
||
}
|
||
vec_add(params, param);
|
||
if (!try_consume(c, TOKEN_COMMA)) break;
|
||
}
|
||
*params_ref = params;
|
||
return true;
|
||
}
|
||
|
||
|
||
/**
|
||
* fn_parameter_list ::= '(' parameters ')'
|
||
*/
|
||
static inline bool parse_fn_parameter_list(ParseContext *c, Signature *signature)
|
||
{
|
||
Decl **decls = NULL;
|
||
CONSUME_OR_RET(TOKEN_LPAREN, false);
|
||
Variadic variadic = VARIADIC_NONE;
|
||
int vararg_index = -1;
|
||
if (!parse_parameters(c, &decls, &variadic, &vararg_index, PARAM_PARSE_FUNC)) return false;
|
||
CONSUME_OR_RET(TOKEN_RPAREN, false);
|
||
signature->vararg_index = vararg_index < 0 ? vec_size(decls) : vararg_index;
|
||
signature->params = decls;
|
||
signature->variadic = variadic;
|
||
|
||
return true;
|
||
}
|
||
|
||
// --- Parse types
|
||
|
||
static bool parse_element_contract(ParseContext *c, const char *error)
|
||
{
|
||
AstId contracts = 0;
|
||
if (!parse_contracts(c, &contracts)) return false;
|
||
if (!contracts) return true;
|
||
Ast *ast = astptr(contracts);
|
||
while (ast)
|
||
{
|
||
if (ast->ast_kind != AST_CONTRACT || ast->contract_stmt.kind != CONTRACT_COMMENT)
|
||
{
|
||
RETURN_PRINT_ERROR_AT(false, ast, "No constraints are allowed on %s.", error);
|
||
}
|
||
ast = astptrzero(ast->next);
|
||
}
|
||
return true;
|
||
}
|
||
/**
|
||
* Expect pointer to after '{'
|
||
*
|
||
* struct_body ::= '{' struct_declaration_list '}'
|
||
*
|
||
* struct_declaration_list ::= struct_member_decl+
|
||
*
|
||
* struct_member_decl ::=
|
||
* (type_expression identifier_list opt_attributes ';')
|
||
* | struct_or_union IDENT opt_attributes struct_body
|
||
* | struct_or_union opt_attributes struct_body
|
||
* | BITSTRUCT IDENT ':' type opt_attributes struct_body
|
||
* | BITSTRUCT ':' type opt_attributes struct_body
|
||
*
|
||
* @param c the parse context
|
||
* @param parent the parent of the struct
|
||
*/
|
||
static bool parse_struct_body(ParseContext *c, Decl *parent)
|
||
{
|
||
CONSUME_OR_RET(TOKEN_LBRACE, false);
|
||
|
||
ASSERT(decl_is_struct_type(parent));
|
||
ArrayIndex index = 0;
|
||
while (!tok_is(c, TOKEN_RBRACE))
|
||
{
|
||
if (!parse_element_contract(c, "struct/union members")) return decl_poison(parent);
|
||
TokenType token_type = c->tok;
|
||
if (token_type == TOKEN_STRUCT || token_type == TOKEN_UNION || token_type == TOKEN_BITSTRUCT)
|
||
{
|
||
DeclKind decl_kind = decl_from_token(token_type);
|
||
Decl *member;
|
||
if (peek(c) != TOKEN_IDENT)
|
||
{
|
||
member = decl_new_with_type(NULL, c->span, decl_kind);
|
||
advance(c);
|
||
if (token_is_some_ident(c->tok))
|
||
{
|
||
RETURN_PRINT_ERROR_HERE("The name of an inner struct or union must be a name starting with a lowercase letter.");
|
||
return decl_poison(parent);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
advance(c);
|
||
member = decl_new_with_type(symstr(c), c->span, decl_kind);
|
||
advance_and_verify(c, TOKEN_IDENT);
|
||
}
|
||
member->strukt.parent = declid(parent);
|
||
if (decl_kind == DECL_BITSTRUCT)
|
||
{
|
||
TRY_CONSUME_OR_RET(TOKEN_COLON, "':' followed by bitstruct type (e.g. 'int') was expected here.", poisoned_decl);
|
||
ASSIGN_TYPE_OR_RET(member->strukt.container_type, parse_optional_type_no_generic(c), poisoned_decl);
|
||
if (!parse_attributes_for_global(c, member)) return decl_poison(parent);
|
||
if (!parse_bitstruct_body(c, member)) return decl_poison(parent);
|
||
}
|
||
else
|
||
{
|
||
bool is_cond;
|
||
if (!parse_attributes(c, &member->attributes, NULL, NULL, &is_cond)) return false;
|
||
member->is_cond = true;
|
||
if (!parse_struct_body(c, member)) return decl_poison(parent);
|
||
}
|
||
vec_add(parent->strukt.members, member);
|
||
index++;
|
||
if (index > MAX_MEMBERS)
|
||
{
|
||
RETURN_PRINT_ERROR_AT(false, member, "Can't add another member: the count would exceed maximum of %d elements.", MAX_MEMBERS);
|
||
}
|
||
continue;
|
||
}
|
||
bool was_inline = false;
|
||
if (tok_is(c, TOKEN_INLINE))
|
||
{
|
||
if (parent->decl_kind != DECL_STRUCT)
|
||
{
|
||
RETURN_PRINT_ERROR_HERE("Only structs may have 'inline' elements, did you make a mistake?");
|
||
}
|
||
if (index > 0)
|
||
{
|
||
RETURN_PRINT_ERROR_LAST("Only the first element may be 'inline', did you order your fields wrong?");
|
||
}
|
||
parent->is_substruct = true;
|
||
was_inline = true;
|
||
advance(c);
|
||
}
|
||
ASSIGN_TYPE_OR_RET(TypeInfo *type, parse_type(c), false);
|
||
|
||
unsigned first_member_index = vec_size(parent->strukt.members);
|
||
while (1)
|
||
{
|
||
if (!tok_is(c, TOKEN_IDENT)) RETURN_PRINT_ERROR_HERE("A valid member name was expected here.");
|
||
|
||
Decl *member = decl_new_var_current(c, type, VARDECL_MEMBER);
|
||
vec_add(parent->strukt.members, member);
|
||
index++;
|
||
if (index > MAX_MEMBERS)
|
||
{
|
||
RETURN_PRINT_ERROR_AT(false, member, "Can't add another member: the count would exceed maximum of %d elements.", MAX_MEMBERS);
|
||
}
|
||
advance(c);
|
||
bool is_cond;
|
||
if (!parse_attributes(c, &member->attributes, NULL, NULL, &is_cond)) return false;
|
||
member->is_cond = true;
|
||
if (!try_consume(c, TOKEN_COMMA)) break;
|
||
if (was_inline)
|
||
{
|
||
RETURN_PRINT_ERROR_AT(false, member, "'inline' can only be applied to a single member, so please define it on its own line.");
|
||
}
|
||
if (token_is_any_type(c->tok))
|
||
{
|
||
RETURN_PRINT_ERROR_LAST("Did you accidentally use ',' rather than ';' between your declarations?");
|
||
}
|
||
}
|
||
Decl **members = parent->strukt.members;
|
||
unsigned last_index = vec_size(members) - 1;
|
||
if (last_index != first_member_index)
|
||
{
|
||
Decl *last_member = members[last_index];
|
||
Attr **attributes = last_member->attributes;
|
||
if (attributes)
|
||
{
|
||
// Copy attributes
|
||
bool is_cond = last_member->is_cond;
|
||
for (unsigned i = first_member_index; i < last_index; i++)
|
||
{
|
||
Decl *member = members[i];
|
||
if (is_cond) member->is_cond = true;
|
||
ASSERT(!member->attributes);
|
||
member->attributes = copy_attributes_single(attributes);
|
||
}
|
||
}
|
||
}
|
||
CONSUME_EOS_OR_RET(false);
|
||
}
|
||
advance_and_verify(c, TOKEN_RBRACE);
|
||
return true;
|
||
}
|
||
|
||
|
||
/**
|
||
* typedef_declaration ::= 'typedef' TYPE_IDENT opt_interfaces attributes? '=' 'inline'? type ';'
|
||
*/
|
||
static inline Decl *parse_typedef_declaration(ParseContext *c)
|
||
{
|
||
advance_and_verify(c, TOKEN_TYPEDEF);
|
||
|
||
Decl *decl = decl_new_with_type(symstr(c), c->span, DECL_DISTINCT);
|
||
|
||
if (!consume_type_name(c, "distinct type")) return poisoned_decl;
|
||
if (!parse_interface_impls(c, &decl->interfaces)) return poisoned_decl;
|
||
|
||
if (!parse_attributes_for_global(c, decl)) return poisoned_decl;
|
||
|
||
decl->type->type_kind = TYPE_DISTINCT;
|
||
decl->decl_kind = DECL_DISTINCT;
|
||
|
||
CONSUME_OR_RET(TOKEN_EQ, poisoned_decl);
|
||
|
||
decl->is_substruct = try_consume(c, TOKEN_INLINE);
|
||
|
||
if (tok_is(c, TOKEN_FN))
|
||
{
|
||
PRINT_ERROR_HERE("A distinct type cannot define a new function type, but you can make a distinct type from an existing function type, e.g. `alias FooFn = fn void(); typedef Bar = FooFn;`");
|
||
return poisoned_decl;
|
||
}
|
||
// 2. Now parse the type which we know is here.
|
||
ASSIGN_TYPE_OR_RET(decl->distinct, parse_type(c), poisoned_decl);
|
||
|
||
ASSERT(!tok_is(c, TOKEN_LBRACE));
|
||
|
||
RANGE_EXTEND_PREV(decl);
|
||
CONSUME_EOS_OR_RET(poisoned_decl);
|
||
return decl;
|
||
}
|
||
|
||
/**
|
||
* struct_declaration ::= struct_or_union TYPE_IDENT opt_interfaces opt_attributes struct_body
|
||
*/
|
||
static inline Decl *parse_struct_declaration(ParseContext *c)
|
||
{
|
||
TokenType type = c->tok;
|
||
|
||
advance(c);
|
||
const char* type_name = type == TOKEN_STRUCT ? "struct" : "union";
|
||
|
||
Decl *decl = decl_new_with_type(symstr(c), c->span, decl_from_token(type));
|
||
|
||
if (!consume_type_name(c, type_name)) return poisoned_decl;
|
||
if (!parse_interface_impls(c, &decl->interfaces)) return poisoned_decl;
|
||
if (!parse_attributes_for_global(c, decl)) return poisoned_decl;
|
||
|
||
if (!parse_struct_body(c, decl)) return poisoned_decl;
|
||
|
||
DEBUG_LOG("Parsed %s %s completely.", type_name, decl->name);
|
||
return decl;
|
||
}
|
||
|
||
/**
|
||
* bitstruct_body ::= '{' bitstruct_def* | bitstruct_simple_def* '}'
|
||
*
|
||
* bitstruct_def ::= base_type IDENT ':' constant_expr (DOTDOT constant_expr)? ';'
|
||
* bitstruct_simple_def ::= base_type IDENT ';'
|
||
*/
|
||
static inline bool parse_bitstruct_body(ParseContext *c, Decl *decl)
|
||
{
|
||
CONSUME_OR_RET(TOKEN_LBRACE, false);
|
||
|
||
bool is_consecutive = false;
|
||
while (!try_consume(c, TOKEN_RBRACE))
|
||
{
|
||
ASSIGN_TYPE_OR_RET(TypeInfo *type, parse_base_type(c), false);
|
||
Decl *member_decl = decl_new_var_current(c, type, VARDECL_BITMEMBER);
|
||
|
||
if (!try_consume(c, TOKEN_IDENT))
|
||
{
|
||
if (try_consume(c, TOKEN_CONST_IDENT) || try_consume(c, TOKEN_TYPE_IDENT))
|
||
{
|
||
PRINT_ERROR_LAST("Expected a field name with an initial lower case.");
|
||
return false;
|
||
}
|
||
PRINT_ERROR_HERE("Expected a field name at this position.");
|
||
return false;
|
||
}
|
||
if (tok_is(c, TOKEN_EOS) || tok_is(c, TOKEN_AT_IDENT))
|
||
{
|
||
if (!is_consecutive)
|
||
{
|
||
if (decl->strukt.members)
|
||
{
|
||
RETURN_PRINT_ERROR_AT(false, member_decl, "Bitstructs either have bit ranges for all members, or no members have ranges – mixing is not permitted. Either add a range to this member or remove ranges from the other member(s).");
|
||
}
|
||
is_consecutive = true;
|
||
}
|
||
bool is_cond = false;
|
||
if (!parse_attributes(c, &member_decl->attributes, NULL, NULL, &is_cond)) return false;
|
||
member_decl->is_cond = is_cond;
|
||
CONSUME_OR_RET(TOKEN_EOS, false);
|
||
unsigned index = vec_size(decl->strukt.members);
|
||
member_decl->var.start_bit = index;
|
||
member_decl->var.end_bit = index;
|
||
vec_add(decl->strukt.members, member_decl);
|
||
continue;
|
||
}
|
||
CONSUME_OR_RET(TOKEN_COLON, false);
|
||
ASSIGN_EXPR_OR_RET(member_decl->var.start, parse_constant_expr(c), false);
|
||
member_decl->var.bit_is_expr = true;
|
||
if (try_consume(c, TOKEN_DOTDOT))
|
||
{
|
||
ASSIGN_EXPR_OR_RET(member_decl->var.end, parse_constant_expr(c), false);
|
||
}
|
||
else
|
||
{
|
||
member_decl->var.end = NULL;
|
||
}
|
||
bool is_cond = false;
|
||
if (!parse_attributes(c, &member_decl->attributes, NULL, NULL, &is_cond)) return false;
|
||
member_decl->is_cond = is_cond;
|
||
CONSUME_EOS_OR_RET(false);
|
||
if (is_consecutive)
|
||
{
|
||
RETURN_PRINT_ERROR_AT(false, member_decl->var.start, "Bitstructs either have bit ranges for all members, or no members have ranges – mixing is not permitted. Either remove this range, or add ranges to all other members.");
|
||
}
|
||
vec_add(decl->strukt.members, member_decl);
|
||
}
|
||
decl->strukt.consecutive = is_consecutive;
|
||
return true;
|
||
}
|
||
|
||
INLINE bool parse_interface_body(ParseContext *c, Decl *interface)
|
||
{
|
||
CONSUME_OR_RET(TOKEN_LBRACE, false);
|
||
Decl **fns = NULL;
|
||
while (!try_consume(c, TOKEN_RBRACE))
|
||
{
|
||
AstId contracts = 0;
|
||
if (!parse_contracts(c, &contracts)) return poisoned_decl;
|
||
if (!tok_is(c, TOKEN_FN))
|
||
{
|
||
RETURN_PRINT_ERROR_HERE("Interfaces can only have function declarations, and they must start with 'fn' as usual.");
|
||
}
|
||
ASSIGN_DECL_OR_RET(Decl *interface_fn, parse_func_definition(c, contracts, FUNC_PARSE_INTERFACE), false);
|
||
vec_add(fns, interface_fn);
|
||
}
|
||
interface->interface_methods = fns;
|
||
return true;
|
||
}
|
||
/**
|
||
* interface_declaration ::= 'interface' TYPE_IDENT ':' (TYPE_IDENT (',' TYPE_IDENT) interface_body
|
||
*/
|
||
static inline Decl *parse_interface_declaration(ParseContext *c)
|
||
{
|
||
advance_and_verify(c, TOKEN_INTERFACE);
|
||
Decl *decl = decl_new_with_type(symstr(c), c->span, DECL_INTERFACE);
|
||
if (!consume_type_name(c, "interface")) return poisoned_decl;
|
||
TypeInfo **parents = NULL;
|
||
if (try_consume(c, TOKEN_COLON))
|
||
{
|
||
do
|
||
{
|
||
ASSIGN_TYPE_OR_RET(TypeInfo *type, parse_optional_type_no_generic(c), poisoned_decl);
|
||
vec_add(parents, type);
|
||
} while (try_consume(c, TOKEN_COMMA));
|
||
}
|
||
decl->interfaces = parents;
|
||
if (!parse_interface_body(c, decl)) return poisoned_decl;
|
||
return decl;
|
||
}
|
||
/**
|
||
* bitstruct_declaration ::= 'bitstruct' TYPE_IDENT ':' type bitstruct_body
|
||
*/
|
||
static inline Decl *parse_bitstruct_declaration(ParseContext *c)
|
||
{
|
||
advance_and_verify(c, TOKEN_BITSTRUCT);
|
||
|
||
Decl *decl = decl_new_with_type(symstr(c), c->span, DECL_BITSTRUCT);
|
||
|
||
if (!consume_type_name(c, "bitstruct")) return poisoned_decl;
|
||
|
||
if (!parse_interface_impls(c, &decl->interfaces)) return poisoned_decl;
|
||
|
||
TRY_CONSUME_OR_RET(TOKEN_COLON, "':' followed by bitstruct type (e.g. 'int') was expected here.", poisoned_decl);
|
||
|
||
ASSIGN_TYPE_OR_RET(TypeInfo *type = decl->strukt.container_type, parse_optional_type_no_generic(c), poisoned_decl);
|
||
|
||
if (type->optional) RETURN_PRINT_ERROR_AT(poisoned_decl, type, "A bitstruct can't have an optional type.");
|
||
|
||
if (!parse_attributes_for_global(c, decl)) return poisoned_decl;
|
||
|
||
if (!parse_bitstruct_body(c, decl)) return poisoned_decl;
|
||
|
||
return decl;
|
||
|
||
}
|
||
|
||
static inline Decl *parse_top_level_const_declaration(ParseContext *c, bool is_extern)
|
||
{
|
||
ASSIGN_DECL_OR_RET(Decl *decl, parse_const_declaration(c, true, is_extern), poisoned_decl);
|
||
CONSUME_EOS_OR_RET(poisoned_decl);
|
||
return decl;
|
||
}
|
||
|
||
|
||
/**
|
||
* macro_params ::= parameters? (EOS trailing_block_param)?
|
||
*
|
||
* trailing_block_param ::= AT_IDENT ( '(' parameters? ')' )?
|
||
*/
|
||
static bool parse_macro_params(ParseContext *c, Decl *macro)
|
||
{
|
||
CONSUME_OR_RET(TOKEN_LPAREN, false);
|
||
|
||
// Parse the regular parameters.
|
||
Variadic variadic = VARIADIC_NONE;
|
||
int vararg_index = -1;
|
||
Decl **params = NULL;
|
||
if (!parse_parameters(c, ¶ms, &variadic, &vararg_index, PARAM_PARSE_MACRO)) return false;
|
||
macro->func_decl.signature.params = params;
|
||
macro->func_decl.signature.vararg_index = vararg_index < 0 ? vec_size(params) : vararg_index;
|
||
macro->func_decl.signature.variadic = variadic;
|
||
|
||
// Do we have trailing block parameters?
|
||
if (try_consume(c, TOKEN_EOS))
|
||
{
|
||
// Consume AT_IDENT
|
||
Decl *body_param = decl_new(DECL_BODYPARAM, symstr(c), c->span);
|
||
TRY_CONSUME_OR_RET(TOKEN_AT_IDENT, "Expected an ending ')' or a block parameter on the format '@block(...).", false);
|
||
if (try_consume(c, TOKEN_LPAREN))
|
||
{
|
||
if (!parse_parameters(c, &body_param->body_params, NULL, NULL, PARAM_PARSE_BODY)) return false;
|
||
CONSUME_OR_RET(TOKEN_RPAREN, false);
|
||
}
|
||
macro->func_decl.body_param = declid(body_param);
|
||
}
|
||
else
|
||
{
|
||
macro->func_decl.body_param = 0;
|
||
}
|
||
CONSUME_OR_RET(TOKEN_RPAREN, false);
|
||
return true;
|
||
}
|
||
|
||
static inline void decl_add_type(Decl *decl, TypeKind kind)
|
||
{
|
||
Type *type = type_new(kind, decl->name);
|
||
type->canonical = type;
|
||
type->decl = decl;
|
||
decl->type = type;
|
||
}
|
||
/**
|
||
* typedef_declaration ::= ALIAS TYPE_IDENT attributes? '=' typedef_type ';'
|
||
*
|
||
* typedef_type ::= func_typedef | type generic_params?
|
||
* func_typedef ::= 'fn' optional_type parameter_type_list
|
||
*/
|
||
static inline Decl *parse_alias_type(ParseContext *c)
|
||
{
|
||
advance_and_verify(c, TOKEN_ALIAS);
|
||
|
||
Decl *decl = decl_new(DECL_POISONED, symstr(c), c->span);
|
||
DEBUG_LOG("Parse def %s", decl->name);
|
||
if (!try_consume(c, TOKEN_TYPE_IDENT))
|
||
{
|
||
if (token_is_any_type(c->tok))
|
||
{
|
||
PRINT_ERROR_HERE("'%s' is the name of a built-in type and can't be used as an alias.",
|
||
token_type_to_string(c->tok));
|
||
return poisoned_decl;
|
||
}
|
||
if (token_is_some_ident(c->tok))
|
||
{
|
||
PRINT_ERROR_HERE("The type name must start with an uppercase letter followed by at least 1 lowercase letter.");
|
||
return poisoned_decl;
|
||
}
|
||
PRINT_ERROR_HERE("A type name was expected here.");
|
||
return poisoned_decl;
|
||
}
|
||
|
||
if (!parse_attributes_for_global(c, decl)) return poisoned_decl;
|
||
|
||
CONSUME_OR_RET(TOKEN_EQ, poisoned_decl);
|
||
|
||
// 1. Did we have `fn`? In that case it's a function pointer.
|
||
if (try_consume(c, TOKEN_FN))
|
||
{
|
||
decl->decl_kind = DECL_TYPEDEF;
|
||
decl_add_type(decl, TYPE_TYPEDEF);
|
||
decl->type_alias_decl.is_func = true;
|
||
Decl *decl_type = decl_new(DECL_FNTYPE, decl->name, c->prev_span);
|
||
decl->type_alias_decl.decl = decl_type;
|
||
ASSIGN_TYPE_OR_RET(TypeInfo *type_info, parse_optional_type(c), poisoned_decl);
|
||
decl_type->fntype_decl.rtype = type_infoid(type_info);
|
||
if (!parse_fn_parameter_list(c, &(decl_type->fntype_decl)))
|
||
{
|
||
return poisoned_decl;
|
||
}
|
||
if (!parse_attributes(c, &decl_type->attributes, NULL, NULL, NULL)) return poisoned_decl;
|
||
RANGE_EXTEND_PREV(decl_type);
|
||
RANGE_EXTEND_PREV(decl);
|
||
CONSUME_EOS_OR_RET(poisoned_decl);
|
||
return decl;
|
||
}
|
||
|
||
// 2. Now parse the type which we know is here.
|
||
|
||
ASSIGN_EXPR_OR_RET(Expr *expr, parse_expr(c), poisoned_decl);
|
||
TypeInfo *type_info;
|
||
switch (expr->expr_kind)
|
||
{
|
||
case EXPR_TYPEINFO:
|
||
type_info = expr->type_expr;
|
||
break;
|
||
case EXPR_UNRESOLVED_IDENTIFIER:
|
||
if (expr->unresolved_ident_expr.is_const)
|
||
{
|
||
print_error_at(decl->span, "A constant may not have a type name alias, it must have an all caps name.");
|
||
}
|
||
else
|
||
{
|
||
print_error_at(decl->span, "An identifier may not be aliased with type name, it must start with a lower case letter.");
|
||
}
|
||
return poisoned_decl;
|
||
default:
|
||
PRINT_ERROR_HERE("Expected a type to alias here.");
|
||
return poisoned_decl;
|
||
}
|
||
ASSERT(!tok_is(c, TOKEN_LBRACE));
|
||
|
||
decl->type_alias_decl.type_info = type_info;
|
||
decl->type_alias_decl.is_func = false;
|
||
decl->decl_kind = DECL_TYPEDEF;
|
||
decl_add_type(decl, TYPE_TYPEDEF);
|
||
if (type_info->kind == TYPE_INFO_IDENTIFIER && type_info->resolve_status == RESOLVE_NOT_DONE
|
||
&& type_info->unresolved.name == decl->name)
|
||
{
|
||
decl->type_alias_decl.is_redef = true;
|
||
}
|
||
|
||
RANGE_EXTEND_PREV(decl);
|
||
CONSUME_EOS_OR_RET(poisoned_decl);
|
||
return decl;
|
||
}
|
||
|
||
/**
|
||
* define_ident ::= 'alias' 'module' IDENT = path
|
||
*/
|
||
static inline Decl *parse_alias_module(ParseContext *c, Decl *decl, TokenType token_type)
|
||
{
|
||
advance_and_verify(c, TOKEN_MODULE);
|
||
|
||
if (token_type != TOKEN_IDENT)
|
||
{
|
||
PRINT_ERROR_AT(decl, "A (lower case) module name was expected here.");
|
||
return poisoned_decl;
|
||
}
|
||
|
||
if (!str_is_valid_lowercase_name(decl->name))
|
||
{
|
||
PRINT_ERROR_AT(decl, "The module name must be all lower case.");
|
||
return poisoned_decl;
|
||
}
|
||
|
||
decl->decl_kind = DECL_ALIAS_PATH;
|
||
|
||
Path *path = parse_module_path(c);
|
||
if (!path) return poisoned_decl;
|
||
|
||
decl->module_alias_decl.alias_path = path;
|
||
|
||
RANGE_EXTEND_PREV(decl);
|
||
CONSUME_EOS_OR_RET(poisoned_decl);
|
||
return decl;
|
||
}
|
||
/**
|
||
* define_ident ::= 'alias' (IDENT | CONST_IDENT | AT_IDENT) attributes? '=' (('module' path) | identifier_alias generic_params?)
|
||
*
|
||
* identifier_alias ::= path? (IDENT | CONST_IDENT | AT_IDENT)
|
||
*/
|
||
static inline Decl *parse_alias_ident(ParseContext *c)
|
||
{
|
||
// 1. Store the beginning of the "alias".
|
||
advance_and_verify(c, TOKEN_ALIAS);
|
||
|
||
// 2. At this point we expect an ident or a const token.
|
||
// since the Type is handled.
|
||
TokenType alias_type = c->tok;
|
||
if (alias_type != TOKEN_IDENT && alias_type != TOKEN_CONST_IDENT && alias_type != TOKEN_AT_IDENT)
|
||
{
|
||
if (token_is_keyword_ident(alias_type) && alias_type != TOKEN_FN)
|
||
{
|
||
PRINT_ERROR_HERE("'%s' is a reserved keyword, try another name.", token_type_to_string(alias_type));
|
||
}
|
||
else if (alias_type == TOKEN_TYPE_IDENT)
|
||
{
|
||
PRINT_ERROR_HERE("A variable, constant or attribute name was expected here. If you want to define a new type, use 'def' instead.");
|
||
}
|
||
else
|
||
{
|
||
PRINT_ERROR_HERE("A type, variable, constant or attribute name was expected here.");
|
||
}
|
||
return poisoned_decl;
|
||
}
|
||
|
||
// 3. Set up the "define".
|
||
Decl *decl = decl_new(DECL_ALIAS, symstr(c), c->span);
|
||
|
||
// 4. Advance and consume the '='
|
||
advance(c);
|
||
|
||
if (!parse_attributes_for_global(c, decl)) return poisoned_decl;
|
||
|
||
CONSUME_OR_RET(TOKEN_EQ, poisoned_decl);
|
||
|
||
if (tok_is(c, TOKEN_MODULE))
|
||
{
|
||
return parse_alias_module(c, decl, alias_type);
|
||
}
|
||
if (decl->name == kw_main)
|
||
{
|
||
RETURN_PRINT_ERROR_AT(poisoned_decl, decl, "'main' is reserved and cannot be used as an alias.");
|
||
}
|
||
|
||
if (tok_is(c, TOKEN_FN))
|
||
{
|
||
RETURN_PRINT_ERROR_AT(poisoned_decl, decl, "This looks like you're declaring a function type alias, and such an alias must have a valid type name, like 'Callback'.");
|
||
}
|
||
|
||
ASSIGN_EXPR_OR_RET(decl->define_decl.alias_expr, parse_expr(c), poisoned_decl);
|
||
|
||
|
||
RANGE_EXTEND_PREV(decl);
|
||
CONSUME_EOS_OR_RET(poisoned_decl);
|
||
return decl;
|
||
}
|
||
|
||
/**
|
||
* define_attribute ::= 'attrdef' AT_TYPE_IDENT '(' parameter_list ')' opt_attributes '=' '{' attributes? '}' ';'
|
||
*/
|
||
static inline Decl *parse_attrdef(ParseContext *c)
|
||
{
|
||
advance_and_verify(c, TOKEN_ATTRDEF);
|
||
|
||
Decl *decl = decl_new(DECL_ATTRIBUTE, symstr(c), c->span);
|
||
|
||
TRY_CONSUME_OR_RET(TOKEN_AT_TYPE_IDENT, "Expected an attribute type name, like '@MyAttr'.", poisoned_decl);
|
||
|
||
if (try_consume(c, TOKEN_LPAREN))
|
||
{
|
||
if (tok_is(c, TOKEN_RPAREN))
|
||
{
|
||
print_error_at(c->prev_span, "At least one parameter was expected after '(' - try removing the '()'.");
|
||
return poisoned_decl;
|
||
}
|
||
if (!parse_parameters(c, &decl->attr_decl.params, NULL, NULL, PARAM_PARSE_ATTR)) return poisoned_decl;
|
||
CONSUME_OR_RET(TOKEN_RPAREN, poisoned_decl);
|
||
}
|
||
|
||
Attr **attributes = NULL;
|
||
if (!parse_attributes_for_global(c, decl)) return poisoned_decl;
|
||
|
||
// Empty
|
||
if (try_consume(c, TOKEN_EOS)) return decl;
|
||
|
||
CONSUME_OR_RET(TOKEN_EQ, poisoned_decl);
|
||
if (tok_is(c, TOKEN_EOS))
|
||
{
|
||
PRINT_ERROR_HERE("Expected a list of attributes.");
|
||
return poisoned_decl;
|
||
}
|
||
|
||
bool is_cond;
|
||
bool is_builtin = false;
|
||
if (!parse_attribute_list(c, &attributes, NULL, decl_needs_prefix(decl) ? &is_builtin : NULL, &is_cond, true)) return poisoned_decl;
|
||
decl->attr_decl.attrs = attributes;
|
||
CONSUME_EOS_OR_RET(poisoned_decl);
|
||
return decl;
|
||
}
|
||
|
||
/**
|
||
* define_decl ::= ALIAS define_type_body
|
||
*/
|
||
static inline Decl *parse_alias(ParseContext *c)
|
||
{
|
||
switch (peek(c))
|
||
{
|
||
case TOKEN_TYPE_IDENT:
|
||
return parse_alias_type(c);
|
||
default:
|
||
return parse_alias_ident(c);
|
||
}
|
||
}
|
||
|
||
static inline bool parse_is_macro_name(ParseContext *c)
|
||
{
|
||
return (tok_is(c, TOKEN_IDENT) && peek(c) != TOKEN_SCOPE) || tok_is(c, TOKEN_AT_IDENT);
|
||
}
|
||
|
||
/**
|
||
* func_header ::= optional_type (type '.')? IDENT
|
||
* macro_header ::= optional_type? (type '.')? (IDENT | MACRO_IDENT)
|
||
*/
|
||
static inline bool parse_func_macro_header(ParseContext *c, Decl *decl)
|
||
{
|
||
TypeInfo *rtype = NULL;
|
||
TypeInfo *method_type = NULL;
|
||
|
||
bool is_macro = decl->decl_kind == DECL_MACRO;
|
||
|
||
// 1. If we have a macro and see the name, we're done.
|
||
if (is_macro && parse_is_macro_name(c))
|
||
{
|
||
goto RESULT;
|
||
}
|
||
|
||
// 2. Now we must have a type - either that is the return type or the method type.
|
||
if (is_macro && token_is_keyword(c->tok) && c->lexer.token_type == TOKEN_LPAREN)
|
||
{
|
||
RETURN_PRINT_ERROR_HERE("This is a reserved keyword and can't be used as a macro name.");
|
||
}
|
||
ASSIGN_TYPE_OR_RET(rtype, parse_optional_type(c), false);
|
||
|
||
// 4. We might have a type here, if so then we read it.
|
||
if (!tok_is(c, TOKEN_DOT) && !parse_is_macro_name(c) && !token_is_keyword(c->tok))
|
||
{
|
||
ASSIGN_TYPE_OR_RET(method_type, parse_type(c), false);
|
||
}
|
||
|
||
// 5. If we have a dot here, then we need to interpret this as method function.
|
||
if (try_consume(c, TOKEN_DOT))
|
||
{
|
||
// 5a. What if we don't have a method type?
|
||
if (!method_type)
|
||
{
|
||
// 5b. If the rtype is not optional or the return type was an optional, then this is an error.
|
||
if (!is_macro || rtype->optional)
|
||
{
|
||
PRINT_ERROR_LAST("This looks like you are declaring a method without a return type?");
|
||
return false;
|
||
}
|
||
method_type = rtype;
|
||
rtype = NULL;
|
||
}
|
||
}
|
||
else if (method_type)
|
||
{
|
||
// 5d. A method type but no dot is also wrong.
|
||
if (method_type->kind == TYPE_INFO_IDENTIFIER && tok_is(c, TOKEN_LPAREN))
|
||
{
|
||
if (method_type->resolve_status == RESOLVE_DONE && !type_is_user_defined(method_type->type))
|
||
{
|
||
RETURN_PRINT_ERROR_AT(false, method_type, "A function name may not use a reserved type name like '%s'.", method_type->type->name);
|
||
}
|
||
RETURN_PRINT_ERROR_AT(false, method_type, "A function name must start with lower case, as names starting with upper case are reserved for types.");
|
||
}
|
||
RETURN_PRINT_ERROR_AT(false, method_type, "There is unexpectedly a type after the return type, did you forget a '.'?");
|
||
}
|
||
RESULT:
|
||
decl->name = symstr(c);
|
||
decl->span = c->span;
|
||
if (token_is_keyword(c->tok) && c->lexer.token_type == TOKEN_LPAREN)
|
||
{
|
||
RETURN_PRINT_ERROR_HERE("This is a reserved keyword and can't be used as a %s name.", is_macro ? "macro" : "function");
|
||
}
|
||
|
||
if (is_macro && c->tok != TOKEN_IDENT && c->tok != TOKEN_AT_IDENT)
|
||
{
|
||
print_error_at(c->span, "Expected a macro name here, e.g. '@someName' or 'someName'.");
|
||
return false;
|
||
}
|
||
if (!is_macro && c->tok != TOKEN_IDENT)
|
||
{
|
||
print_error_at(c->span, "Expected a function name here, e.g. 'someName'.");
|
||
return false;
|
||
}
|
||
advance(c);
|
||
decl->func_decl.signature.rtype = rtype ? type_infoid(rtype) : 0;
|
||
decl->func_decl.signature.is_macro = is_macro;
|
||
decl->func_decl.signature.is_at_macro = decl->name[0] == '@';
|
||
decl->func_decl.type_parent = method_type ? type_infoid(method_type) : 0;
|
||
return true;
|
||
}
|
||
|
||
|
||
/**
|
||
* macro ::= MACRO macro_header '(' macro_params ')' opt_attributes macro_body
|
||
* macro_body ::= IMPLIES expression ';' | compound_statement
|
||
*/
|
||
static inline Decl *parse_macro_declaration(ParseContext *c, AstId docs)
|
||
{
|
||
advance_and_verify(c, TOKEN_MACRO);
|
||
|
||
Decl *decl = decl_calloc();
|
||
decl->decl_kind = DECL_MACRO;
|
||
decl->func_decl.docs = docs;
|
||
if (!parse_func_macro_header(c, decl)) return poisoned_decl;
|
||
if (!parse_macro_params(c, decl)) return poisoned_decl;
|
||
if (!parse_attributes_for_global(c, decl)) return poisoned_decl;
|
||
if (tok_is(c, TOKEN_IMPLIES))
|
||
{
|
||
ASSIGN_ASTID_OR_RET(decl->func_decl.body,
|
||
parse_short_body(c, decl->func_decl.signature.rtype, true), poisoned_decl);
|
||
return decl;
|
||
}
|
||
ASSIGN_ASTID_OR_RET(decl->func_decl.body, parse_compound_stmt(c), poisoned_decl);
|
||
return decl;
|
||
}
|
||
|
||
static inline Decl *parse_fault(ParseContext *c)
|
||
{
|
||
if (!parse_element_contract(c, "faults")) return poisoned_decl;
|
||
Decl *decl = decl_new(DECL_FAULT, symstr(c), c->span);
|
||
if (!consume_const_name(c, "fault")) return poisoned_decl;
|
||
if (!parse_attributes_for_global(c, decl)) return poisoned_decl;
|
||
return decl;
|
||
}
|
||
|
||
/**
|
||
* faultdef_declaration ::= FAULTDEF CONST_IDENT (',' CONST_IDENT)* ','? ';'
|
||
*/
|
||
static inline Decl *parse_faultdef_declaration(ParseContext *c)
|
||
{
|
||
advance_and_verify(c, TOKEN_FAULTDEF);
|
||
|
||
if (c->lexer.token_type == TOKEN_EOS)
|
||
{
|
||
ASSIGN_DECL_OR_RET(Decl *decl, parse_fault(c), poisoned_decl);
|
||
CONSUME_EOS_OR_RET(poisoned_decl);
|
||
return decl;
|
||
}
|
||
Decl **decls = NULL;
|
||
while (!try_consume(c, TOKEN_EOS))
|
||
{
|
||
ASSIGN_DECL_OR_RET(Decl *decl, parse_fault(c), poisoned_decl);
|
||
vec_add(decls, decl);
|
||
if (try_consume(c, TOKEN_COMMA)) continue;
|
||
CONSUME_OR_RET(TOKEN_EOS, poisoned_decl);
|
||
break;
|
||
}
|
||
if (!decls)
|
||
{
|
||
PRINT_ERROR_LAST("Expected the name of a fault here.");
|
||
return poisoned_decl;
|
||
}
|
||
Decl *decl = decl_calloc();
|
||
decl->decl_kind = DECL_GROUP;
|
||
decl->decl_list = decls;
|
||
return decl;
|
||
}
|
||
|
||
/**
|
||
* enum_param_list ::= '(' enum_param* ')'
|
||
*/
|
||
static inline bool parse_enum_param_list(ParseContext *c, Decl*** parameters_ref, ArrayIndex *inline_index)
|
||
{
|
||
// If no left parenthesis we're done.
|
||
if (!try_consume(c, TOKEN_LPAREN)) return true;
|
||
|
||
ArrayIndex index = -1;
|
||
bool has_inline = !inline_index;
|
||
while (!try_consume(c, TOKEN_RPAREN))
|
||
{
|
||
index++;
|
||
bool is_inline = try_consume(c, TOKEN_INLINE);
|
||
if (is_inline)
|
||
{
|
||
if (!compiler.build.old_enums)
|
||
{
|
||
RETURN_PRINT_ERROR_HERE("Inline parameters are not allowed for enums.");
|
||
}
|
||
if (has_inline) RETURN_PRINT_ERROR_HERE("An enum cannot combine an inline value and a inline parameter.");
|
||
if (*inline_index > -1) RETURN_PRINT_ERROR_HERE("An enum may only have one inline parameter.");
|
||
*inline_index = index;
|
||
}
|
||
if (!parse_enum_param_decl(c, parameters_ref)) return false;
|
||
Decl *last_parameter = VECLAST(*parameters_ref);
|
||
ASSERT(last_parameter);
|
||
last_parameter->var.index = vec_size(*parameters_ref) - 1; // NOLINT
|
||
if (!try_consume(c, TOKEN_COMMA))
|
||
{
|
||
EXPECT_OR_RET(TOKEN_RPAREN, false);
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
static bool parse_enum_values(ParseContext *c, Decl*** values_ref, Visibility visibility, bool is_single_value, bool is_const_enum)
|
||
{
|
||
Decl **values = NULL;
|
||
while (!try_consume(c, TOKEN_RBRACE))
|
||
{
|
||
if (!parse_element_contract(c, "enum values")) return false;
|
||
Decl *enum_const = decl_new(DECL_ENUM_CONSTANT, symstr(c), c->span);
|
||
if (is_const_enum) enum_const->enum_constant.is_raw = is_const_enum;
|
||
enum_const->visibility = visibility;
|
||
const char *name = enum_const->name;
|
||
if (!consume_const_name(c, "enum constant")) return false;
|
||
FOREACH(Decl *, other_constant, values)
|
||
{
|
||
if (other_constant->name == name)
|
||
{
|
||
PRINT_ERROR_AT(enum_const, "This enum constant is declared twice.");
|
||
SEMA_NOTE(other_constant, "The previous declaration was here.");
|
||
return false;
|
||
}
|
||
}
|
||
if (!parse_attributes_for_global(c, enum_const)) return false;
|
||
if (try_consume(c, TOKEN_EQ))
|
||
{
|
||
Expr **args = NULL;
|
||
if (is_single_value || !tok_is(c, TOKEN_LBRACE))
|
||
{
|
||
ASSIGN_EXPR_OR_RET(Expr *single, parse_constant_expr(c), false);
|
||
if (is_const_enum)
|
||
{
|
||
enum_const->enum_constant.value = single;
|
||
goto NEXT;
|
||
}
|
||
vec_add(args, single);
|
||
}
|
||
else
|
||
{
|
||
CONSUME_OR_RET(TOKEN_LBRACE, false);
|
||
while (1)
|
||
{
|
||
if (try_consume(c, TOKEN_RBRACE)) break;
|
||
ASSIGN_EXPR_OR_RET(Expr *arg, parse_expr(c), false);
|
||
vec_add(args, arg);
|
||
if (tok_is(c, TOKEN_COLON) && arg->expr_kind == EXPR_UNRESOLVED_IDENTIFIER)
|
||
{
|
||
print_error_at(extend_span_with_token(arg->span, c->span),
|
||
"This looks like a designated initializer, but that style of declaration "
|
||
"is not supported for declaring enum associated values.");
|
||
return false;
|
||
}
|
||
if (!try_consume(c, TOKEN_COMMA))
|
||
{
|
||
if (!try_consume(c, TOKEN_RBRACE))
|
||
{
|
||
PRINT_ERROR_HERE("A comma or a closing brace was expected here.");
|
||
return false;
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
enum_const->enum_constant.associated = args;
|
||
}
|
||
NEXT:
|
||
vec_add(values, enum_const);
|
||
// Allow trailing ','
|
||
if (!try_consume(c, TOKEN_COMMA))
|
||
{
|
||
if (tok_is(c, TOKEN_CONST_IDENT))
|
||
{
|
||
PRINT_ERROR_HERE("It looks like you forgot a comma before this identifier.");
|
||
return false;
|
||
}
|
||
EXPECT_OR_RET(TOKEN_RBRACE, false);
|
||
}
|
||
}
|
||
*values_ref = values;
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* Parse an enum declaration (after "enum")
|
||
*
|
||
* enum ::= ENUM TYPE_IDENT opt_interfaces (':' 'inline'? type? ('const' | enum_param_list?)?)? opt_attributes '{' enum_body '}'
|
||
* enum_body ::= enum_def (',' enum_def)* ','?
|
||
* enum_def ::= CONST_IDENT ('(' arg_list ')')?
|
||
*/
|
||
static inline Decl *parse_enum_declaration(ParseContext *c)
|
||
{
|
||
advance_and_verify(c, TOKEN_ENUM);
|
||
|
||
const char *name = symstr(c);
|
||
SourceSpan span = c->span;
|
||
if (!consume_type_name(c, "enum")) return poisoned_decl;
|
||
TypeInfo **interfaces = NULL;
|
||
if (!parse_interface_impls(c, &interfaces)) return poisoned_decl;
|
||
TypeInfo *type = NULL;
|
||
|
||
bool val_is_inline = false;
|
||
ArrayIndex inline_index = -1;
|
||
bool is_const_enum = false;
|
||
Decl **param_list = NULL;
|
||
if (try_consume(c, TOKEN_COLON))
|
||
{
|
||
is_const_enum = try_consume(c, TOKEN_CONST);
|
||
if (!tok_is(c, TOKEN_LPAREN) && !tok_is(c, TOKEN_LBRACE))
|
||
{
|
||
val_is_inline = try_consume(c, TOKEN_INLINE);
|
||
ASSIGN_TYPE_OR_RET(type, parse_optional_type_no_generic(c), poisoned_decl);
|
||
if (type->optional)
|
||
{
|
||
RETURN_PRINT_ERROR_AT(poisoned_decl, type, "An enum can't have an optional type.");
|
||
}
|
||
}
|
||
if (is_const_enum)
|
||
{
|
||
if (tok_is(c, TOKEN_LPAREN))
|
||
{
|
||
PRINT_ERROR_HERE("Const enums cannot have associated values.");
|
||
return poisoned_decl;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (!parse_enum_param_list(c, ¶m_list, val_is_inline ? NULL : &inline_index)) return poisoned_decl;
|
||
}
|
||
}
|
||
|
||
Decl *decl = decl_new_with_type(name, span, is_const_enum ? DECL_CONST_ENUM : DECL_ENUM);
|
||
decl->interfaces = interfaces;
|
||
if (param_list) decl->enums.parameters = param_list;
|
||
if (!parse_attributes_for_global(c, decl)) return poisoned_decl;
|
||
unsigned expected_parameters = vec_size(decl->enums.parameters);
|
||
Visibility visibility = decl->visibility;
|
||
CONSUME_OR_RET(TOKEN_LBRACE, poisoned_decl);
|
||
|
||
decl->enums.type_info = type ? type : type_info_new_base(type_int, decl->span);
|
||
decl->enums.inline_index = (int16_t)inline_index;
|
||
decl->enums.inline_value = is_const_enum ? false : val_is_inline;
|
||
if (is_const_enum && val_is_inline) decl->is_substruct = true;
|
||
if (!parse_enum_values(c, &decl->enums.values, visibility, is_const_enum || expected_parameters == 1, is_const_enum)) return poisoned_decl;
|
||
return decl;
|
||
}
|
||
|
||
// --- Parse function
|
||
|
||
/**
|
||
* Starts after 'fn'
|
||
*
|
||
* func_definition ::= func_macro_header fn_parameter_list opt_attributes (func_body | ';')
|
||
* func_body ::= ('=>' short_body) | compound_stmt
|
||
*
|
||
*/
|
||
static inline Decl *parse_func_definition(ParseContext *c, AstId contracts, FunctionParse parse_kind)
|
||
{
|
||
advance_and_verify(c, TOKEN_FN);
|
||
Decl *func = decl_calloc();
|
||
func->decl_kind = DECL_FUNC;
|
||
func->func_decl.docs = contracts;
|
||
func->func_decl.attr_interface_method = parse_kind == FUNC_PARSE_INTERFACE;
|
||
if (!parse_func_macro_header(c, func)) return poisoned_decl;
|
||
if (func->name[0] == '@')
|
||
{
|
||
RETURN_PRINT_ERROR_AT(false, func, "Function names may not use '@'.");
|
||
}
|
||
if (!parse_fn_parameter_list(c, &(func->func_decl.signature))) return poisoned_decl;
|
||
if (!parse_attributes_for_global(c, func)) return poisoned_decl;
|
||
if (parse_kind != FUNC_PARSE_REGULAR)
|
||
{
|
||
if (tok_is(c, TOKEN_LBRACE) || tok_is(c, TOKEN_IMPLIES))
|
||
{
|
||
switch (parse_kind)
|
||
{
|
||
case FUNC_PARSE_REGULAR:
|
||
UNREACHABLE
|
||
case FUNC_PARSE_C3I:
|
||
PRINT_ERROR_HERE("An interface file may not contain function bodies.");
|
||
return poisoned_decl;
|
||
case FUNC_PARSE_EXTERN:
|
||
PRINT_ERROR_HERE("An 'extern' function may not have a body.");
|
||
return poisoned_decl;
|
||
case FUNC_PARSE_INTERFACE:
|
||
PRINT_ERROR_HERE("An interface method cannot have a body.");
|
||
return poisoned_decl;
|
||
}
|
||
UNREACHABLE
|
||
}
|
||
TRY_CONSUME_OR_RET(TOKEN_EOS, "Expected ';' after the function declaration.", poisoned_decl);
|
||
return func;
|
||
}
|
||
|
||
if (try_consume(c, TOKEN_EOS))
|
||
{
|
||
return func;
|
||
}
|
||
|
||
if (tok_is(c, TOKEN_IMPLIES))
|
||
{
|
||
ASSIGN_ASTID_OR_RET(func->func_decl.body,
|
||
parse_short_body(c, func->func_decl.signature.rtype, true), poisoned_decl);
|
||
}
|
||
else if (tok_is(c, TOKEN_LBRACE))
|
||
{
|
||
ASSIGN_ASTID_OR_RET(func->func_decl.body, parse_compound_stmt(c), poisoned_decl);
|
||
}
|
||
else
|
||
{
|
||
PRINT_ERROR_HERE("Expected the beginning of a block or a short statement.");
|
||
return poisoned_decl;
|
||
}
|
||
|
||
DEBUG_LOG("Finished parsing function %s", func->name);
|
||
return func;
|
||
}
|
||
|
||
|
||
|
||
/**
|
||
*
|
||
* import ::= IMPORT import_path (',' import_path)* opt_attributes EOS
|
||
*
|
||
* @return true if import succeeded
|
||
*/
|
||
static inline bool parse_import(ParseContext *c)
|
||
{
|
||
advance_and_verify(c, TOKEN_IMPORT);
|
||
|
||
bool is_not_first = false;
|
||
while (1)
|
||
{
|
||
if (!tok_is(c, TOKEN_IDENT))
|
||
{
|
||
if (is_not_first)
|
||
{
|
||
PRINT_ERROR_LAST("Another module name was expected after the comma.");
|
||
return false;
|
||
}
|
||
if (tok_is(c, TOKEN_STRING))
|
||
{
|
||
PRINT_ERROR_HERE("An import should be followed by a plain identifier, not a string. Did you accidentally put the module name between \"\"?");
|
||
return false;
|
||
}
|
||
PRINT_ERROR_HERE("Import statement should be followed by the name of the module to import.");
|
||
return false;
|
||
}
|
||
is_not_first = true;
|
||
Path *path = parse_module_path(c);
|
||
if (!path) return false;
|
||
bool private = false;
|
||
bool is_norecurse = false;
|
||
while (tok_is(c, TOKEN_AT_IDENT))
|
||
{
|
||
const char *name = symstr(c);
|
||
if (name == attribute_list[ATTRIBUTE_PUBLIC])
|
||
{
|
||
private = true;
|
||
}
|
||
else if (name == attribute_list[ATTRIBUTE_NORECURSE])
|
||
{
|
||
is_norecurse = true;
|
||
}
|
||
else
|
||
{
|
||
PRINT_ERROR_HERE("Only '@public' and '@norecurse' are valid attributes here.");
|
||
return false;
|
||
}
|
||
advance_and_verify(c, TOKEN_AT_IDENT);
|
||
}
|
||
unit_add_import(c->unit, path, private, is_norecurse);
|
||
if (tok_is(c, TOKEN_COLON) && peek(c) == TOKEN_IDENT)
|
||
{
|
||
PRINT_ERROR_HERE("'::' was expected here, did you make a mistake?");
|
||
return false;
|
||
}
|
||
if (!try_consume(c, TOKEN_COMMA)) break;
|
||
}
|
||
|
||
CONSUME_EOS_OR_RET(false);
|
||
return true;
|
||
}
|
||
|
||
|
||
INLINE void append_docs(AstId **next, AstId *first, Ast *new_doc)
|
||
{
|
||
if (!*first)
|
||
{
|
||
*first = astid(new_doc);
|
||
}
|
||
**next = astid(new_doc);
|
||
*next = &new_doc->next;
|
||
}
|
||
|
||
INLINE bool parse_doc_to_eol(ParseContext *c)
|
||
{
|
||
if (try_consume(c, TOKEN_DOCS_EOL)) return true;
|
||
if (tok_is(c, TOKEN_DOCS_END)) return true;
|
||
return false;
|
||
}
|
||
|
||
INLINE bool parse_doc_check_skip_string_eos(ParseContext *c)
|
||
{
|
||
if (tok_is(c, TOKEN_STRING)) return true;
|
||
if (!tok_is(c, TOKEN_DOCS_EOL)) return false;
|
||
if (peek(c) != TOKEN_STRING) return false;
|
||
advance_and_verify(c, TOKEN_DOCS_EOL);
|
||
return true;
|
||
}
|
||
|
||
INLINE bool parse_docs_to_comment(ParseContext *c)
|
||
{
|
||
if (try_consume(c, TOKEN_COLON)) return true;
|
||
if (!tok_is(c, TOKEN_DOCS_EOL)) return false;
|
||
if (peek(c) == TOKEN_COLON)
|
||
{
|
||
advance_and_verify(c, TOKEN_DOCS_EOL);
|
||
advance_and_verify(c, TOKEN_COLON);
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
static bool parse_doc_discarded_comment(ParseContext *c)
|
||
{
|
||
if (try_consume(c, TOKEN_STRING))
|
||
{
|
||
PRINT_DEPRECATED_AT(c->span, "Not using ':' before the description is deprecated");
|
||
return true;
|
||
}
|
||
if (!parse_docs_to_comment(c)) return true;
|
||
if (!parse_doc_check_skip_string_eos(c)) return true;
|
||
return parse_joined_strings(c, NULL, NULL);
|
||
}
|
||
static bool parse_doc_direct_comment(ParseContext *c)
|
||
{
|
||
if (tok_is(c, TOKEN_DOCS_EOL) && peek(c) == TOKEN_STRING)
|
||
{
|
||
advance(c);
|
||
}
|
||
if (!tok_is(c, TOKEN_STRING)) return true;
|
||
return parse_joined_strings(c, NULL, NULL);
|
||
}
|
||
|
||
/**
|
||
* contract ::= expression_list (':'? STRING)?
|
||
*/
|
||
static inline bool parse_doc_contract(ParseContext *c, AstId *docs, AstId **docs_next, ContractKind kind)
|
||
{
|
||
Ast *ast = ast_new_curr(c, AST_CONTRACT);
|
||
ast->contract_stmt.kind = kind;
|
||
const char *start = c->lexer.data.lex_start;
|
||
advance(c);
|
||
ASSIGN_EXPR_OR_RET(ast->contract_stmt.contract.decl_exprs, parse_expression_list(c, false), false);
|
||
const char *end = start + 1;
|
||
while (end[0] != '\n' && end[0] != '\0') end++;
|
||
if (end > c->data.lex_start) end = c->data.lex_start;
|
||
while (is_space(end[-1])) end--;
|
||
scratch_buffer_clear();
|
||
switch (kind)
|
||
{
|
||
case CONTRACT_ENSURE:
|
||
scratch_buffer_append("@ensure \"");
|
||
break;
|
||
default:
|
||
scratch_buffer_append("@require \"");
|
||
break;
|
||
}
|
||
scratch_buffer_append_remove_space(start, (int)(end - start));
|
||
scratch_buffer_append("\" violated");
|
||
bool docs_to_comment = false;
|
||
if (parse_docs_to_comment(c))
|
||
{
|
||
docs_to_comment = true;
|
||
if (!parse_doc_check_skip_string_eos(c))
|
||
{
|
||
print_error_at(c->prev_span, "Expected a string after ':'");
|
||
return false;
|
||
}
|
||
}
|
||
if (tok_is(c, TOKEN_STRING))
|
||
{
|
||
scratch_buffer_append(": '");
|
||
if (!parse_joined_strings(c, NULL, NULL)) return false;
|
||
scratch_buffer_append("'.");
|
||
ast->contract_stmt.contract.comment = scratch_buffer_copy();
|
||
if (!docs_to_comment)
|
||
{
|
||
SEMA_DEPRECATED(ast, "Not using ':' before the description is deprecated");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
scratch_buffer_append(".");
|
||
ast->contract_stmt.contract.expr_string = scratch_buffer_copy();
|
||
}
|
||
append_docs(docs_next, docs, ast);
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* param_contract ::= '@param' inout_attribute? any_identifier ( ':' STRING )?
|
||
* inout_attribute ::= '[' '&'? ('in' | 'inout' | 'out') ']'
|
||
*/
|
||
static inline bool parse_contract_param(ParseContext *c, AstId *docs, AstId **docs_next)
|
||
{
|
||
Ast *ast = ast_new_curr(c, AST_CONTRACT);
|
||
ast->contract_stmt.kind = CONTRACT_PARAM;
|
||
advance(c);
|
||
|
||
// [inout] [in] [out]
|
||
bool is_ref = false;
|
||
InOutModifier mod = INOUT_ANY;
|
||
if (try_consume(c, TOKEN_LBRACKET))
|
||
{
|
||
is_ref = try_consume(c, TOKEN_AMP);
|
||
const char *modifier = tok_is(c, TOKEN_IDENT) ? symstr(c) : NULL;
|
||
if (modifier) advance(c);
|
||
if (modifier == kw_in)
|
||
{
|
||
mod = INOUT_IN;
|
||
}
|
||
else if (modifier == kw_inout)
|
||
{
|
||
mod = INOUT_INOUT;
|
||
}
|
||
else if (modifier == kw_out)
|
||
{
|
||
mod = INOUT_OUT;
|
||
}
|
||
else
|
||
{
|
||
RETURN_PRINT_ERROR_LAST("'in', 'out' or 'inout' were expected.");
|
||
}
|
||
CONSUME_OR_RET(TOKEN_RBRACKET, false);
|
||
}
|
||
|
||
switch (c->tok)
|
||
{
|
||
case TOKEN_IDENT:
|
||
case TOKEN_CT_IDENT:
|
||
case TOKEN_TYPE_IDENT:
|
||
case TOKEN_CT_CONST_IDENT:
|
||
case TOKEN_CT_TYPE_IDENT:
|
||
case TOKEN_CONST_IDENT:
|
||
case TOKEN_HASH_IDENT:
|
||
break;
|
||
default:
|
||
RETURN_PRINT_ERROR_HERE("Expected a parameter name here.");
|
||
}
|
||
ast->contract_stmt.param.name = symstr(c);
|
||
ast->contract_stmt.param.span = c->span;
|
||
ast->contract_stmt.param.modifier = mod;
|
||
ast->contract_stmt.param.by_ref = is_ref;
|
||
advance(c);
|
||
|
||
if (parse_docs_to_comment(c))
|
||
{
|
||
if (!parse_doc_check_skip_string_eos(c))
|
||
{
|
||
RETURN_PRINT_ERROR_LAST("Expected a string after ':'");
|
||
}
|
||
if (!parse_joined_strings(c, NULL, NULL)) return false;
|
||
}
|
||
else
|
||
{
|
||
if (!try_consume(c, TOKEN_STRING))
|
||
{
|
||
if (!tok_is(c, TOKEN_DOCS_EOL) && !tok_is(c, TOKEN_DOCS_END))
|
||
{
|
||
if (tok_is(c, TOKEN_IDENT) || tok_is(c, TOKEN_TYPE_IDENT))
|
||
{
|
||
PRINT_ERROR_HERE("A string containing the parameter description was expected, did you forget enclosing the description in \"\" or ``?");
|
||
}
|
||
else
|
||
{
|
||
PRINT_ERROR_HERE("A string containing the description was expected after the parameter name.");
|
||
}
|
||
return false;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
RANGE_EXTEND_PREV(ast);
|
||
SEMA_DEPRECATED(ast, "Not using ':' before the string is deprecated.");
|
||
}
|
||
}
|
||
append_docs(docs_next, docs, ast);
|
||
return true;
|
||
}
|
||
|
||
static inline bool parse_doc_optreturn(ParseContext *c, AstId *docs, AstId **docs_next)
|
||
{
|
||
Ast **returns = NULL;
|
||
Ast *ast = ast_new_curr(c, AST_CONTRACT);
|
||
ast->span = c->prev_span;
|
||
advance_and_verify(c, TOKEN_QUESTION);
|
||
ast->contract_stmt.kind = CONTRACT_OPTIONALS;
|
||
while (1)
|
||
{
|
||
Ast *ret = ast_new_curr(c, AST_CONTRACT_FAULT);
|
||
ASSIGN_EXPR_OR_RET(ret->contract_fault.expr, parse_expr(c), false);
|
||
vec_add(returns, ret);
|
||
if (!try_consume(c, TOKEN_COMMA)) break;
|
||
}
|
||
RANGE_EXTEND_PREV(ast);
|
||
// Just ignore our potential string:
|
||
if (!parse_doc_discarded_comment(c)) return false;
|
||
ast->contract_stmt.faults = returns;
|
||
append_docs(docs_next, docs, ast);
|
||
return true;
|
||
}
|
||
|
||
|
||
static bool parse_contracts(ParseContext *c, AstId *contracts_ref)
|
||
{
|
||
*contracts_ref = 0;
|
||
if (!tok_is(c, TOKEN_DOCS_START)) return true;
|
||
|
||
AstId **next = &contracts_ref;
|
||
if (c->data.strlen > 0)
|
||
{
|
||
Ast *ast = ast_new_curr(c, AST_CONTRACT);
|
||
ast->contract_stmt.kind = CONTRACT_COMMENT;
|
||
ast->contract_stmt.string = symstr(c);
|
||
ast->contract_stmt.strlen = c->data.strlen;
|
||
ast->span = c->span;
|
||
append_docs(next, contracts_ref, ast);
|
||
}
|
||
|
||
advance_and_verify(c, TOKEN_DOCS_START);
|
||
|
||
while (!try_consume(c, TOKEN_DOCS_END))
|
||
{
|
||
if (try_consume(c, TOKEN_DOCS_EOL)) continue;
|
||
if (!tok_is(c, TOKEN_AT_IDENT))
|
||
{
|
||
RETURN_PRINT_ERROR_HERE("Expected a directive starting with '@' here, like '@param' or `@require`");
|
||
}
|
||
const char *name = symstr(c);
|
||
if (name == kw_at_param)
|
||
{
|
||
if (!parse_contract_param(c, contracts_ref, next)) return false;
|
||
}
|
||
else if (name == kw_at_return)
|
||
{
|
||
advance(c);
|
||
if (tok_is(c, TOKEN_QUESTION))
|
||
{
|
||
if (!parse_doc_optreturn(c, contracts_ref, next)) return false;
|
||
}
|
||
else
|
||
{
|
||
if (!parse_doc_direct_comment(c)) return false;
|
||
}
|
||
}
|
||
else if (name == kw_at_deprecated)
|
||
{
|
||
advance(c);
|
||
if (!parse_doc_direct_comment(c)) return false;
|
||
REMINDER("Implement @deprecated tracking");
|
||
}
|
||
else if (name == kw_at_require)
|
||
{
|
||
if (!parse_doc_contract(c, contracts_ref, next, CONTRACT_REQUIRE)) return false;
|
||
}
|
||
else if (name == kw_at_ensure)
|
||
{
|
||
if (!parse_doc_contract(c, contracts_ref, next, CONTRACT_ENSURE)) return false;
|
||
}
|
||
else if (name == kw_at_pure)
|
||
{
|
||
Ast *ast = ast_new_curr(c, AST_CONTRACT);
|
||
ast->contract_stmt.kind = CONTRACT_PURE;
|
||
advance(c);
|
||
if (!parse_doc_direct_comment(c)) return false;
|
||
append_docs(next, contracts_ref, ast);
|
||
}
|
||
else
|
||
{
|
||
advance(c);
|
||
if (!parse_doc_direct_comment(c)) return false;
|
||
if (parse_doc_to_eol(c)) continue;
|
||
RETURN_PRINT_ERROR_HERE("Expected a string description for the custom contract '%s'.", name);
|
||
}
|
||
if (parse_doc_to_eol(c)) continue;
|
||
PRINT_ERROR_HERE("Expected the end of the contract here.");
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
static Decl *parse_ct_include(ParseContext *c)
|
||
{
|
||
SourceSpan loc = c->span;
|
||
Decl *decl = decl_new(DECL_CT_INCLUDE, NULL, loc);
|
||
advance_and_verify(c, TOKEN_CT_INCLUDE);
|
||
ASSIGN_EXPR_OR_RET(decl->include.filename, parse_constant_expr(c), poisoned_decl);
|
||
if (!parse_attributes_for_global(c, decl)) return poisoned_decl;
|
||
CONSUME_EOS_OR_RET(poisoned_decl);
|
||
return decl;
|
||
}
|
||
|
||
static Decl *parse_exec(ParseContext *c)
|
||
{
|
||
SourceSpan loc = c->span;
|
||
Decl *decl = decl_new(DECL_CT_EXEC, NULL, loc);
|
||
advance_and_verify(c, TOKEN_CT_EXEC);
|
||
CONSUME_OR_RET(TOKEN_LPAREN, poisoned_decl);
|
||
ASSIGN_EXPR_OR_RET(decl->exec_decl.filename, parse_constant_expr(c), poisoned_decl);
|
||
// We might just have `$exec("foo")`
|
||
if (try_consume(c, TOKEN_RPAREN)) goto END;
|
||
// Get the `,`
|
||
CONSUME_OR_RET(TOKEN_COMMA, poisoned_decl);
|
||
CONSUME_OR_RET(TOKEN_LBRACE, poisoned_decl);
|
||
do
|
||
{
|
||
if (tok_is(c, TOKEN_RBRACE)) break;
|
||
ASSIGN_EXPR_OR_RET(Expr *expr, parse_constant_expr(c), poisoned_decl);
|
||
vec_add(decl->exec_decl.args, expr);
|
||
} while (try_consume(c, TOKEN_COMMA));
|
||
CONSUME_OR_RET(TOKEN_RBRACE, poisoned_decl);
|
||
if (try_consume(c, TOKEN_RPAREN)) goto END;
|
||
CONSUME_OR_RET(TOKEN_COMMA, poisoned_decl);
|
||
ASSIGN_EXPR_OR_RET(decl->exec_decl.stdin_string, parse_constant_expr(c), poisoned_decl);
|
||
CONSUME_OR_RET(TOKEN_RPAREN, poisoned_decl);
|
||
END:
|
||
if (!parse_attributes_for_global(c, decl)) return poisoned_decl;
|
||
CONSUME_EOS_OR_RET(poisoned_decl);
|
||
return decl;
|
||
}
|
||
|
||
/**
|
||
* top_level_statement ::= struct_declaration | enum_declaration | fault_declaration | const_declaration
|
||
* | global_declaration | macro_declaration | func_definition | typedef_declaration
|
||
* | conditional_compilation | define_declaration | import_declaration | module_declaration
|
||
* | distinct_declaration | interface_declaration
|
||
* | static_declaration | ct_assert_declaration | ct_echo_declaration | bitstruct_declaration
|
||
*
|
||
* @return Decl* or a poison value if parsing failed
|
||
*/
|
||
Decl *parse_top_level_statement(ParseContext *c, ParseContext **context_out)
|
||
{
|
||
AstId contracts = 0;
|
||
if (!parse_contracts(c, &contracts)) return poisoned_decl;
|
||
Decl *decl;
|
||
|
||
bool has_real_contracts = false;
|
||
if (contracts)
|
||
{
|
||
Ast *contract_start = astptr(contracts);
|
||
if (contract_start->contract_stmt.kind != CONTRACT_COMMENT || contract_start->next)
|
||
{
|
||
has_real_contracts = true;
|
||
}
|
||
}
|
||
TokenType tok = c->tok;
|
||
if (tok != TOKEN_MODULE && !c->unit->module)
|
||
{
|
||
if (!context_set_module_from_filename(c)) return poisoned_decl;
|
||
c->unit->module_generated = true;
|
||
// Pass the docs to the next thing.
|
||
}
|
||
|
||
switch (tok)
|
||
{
|
||
case TOKEN_EXTERN:
|
||
// Extern declarations
|
||
advance(c);
|
||
tok = c->tok;
|
||
switch (tok)
|
||
{
|
||
case TOKEN_FN:
|
||
decl = parse_func_definition(c, contracts, FUNC_PARSE_EXTERN);
|
||
break;
|
||
case TOKEN_CONST:
|
||
if (has_real_contracts) goto CONTRACT_NOT_ALLOWED;
|
||
decl = parse_top_level_const_declaration(c, true);
|
||
break;
|
||
case TOKEN_IDENT:
|
||
case TOKEN_TLOCAL:
|
||
case TYPELIKE_TOKENS:
|
||
if (has_real_contracts) goto CONTRACT_NOT_ALLOWED;
|
||
decl = parse_global_declaration(c);
|
||
break;
|
||
default:
|
||
PRINT_ERROR_HERE("Expected 'extern' to be followed by a function, constant or global variable.");
|
||
return poisoned_decl;
|
||
}
|
||
if (!decl_ok(decl)) return decl;
|
||
decl->is_extern = true;
|
||
break;
|
||
case TOKEN_MODULE:
|
||
if (!context_out)
|
||
{
|
||
PRINT_ERROR_HERE("'module' is not valid inside an include.");
|
||
return poisoned_decl;
|
||
}
|
||
advance(c);
|
||
if (c->unit->module)
|
||
{
|
||
if (c->unit->module_generated)
|
||
{
|
||
print_error_at(c->unit->module->name->span, "This file begins with an auto-generated module '%s', which isn't compatible with having other module sections, please start the file with an explicit 'module'.",
|
||
c->unit->module->name->module);
|
||
sema_note_prev_at(c->span, "This declaration creates the next module section.");
|
||
c->unit->module_generated = false;
|
||
return poisoned_decl;
|
||
}
|
||
// We might run into another module declaration. If so, create a new unit.
|
||
ParseContext *new_context = CALLOCS(ParseContext);
|
||
*new_context = *c;
|
||
new_context->unit = unit_create(c->unit->file);
|
||
*context_out = c = new_context;
|
||
}
|
||
if (!parse_module(c, contracts)) return poisoned_decl;
|
||
return NULL;
|
||
case TOKEN_DOCS_START:
|
||
PRINT_ERROR_HERE("There are more than one doc comment in a row, that is not allowed.");
|
||
return poisoned_decl;
|
||
case TOKEN_ALIAS:
|
||
if (has_real_contracts) goto CONTRACT_NOT_ALLOWED;
|
||
decl = parse_alias(c);
|
||
if (decl->decl_kind == DECL_ALIAS_PATH)
|
||
{
|
||
if (!context_out)
|
||
{
|
||
PRINT_ERROR_HERE("'alias module' may not appear inside a compile time statement.");
|
||
return poisoned_decl;
|
||
}
|
||
if (!unit_add_alias(c->unit, decl)) return poisoned_decl;
|
||
return NULL;
|
||
}
|
||
break;
|
||
case TOKEN_ATTRDEF:
|
||
if (has_real_contracts) goto CONTRACT_NOT_ALLOWED;
|
||
decl = parse_attrdef(c);
|
||
break;
|
||
case TOKEN_FN:
|
||
decl = parse_func_definition(c, contracts, c->unit->is_interface_file ? FUNC_PARSE_C3I : FUNC_PARSE_REGULAR);
|
||
break;
|
||
case TOKEN_CT_ASSERT:
|
||
{
|
||
if (contracts) goto CONTRACT_NOT_ALLOWED;
|
||
ASSIGN_AST_OR_RET(Ast *ast, parse_ct_assert_stmt(c), poisoned_decl);
|
||
decl = decl_new_ct(DECL_CT_ASSERT, ast->span);
|
||
decl->ct_assert_decl = ast;
|
||
return decl;
|
||
}
|
||
case TOKEN_CT_ERROR:
|
||
{
|
||
if (contracts) goto CONTRACT_NOT_ALLOWED;
|
||
ASSIGN_AST_OR_RET(Ast *ast, parse_ct_error_stmt(c), poisoned_decl);
|
||
decl = decl_new_ct(DECL_CT_ASSERT, ast->span);
|
||
decl->ct_assert_decl = ast;
|
||
return decl;
|
||
}
|
||
case TOKEN_CT_ECHO:
|
||
{
|
||
if (contracts) goto CONTRACT_NOT_ALLOWED;
|
||
ASSIGN_AST_OR_RET(Ast *ast, parse_ct_echo_stmt(c), poisoned_decl);
|
||
decl = decl_new_ct(DECL_CT_ECHO, ast->span);
|
||
decl->ct_echo_decl = ast;
|
||
break;
|
||
}
|
||
case TOKEN_IMPORT:
|
||
if (contracts) goto CONTRACT_NOT_ALLOWED;
|
||
if (!context_out)
|
||
{
|
||
PRINT_ERROR_HERE("'import' may not appear inside a compile time statement.");
|
||
return poisoned_decl;
|
||
}
|
||
if (!parse_import(c)) return poisoned_decl;
|
||
return NULL;
|
||
case TOKEN_CT_INCLUDE:
|
||
if (contracts) goto CONTRACT_NOT_ALLOWED;
|
||
decl = parse_ct_include(c);
|
||
break;
|
||
case TOKEN_CT_EXEC:
|
||
if (contracts) goto CONTRACT_NOT_ALLOWED;
|
||
decl = parse_exec(c);
|
||
break;
|
||
case TOKEN_BITSTRUCT:
|
||
if (has_real_contracts) goto CONTRACT_NOT_ALLOWED;
|
||
decl = parse_bitstruct_declaration(c);
|
||
break;
|
||
case TOKEN_INTERFACE:
|
||
if (has_real_contracts) goto CONTRACT_NOT_ALLOWED;
|
||
decl = parse_interface_declaration(c);
|
||
break;
|
||
case TOKEN_TYPEDEF:
|
||
if (has_real_contracts) goto CONTRACT_NOT_ALLOWED;
|
||
decl = parse_typedef_declaration(c);
|
||
break;
|
||
case TOKEN_CONST:
|
||
if (has_real_contracts) goto CONTRACT_NOT_ALLOWED;
|
||
decl = parse_top_level_const_declaration(c, false);
|
||
break;
|
||
case TOKEN_STRUCT:
|
||
case TOKEN_UNION:
|
||
if (has_real_contracts) goto CONTRACT_NOT_ALLOWED;
|
||
decl = parse_struct_declaration(c);
|
||
break;
|
||
case TOKEN_MACRO:
|
||
decl = parse_macro_declaration(c, contracts);
|
||
break;
|
||
case TOKEN_ENUM:
|
||
if (has_real_contracts) goto CONTRACT_NOT_ALLOWED;
|
||
decl = parse_enum_declaration(c);
|
||
break;
|
||
case TOKEN_FAULTDEF:
|
||
if (has_real_contracts) goto CONTRACT_NOT_ALLOWED;
|
||
decl = parse_faultdef_declaration(c);
|
||
break;
|
||
case TOKEN_IDENT:
|
||
if (has_real_contracts) goto CONTRACT_NOT_ALLOWED;
|
||
decl = parse_global_declaration(c);
|
||
break;
|
||
case TOKEN_EOF:
|
||
PRINT_ERROR_LAST("Expected a top level declaration.");
|
||
return poisoned_decl;
|
||
case TOKEN_STATIC:
|
||
PRINT_ERROR_HERE("'static' is only used with local variable declarations.");
|
||
return poisoned_decl;
|
||
case TOKEN_CT_CONST_IDENT:
|
||
if (peek(c) == TOKEN_EQ)
|
||
{
|
||
PRINT_ERROR_HERE("Did you forget a 'const' before the name of this compile time constant?");
|
||
}
|
||
else
|
||
{
|
||
PRINT_ERROR_HERE("Compile time constant unexpectedly found.");
|
||
}
|
||
return poisoned_decl;
|
||
case TOKEN_TLOCAL:
|
||
case TYPELIKE_TOKENS:
|
||
if (has_real_contracts) goto CONTRACT_NOT_ALLOWED;
|
||
decl = parse_global_declaration(c);
|
||
break;
|
||
case TOKEN_EOS:
|
||
PRINT_ERROR_HERE("';' wasn't expected here, try removing it.");
|
||
return poisoned_decl;
|
||
default:
|
||
PRINT_ERROR_HERE("Expected the start of a global declaration here.");
|
||
return poisoned_decl;
|
||
}
|
||
if (!decl_ok(decl)) return decl;
|
||
ASSERT(decl);
|
||
return decl;
|
||
CONTRACT_NOT_ALLOWED:
|
||
RETURN_PRINT_ERROR_AT(poisoned_decl, astptr(contracts), "Contracts are only used for modules, functions and macros.");
|
||
}
|
||
|