2020-08-10 01:37:07 -07:00
|
|
|
#include "SemanticAnalyzer.h"
|
|
|
|
|
|
2020-08-31 18:46:27 -07:00
|
|
|
#include <boost/range/adaptor/reversed.hpp>
|
|
|
|
|
|
2021-02-25 16:53:22 +01:00
|
|
|
#include "bscript/compiler/Report.h"
|
|
|
|
|
#include "bscript/compiler/analyzer/Constants.h"
|
|
|
|
|
#include "bscript/compiler/analyzer/FlowControlScope.h"
|
|
|
|
|
#include "bscript/compiler/analyzer/LocalVariableScope.h"
|
|
|
|
|
#include "bscript/compiler/analyzer/LocalVariableScopes.h"
|
|
|
|
|
#include "bscript/compiler/ast/Argument.h"
|
|
|
|
|
#include "bscript/compiler/ast/BasicForLoop.h"
|
|
|
|
|
#include "bscript/compiler/ast/BinaryOperator.h"
|
|
|
|
|
#include "bscript/compiler/ast/Block.h"
|
|
|
|
|
#include "bscript/compiler/ast/CaseDispatchDefaultSelector.h"
|
|
|
|
|
#include "bscript/compiler/ast/CaseDispatchGroup.h"
|
|
|
|
|
#include "bscript/compiler/ast/CaseDispatchGroups.h"
|
|
|
|
|
#include "bscript/compiler/ast/CaseDispatchSelectors.h"
|
|
|
|
|
#include "bscript/compiler/ast/CaseStatement.h"
|
|
|
|
|
#include "bscript/compiler/ast/ConstDeclaration.h"
|
|
|
|
|
#include "bscript/compiler/ast/CstyleForLoop.h"
|
|
|
|
|
#include "bscript/compiler/ast/DoWhileLoop.h"
|
|
|
|
|
#include "bscript/compiler/ast/ForeachLoop.h"
|
|
|
|
|
#include "bscript/compiler/ast/FunctionBody.h"
|
|
|
|
|
#include "bscript/compiler/ast/FunctionCall.h"
|
|
|
|
|
#include "bscript/compiler/ast/FunctionParameterDeclaration.h"
|
|
|
|
|
#include "bscript/compiler/ast/FunctionParameterList.h"
|
|
|
|
|
#include "bscript/compiler/ast/FunctionReference.h"
|
|
|
|
|
#include "bscript/compiler/ast/Identifier.h"
|
|
|
|
|
#include "bscript/compiler/ast/IntegerValue.h"
|
|
|
|
|
#include "bscript/compiler/ast/JumpStatement.h"
|
|
|
|
|
#include "bscript/compiler/ast/MemberAccess.h"
|
|
|
|
|
#include "bscript/compiler/ast/ModuleFunctionDeclaration.h"
|
|
|
|
|
#include "bscript/compiler/ast/Program.h"
|
|
|
|
|
#include "bscript/compiler/ast/ProgramParameterDeclaration.h"
|
|
|
|
|
#include "bscript/compiler/ast/RepeatUntilLoop.h"
|
|
|
|
|
#include "bscript/compiler/ast/StringValue.h"
|
|
|
|
|
#include "bscript/compiler/ast/TopLevelStatements.h"
|
|
|
|
|
#include "bscript/compiler/ast/UserFunction.h"
|
|
|
|
|
#include "bscript/compiler/ast/VarStatement.h"
|
|
|
|
|
#include "bscript/compiler/ast/VariableAssignmentStatement.h"
|
|
|
|
|
#include "bscript/compiler/ast/WhileLoop.h"
|
|
|
|
|
#include "bscript/compiler/astbuilder/SimpleValueCloner.h"
|
|
|
|
|
#include "bscript/compiler/model/CompilerWorkspace.h"
|
|
|
|
|
#include "bscript/compiler/model/FunctionLink.h"
|
|
|
|
|
#include "bscript/compiler/model/Variable.h"
|
|
|
|
|
#include "bscript/compiler/optimizer/ConstantValidator.h"
|
2024-01-12 06:51:44 +01:00
|
|
|
#include "clib/strutil.h"
|
2020-09-07 12:35:17 -07:00
|
|
|
#include "filefmt.h"
|
2020-08-10 01:37:07 -07:00
|
|
|
|
2020-08-10 03:20:29 -07:00
|
|
|
namespace Pol::Bscript::Compiler
|
2020-08-10 01:37:07 -07:00
|
|
|
{
|
2020-09-11 00:16:20 -07:00
|
|
|
SemanticAnalyzer::SemanticAnalyzer( CompilerWorkspace& workspace, Report& report )
|
2024-01-12 06:51:44 +01:00
|
|
|
: workspace( workspace ),
|
|
|
|
|
report( report ),
|
|
|
|
|
globals( VariableScope::Global, report ),
|
|
|
|
|
locals( VariableScope::Local, report ),
|
|
|
|
|
break_scopes( locals, report ),
|
|
|
|
|
continue_scopes( locals, report ),
|
|
|
|
|
local_scopes( locals, report )
|
2020-08-10 01:37:07 -07:00
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SemanticAnalyzer::~SemanticAnalyzer() = default;
|
|
|
|
|
|
2020-09-11 00:16:20 -07:00
|
|
|
void SemanticAnalyzer::register_const_declarations( CompilerWorkspace& workspace, Report& report )
|
2020-08-10 01:37:07 -07:00
|
|
|
{
|
2020-09-01 00:54:24 -07:00
|
|
|
for ( auto& constant : workspace.const_declarations )
|
|
|
|
|
{
|
2020-09-11 00:16:20 -07:00
|
|
|
report_function_name_conflict( workspace, report, constant->source_location,
|
|
|
|
|
constant->identifier, "constant" );
|
2020-09-01 00:54:24 -07:00
|
|
|
workspace.constants.create( *constant );
|
|
|
|
|
}
|
2020-08-10 01:37:07 -07:00
|
|
|
}
|
|
|
|
|
|
2020-09-11 00:16:20 -07:00
|
|
|
void SemanticAnalyzer::analyze()
|
2020-08-10 01:37:07 -07:00
|
|
|
{
|
2020-08-17 22:53:03 -07:00
|
|
|
workspace.top_level_statements->accept( *this );
|
2020-08-28 22:45:07 -07:00
|
|
|
if ( auto& program = workspace.program )
|
|
|
|
|
{
|
|
|
|
|
program->accept( *this );
|
|
|
|
|
}
|
2020-08-25 00:57:03 -07:00
|
|
|
|
2020-08-31 18:46:27 -07:00
|
|
|
for ( auto& user_function : workspace.user_functions )
|
|
|
|
|
{
|
|
|
|
|
user_function->accept( *this );
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-25 00:57:03 -07:00
|
|
|
workspace.global_variable_names = globals.get_names();
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-08 01:56:30 -07:00
|
|
|
void SemanticAnalyzer::visit_basic_for_loop( BasicForLoop& node )
|
|
|
|
|
{
|
|
|
|
|
if ( locals.find( node.identifier ) )
|
|
|
|
|
{
|
2024-01-12 06:51:44 +01:00
|
|
|
report.error( node, "FOR iterator '{}' hides a local variable.", node.identifier );
|
2020-09-08 01:56:30 -07:00
|
|
|
return;
|
|
|
|
|
}
|
2020-09-11 00:16:20 -07:00
|
|
|
if ( report_function_name_conflict( node.source_location, node.identifier, "for loop iterator" ) )
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-09-08 01:56:30 -07:00
|
|
|
|
|
|
|
|
node.first().accept( *this );
|
|
|
|
|
node.last().accept( *this );
|
|
|
|
|
|
2020-09-12 09:31:52 -07:00
|
|
|
LocalVariableScope scope( local_scopes, node.local_variable_scope_info );
|
2020-09-08 01:56:30 -07:00
|
|
|
scope.create( node.identifier, WarnOn::Never, node.source_location );
|
|
|
|
|
scope.create( "_" + node.identifier + "_end", WarnOn::Never, node.source_location );
|
|
|
|
|
|
|
|
|
|
FlowControlScope break_scope( break_scopes, node.source_location, node.get_label(),
|
|
|
|
|
node.break_label );
|
|
|
|
|
FlowControlScope continue_scope( continue_scopes, node.source_location, node.get_label(),
|
|
|
|
|
node.continue_label );
|
|
|
|
|
|
|
|
|
|
node.block().accept( *this );
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-30 17:03:26 -07:00
|
|
|
void SemanticAnalyzer::visit_block( Block& block )
|
|
|
|
|
{
|
2020-09-12 09:31:52 -07:00
|
|
|
LocalVariableScope scope( local_scopes, block.local_variable_scope_info );
|
2020-08-30 17:03:26 -07:00
|
|
|
|
|
|
|
|
visit_children( block );
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-05 20:41:58 -07:00
|
|
|
class CaseDispatchDuplicateSelectorAnalyzer : public NodeVisitor
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
explicit CaseDispatchDuplicateSelectorAnalyzer( Report& report ) : report( report ) {}
|
|
|
|
|
|
|
|
|
|
void visit_block( Block& ) override
|
|
|
|
|
{
|
|
|
|
|
// just don't recurse into children
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void visit_integer_value( IntegerValue& node ) override
|
|
|
|
|
{
|
|
|
|
|
auto seen = already_seen_integers.find( node.value );
|
|
|
|
|
if ( seen != already_seen_integers.end() )
|
|
|
|
|
{
|
2024-01-12 06:51:44 +01:00
|
|
|
report.error( node,
|
|
|
|
|
"case statement already has a selector for integer value {}.\n"
|
|
|
|
|
" See also: {}",
|
|
|
|
|
node.value, ( *seen ).second->source_location );
|
2020-09-05 20:41:58 -07:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2024-01-12 06:51:44 +01:00
|
|
|
already_seen_integers[node.value] = &node;
|
2020-09-05 20:41:58 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void visit_string_value( StringValue& node ) override
|
|
|
|
|
{
|
|
|
|
|
auto seen = already_seen_strings.find( node.value );
|
|
|
|
|
if ( seen != already_seen_strings.end() )
|
|
|
|
|
{
|
2024-01-12 06:51:44 +01:00
|
|
|
report.error( node,
|
|
|
|
|
"case statement already has a selector for string value {}.\n"
|
|
|
|
|
" See also: {}",
|
|
|
|
|
Clib::getencodedquotedstring( node.value ), ( *seen ).second->source_location );
|
2020-09-05 20:41:58 -07:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2024-01-12 06:51:44 +01:00
|
|
|
already_seen_strings[node.value] = &node;
|
2020-09-05 20:41:58 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void visit_case_dispatch_default_selector( CaseDispatchDefaultSelector& node ) override
|
|
|
|
|
{
|
2024-01-12 06:51:44 +01:00
|
|
|
if ( already_seen_default )
|
|
|
|
|
{
|
|
|
|
|
report.error( node,
|
|
|
|
|
"case statement already has a default clause.\n"
|
|
|
|
|
" See also: {}",
|
|
|
|
|
already_seen_default->source_location );
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2020-09-05 20:41:58 -07:00
|
|
|
already_seen_default = &node;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
Report& report;
|
|
|
|
|
|
|
|
|
|
CaseDispatchDefaultSelector* already_seen_default = nullptr;
|
|
|
|
|
std::map<int, IntegerValue*> already_seen_integers;
|
|
|
|
|
std::map<std::string, StringValue*> already_seen_strings;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void SemanticAnalyzer::visit_case_statement( CaseStatement& case_ast )
|
|
|
|
|
{
|
|
|
|
|
CaseDispatchDuplicateSelectorAnalyzer duplicate_detector( report );
|
|
|
|
|
case_ast.dispatch_groups().accept( duplicate_detector );
|
|
|
|
|
|
|
|
|
|
FlowControlScope break_scope( break_scopes, case_ast.source_location, case_ast.get_label(),
|
|
|
|
|
case_ast.break_label );
|
|
|
|
|
|
|
|
|
|
visit_children( case_ast );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SemanticAnalyzer::visit_case_dispatch_group( CaseDispatchGroup& dispatch_group )
|
|
|
|
|
{
|
|
|
|
|
FlowControlScope break_scope( break_scopes, dispatch_group.source_location, "",
|
|
|
|
|
dispatch_group.break_label );
|
|
|
|
|
|
|
|
|
|
visit_children( dispatch_group );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class CaseDispatchSelectorAnalyzer : public NodeVisitor
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
explicit CaseDispatchSelectorAnalyzer( Report& report ) : report( report ) {}
|
|
|
|
|
|
|
|
|
|
void visit_identifier( Identifier& identifier ) override
|
|
|
|
|
{
|
2024-01-12 06:51:44 +01:00
|
|
|
report.error( identifier, "Case selector '{}' is not a constant.", identifier.name );
|
2020-09-05 20:41:58 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void visit_string_value( StringValue& sv ) override
|
|
|
|
|
{
|
|
|
|
|
if ( sv.value.size() >= 254 )
|
|
|
|
|
{
|
|
|
|
|
report.error( sv, "String expressions in CASE statements must be <= 253 characters." );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
Report& report;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
void SemanticAnalyzer::visit_case_dispatch_selectors( CaseDispatchSelectors& selectors )
|
|
|
|
|
{
|
|
|
|
|
visit_children( selectors );
|
|
|
|
|
|
|
|
|
|
CaseDispatchSelectorAnalyzer selector_analyzer( report );
|
|
|
|
|
selectors.accept( selector_analyzer );
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-08 01:56:30 -07:00
|
|
|
void SemanticAnalyzer::visit_cstyle_for_loop( CstyleForLoop& loop )
|
|
|
|
|
{
|
|
|
|
|
visit_loop_statement( loop );
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-06 02:26:41 -07:00
|
|
|
void SemanticAnalyzer::visit_do_while_loop( DoWhileLoop& do_while )
|
|
|
|
|
{
|
|
|
|
|
visit_loop_statement( do_while );
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-06 01:22:47 -07:00
|
|
|
void SemanticAnalyzer::visit_foreach_loop( ForeachLoop& node )
|
|
|
|
|
{
|
2020-09-11 00:16:20 -07:00
|
|
|
if ( report_function_name_conflict( node.source_location, node.iterator_name,
|
|
|
|
|
"foreach iterator" ) )
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-06 01:22:47 -07:00
|
|
|
node.expression().accept( *this );
|
|
|
|
|
|
2020-09-12 09:31:52 -07:00
|
|
|
LocalVariableScope scope( local_scopes, node.local_variable_scope_info );
|
2020-09-06 01:22:47 -07:00
|
|
|
scope.create( node.iterator_name, WarnOn::Never, node.source_location );
|
|
|
|
|
scope.create( "_" + node.iterator_name + "_expr", WarnOn::Never, node.source_location );
|
|
|
|
|
scope.create( "_" + node.iterator_name + "_iter", WarnOn::Never, node.source_location );
|
|
|
|
|
|
|
|
|
|
FlowControlScope break_scope( break_scopes, node.source_location, node.get_label(),
|
|
|
|
|
node.break_label );
|
|
|
|
|
FlowControlScope continue_scope( continue_scopes, node.source_location, node.get_label(),
|
|
|
|
|
node.continue_label );
|
|
|
|
|
|
|
|
|
|
node.block().accept( *this );
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-31 23:16:30 -07:00
|
|
|
void SemanticAnalyzer::visit_function_call( FunctionCall& fc )
|
|
|
|
|
{
|
|
|
|
|
// here we turn the arguments passed (which can be named or positional)
|
|
|
|
|
// into the final_arguments vector, which is just one parameter per
|
|
|
|
|
// argument, in the correct order.
|
|
|
|
|
|
|
|
|
|
typedef std::map<std::string, std::unique_ptr<Expression>> ArgumentList;
|
|
|
|
|
ArgumentList arguments_passed;
|
|
|
|
|
|
|
|
|
|
bool any_named = false;
|
|
|
|
|
|
|
|
|
|
std::vector<std::unique_ptr<Argument>> arguments = fc.take_arguments();
|
|
|
|
|
auto parameters = fc.parameters();
|
|
|
|
|
|
|
|
|
|
for ( auto& arg_unique_ptr : arguments )
|
|
|
|
|
{
|
|
|
|
|
auto& arg = *arg_unique_ptr;
|
|
|
|
|
std::string arg_name = arg.identifier;
|
|
|
|
|
if ( arg_name.empty() )
|
|
|
|
|
{
|
|
|
|
|
if ( any_named )
|
|
|
|
|
{
|
2024-01-12 06:51:44 +01:00
|
|
|
report.error( arg, "In call to '{}': Unnamed args cannot follow named args.",
|
|
|
|
|
fc.method_name );
|
2020-08-31 23:16:30 -07:00
|
|
|
return;
|
|
|
|
|
}
|
2020-09-14 22:05:08 -07:00
|
|
|
|
|
|
|
|
if ( arguments_passed.size() >= parameters.size() )
|
|
|
|
|
{
|
2024-01-12 06:51:44 +01:00
|
|
|
report.error( arg, "In call to '{}': Too many arguments passed. Expected {}, got {}.",
|
|
|
|
|
fc.method_name, parameters.size(), arguments.size() );
|
2020-09-14 22:05:08 -07:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
arg_name = parameters.at( arguments_passed.size() ).get().name;
|
2020-08-31 23:16:30 -07:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
any_named = true;
|
|
|
|
|
}
|
|
|
|
|
if ( arguments_passed.find( arg_name ) != arguments_passed.end() )
|
|
|
|
|
{
|
2024-01-12 06:51:44 +01:00
|
|
|
report.error( arg, "In call to '{}': Parameter '{}' passed more than once.", fc.method_name,
|
|
|
|
|
arg_name );
|
2020-08-31 23:16:30 -07:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
arguments_passed[arg_name] = arg.take_expression();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<std::unique_ptr<Node>> final_arguments;
|
|
|
|
|
|
|
|
|
|
for ( auto& param_ref : parameters )
|
|
|
|
|
{
|
|
|
|
|
FunctionParameterDeclaration& param = param_ref.get();
|
|
|
|
|
auto itr = arguments_passed.find( param.name );
|
|
|
|
|
if ( itr == arguments_passed.end() )
|
|
|
|
|
{
|
|
|
|
|
if ( auto default_value = param.default_value() )
|
|
|
|
|
{
|
|
|
|
|
SimpleValueCloner cloner( report, default_value->source_location );
|
|
|
|
|
auto final_argument = cloner.clone( *default_value );
|
|
|
|
|
|
|
|
|
|
if ( final_argument )
|
|
|
|
|
{
|
|
|
|
|
final_arguments.push_back( std::move( final_argument ) );
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2024-01-12 06:51:44 +01:00
|
|
|
report.error(
|
|
|
|
|
param, "In call to '{}': Unable to create argument from default for parameter '{}'.",
|
|
|
|
|
fc.method_name, param.name );
|
2020-08-31 23:16:30 -07:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2024-01-12 06:51:44 +01:00
|
|
|
report.error( fc,
|
|
|
|
|
"In call to '{}': Parameter '{}' was not passed, and there is no default.",
|
|
|
|
|
fc.method_name, param.name );
|
2020-08-31 23:16:30 -07:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
final_arguments.push_back( std::move( ( *itr ).second ) );
|
|
|
|
|
arguments_passed.erase( itr );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for ( auto& unused_argument : arguments_passed )
|
|
|
|
|
{
|
2024-01-12 06:51:44 +01:00
|
|
|
report.error(
|
|
|
|
|
*unused_argument.second,
|
|
|
|
|
"In call to '{}': Parameter '{}' passed by name, but the function has no such parameter.",
|
|
|
|
|
fc.method_name, unused_argument.first );
|
2020-08-31 23:16:30 -07:00
|
|
|
}
|
2024-01-12 06:51:44 +01:00
|
|
|
if ( !arguments_passed.empty() || arguments.size() > parameters.size() )
|
2020-08-31 23:16:30 -07:00
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
fc.children = std::move( final_arguments );
|
|
|
|
|
|
|
|
|
|
// do this afterwards, so that named parameters will not be looked up as identifiers.
|
|
|
|
|
visit_children( fc );
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-31 18:46:27 -07:00
|
|
|
void SemanticAnalyzer::visit_function_parameter_list( FunctionParameterList& node )
|
|
|
|
|
{
|
|
|
|
|
for ( auto& child : boost::adaptors::reverse( node.children ) )
|
|
|
|
|
{
|
|
|
|
|
child->accept( *this );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SemanticAnalyzer::visit_function_parameter_declaration( FunctionParameterDeclaration& node )
|
|
|
|
|
{
|
2020-09-10 01:39:57 -07:00
|
|
|
if ( auto default_value = node.default_value() )
|
|
|
|
|
{
|
|
|
|
|
ConstantValidator validator;
|
|
|
|
|
// By accident, 0-parameter system function calls are allowed as constant values.
|
|
|
|
|
// They are not allowed as default parameters, though.
|
|
|
|
|
if ( !validator.validate( *default_value ) || dynamic_cast<FunctionCall*>( default_value ) )
|
|
|
|
|
{
|
2024-01-12 06:51:44 +01:00
|
|
|
report.error( node,
|
|
|
|
|
"Parameter '{}' has a disallowed default. Only simple operands are allowed as "
|
|
|
|
|
"default arguments.",
|
|
|
|
|
node.name );
|
2020-09-10 01:39:57 -07:00
|
|
|
// but continue, to avoid unknown identifier errors
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-08-31 18:46:27 -07:00
|
|
|
if ( auto existing = locals.find( node.name ) )
|
|
|
|
|
{
|
2024-01-12 06:51:44 +01:00
|
|
|
report.error( node, "Parameter '{}' already defined.", node.name );
|
2020-08-31 18:46:27 -07:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
WarnOn warn_on = node.unused ? WarnOn::IfUsed : WarnOn::IfNotUsed;
|
2020-09-11 00:16:20 -07:00
|
|
|
|
|
|
|
|
if ( report_function_name_conflict( node.source_location, node.name, "function parameter" ) )
|
|
|
|
|
{
|
|
|
|
|
warn_on = WarnOn::Never;
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-31 18:46:27 -07:00
|
|
|
local_scopes.current_local_scope()->create( node.name, warn_on, node.source_location );
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-08 23:20:32 -07:00
|
|
|
void SemanticAnalyzer::visit_function_reference( FunctionReference& node )
|
|
|
|
|
{
|
|
|
|
|
if ( !node.function_link->function() )
|
|
|
|
|
{
|
2024-01-12 06:51:44 +01:00
|
|
|
report.error( node, "User function '{}' not found", node.name );
|
2020-09-08 23:20:32 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-25 00:57:03 -07:00
|
|
|
void SemanticAnalyzer::visit_identifier( Identifier& node )
|
|
|
|
|
{
|
2020-08-30 01:32:38 -07:00
|
|
|
if ( auto local = locals.find( node.name ) )
|
|
|
|
|
{
|
|
|
|
|
local->mark_used();
|
|
|
|
|
node.variable = local;
|
|
|
|
|
}
|
|
|
|
|
else if ( auto global = globals.find( node.name ) )
|
2020-08-25 00:57:03 -07:00
|
|
|
{
|
|
|
|
|
node.variable = global;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2024-01-12 06:51:44 +01:00
|
|
|
report.error( node, "Unknown identifier '{}'.", node.name );
|
2020-08-25 00:57:03 -07:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-04 18:21:08 -07:00
|
|
|
void SemanticAnalyzer::visit_jump_statement( JumpStatement& node )
|
|
|
|
|
{
|
|
|
|
|
auto& scopes = node.jump_type == JumpStatement::Break ? break_scopes : continue_scopes;
|
|
|
|
|
|
|
|
|
|
if ( auto scope = scopes.find( node.label ) )
|
|
|
|
|
{
|
|
|
|
|
node.flow_control_label = scope->flow_control_label;
|
|
|
|
|
node.local_variables_to_remove = locals.count() - scope->local_variables_size;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
auto type_str = node.jump_type == JumpStatement::Break ? "break" : "continue";
|
|
|
|
|
|
|
|
|
|
if ( !node.label.empty() && break_scopes.any() )
|
2024-01-12 06:51:44 +01:00
|
|
|
report.error( node, "Label '{}' not found for {}", node.label, type_str );
|
2020-09-04 18:21:08 -07:00
|
|
|
else
|
2024-01-12 06:51:44 +01:00
|
|
|
report.error( node, "Cannot {} here.", type_str );
|
2020-09-04 18:21:08 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2020-09-03 19:46:27 -07:00
|
|
|
void SemanticAnalyzer::visit_loop_statement( LoopStatement& loop )
|
|
|
|
|
{
|
|
|
|
|
FlowControlScope continue_scope( continue_scopes, loop.source_location, loop.get_label(),
|
|
|
|
|
loop.continue_label );
|
|
|
|
|
FlowControlScope break_scope( break_scopes, loop.source_location, loop.get_label(),
|
|
|
|
|
loop.break_label );
|
|
|
|
|
|
|
|
|
|
visit_children( loop );
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-30 01:32:38 -07:00
|
|
|
void SemanticAnalyzer::visit_program( Program& program )
|
2020-08-25 00:57:03 -07:00
|
|
|
{
|
2020-09-12 09:31:52 -07:00
|
|
|
LocalVariableScope scope( local_scopes, program.local_variable_scope_info );
|
2020-08-30 01:32:38 -07:00
|
|
|
|
|
|
|
|
visit_children( program );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SemanticAnalyzer::visit_program_parameter_declaration( ProgramParameterDeclaration& node )
|
|
|
|
|
{
|
|
|
|
|
if ( auto existing = locals.find( node.name ) )
|
2020-08-25 00:57:03 -07:00
|
|
|
{
|
2024-01-12 06:51:44 +01:00
|
|
|
report.error( node, "Parameter '{}' already defined.", node.name );
|
2020-08-25 00:57:03 -07:00
|
|
|
return;
|
|
|
|
|
}
|
2020-08-30 01:32:38 -07:00
|
|
|
WarnOn warn_on = node.unused ? WarnOn::IfUsed : WarnOn::IfNotUsed;
|
2020-09-11 00:16:20 -07:00
|
|
|
|
|
|
|
|
if ( report_function_name_conflict( node.source_location, node.name, "program parameter" ) )
|
|
|
|
|
{
|
|
|
|
|
warn_on = WarnOn::Never;
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-30 01:32:38 -07:00
|
|
|
local_scopes.current_local_scope()->create( node.name, warn_on, node.source_location );
|
|
|
|
|
}
|
2020-08-25 00:57:03 -07:00
|
|
|
|
2020-09-07 10:28:26 -07:00
|
|
|
void SemanticAnalyzer::visit_repeat_until_loop( RepeatUntilLoop& node )
|
|
|
|
|
{
|
|
|
|
|
visit_loop_statement( node );
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-31 18:46:27 -07:00
|
|
|
void SemanticAnalyzer::visit_user_function( UserFunction& node )
|
|
|
|
|
{
|
2020-09-07 12:35:17 -07:00
|
|
|
if ( node.exported )
|
|
|
|
|
{
|
|
|
|
|
unsigned max_name_length = sizeof( Pol::Bscript::BSCRIPT_EXPORTED_FUNCTION::funcname ) - 1;
|
|
|
|
|
if ( node.name.length() > max_name_length )
|
|
|
|
|
{
|
2024-01-12 06:51:44 +01:00
|
|
|
report.error( node,
|
|
|
|
|
"Exported function name '{}' is too long at {} characters. Max length: {}",
|
|
|
|
|
node.name, node.name.length(), max_name_length );
|
2020-09-07 12:35:17 -07:00
|
|
|
}
|
|
|
|
|
}
|
2020-09-12 09:31:52 -07:00
|
|
|
LocalVariableScope scope( local_scopes, node.local_variable_scope_info );
|
2020-08-31 18:46:27 -07:00
|
|
|
|
|
|
|
|
visit_children( node );
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-30 01:32:38 -07:00
|
|
|
void SemanticAnalyzer::visit_var_statement( VarStatement& node )
|
|
|
|
|
{
|
2020-09-11 00:16:20 -07:00
|
|
|
if ( auto constant = workspace.constants.find( node.name ) )
|
2020-09-01 00:54:24 -07:00
|
|
|
{
|
2024-01-12 06:51:44 +01:00
|
|
|
report.error( node,
|
|
|
|
|
"Cannot define a variable with the same name as constant '{}'.\n"
|
|
|
|
|
" See also: {}",
|
|
|
|
|
node.name, constant->source_location );
|
2020-09-01 00:54:24 -07:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-12 06:51:44 +01:00
|
|
|
report_function_name_conflict( node.source_location, node.name, "variable" );
|
2020-09-11 00:16:20 -07:00
|
|
|
|
2020-08-30 01:32:38 -07:00
|
|
|
if ( auto local_scope = local_scopes.current_local_scope() )
|
|
|
|
|
{
|
|
|
|
|
node.variable = local_scope->create( node.name, WarnOn::Never, node.source_location );
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if ( auto existing = globals.find( node.name ) )
|
|
|
|
|
{
|
2024-01-12 06:51:44 +01:00
|
|
|
report.error( node,
|
|
|
|
|
"Global variable '{}' already defined.\n"
|
|
|
|
|
" See also: {}",
|
|
|
|
|
node.name, existing->source_location );
|
2020-08-30 01:32:38 -07:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
node.variable = globals.create( node.name, 0, WarnOn::Never, node.source_location );
|
|
|
|
|
}
|
2020-08-25 00:57:03 -07:00
|
|
|
visit_children( node );
|
2020-08-10 01:37:07 -07:00
|
|
|
}
|
|
|
|
|
|
2020-09-28 08:11:10 -07:00
|
|
|
void SemanticAnalyzer::visit_variable_assignment_statement( VariableAssignmentStatement& node )
|
|
|
|
|
{
|
|
|
|
|
visit_children( node );
|
|
|
|
|
|
|
|
|
|
if ( auto bop = dynamic_cast<BinaryOperator*>( &node.rhs() ) )
|
|
|
|
|
{
|
|
|
|
|
if ( bop->token_id == TOK_ASSIGN )
|
|
|
|
|
{
|
|
|
|
|
if ( auto second_ident = dynamic_cast<Identifier*>( &bop->lhs() ) )
|
|
|
|
|
{
|
|
|
|
|
if ( node.identifier().variable == second_ident->variable )
|
|
|
|
|
{
|
|
|
|
|
// we have something like
|
|
|
|
|
// a := a := expr;
|
2024-01-12 06:51:44 +01:00
|
|
|
report.warning( node, "Double-assignment to the same variable '{}'.",
|
|
|
|
|
node.identifier().name );
|
2020-09-28 08:11:10 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-03 19:46:27 -07:00
|
|
|
void SemanticAnalyzer::visit_while_loop( WhileLoop& node )
|
|
|
|
|
{
|
|
|
|
|
visit_loop_statement( node );
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-11 00:16:20 -07:00
|
|
|
bool SemanticAnalyzer::report_function_name_conflict( const SourceLocation& referencing_loc,
|
|
|
|
|
const std::string& function_name,
|
|
|
|
|
const std::string& element_description )
|
|
|
|
|
{
|
|
|
|
|
return report_function_name_conflict( workspace, report, referencing_loc, function_name,
|
|
|
|
|
element_description );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool SemanticAnalyzer::report_function_name_conflict( const CompilerWorkspace& workspace,
|
|
|
|
|
Report& report,
|
|
|
|
|
const SourceLocation& referencing_loc,
|
|
|
|
|
const std::string& function_name,
|
|
|
|
|
const std::string& element_description )
|
|
|
|
|
{
|
|
|
|
|
auto func_itr = workspace.all_function_locations.find( function_name );
|
|
|
|
|
if ( func_itr != workspace.all_function_locations.end() )
|
|
|
|
|
{
|
|
|
|
|
const SourceLocation& function_loc = ( *func_itr ).second;
|
2024-01-12 06:51:44 +01:00
|
|
|
report.error( referencing_loc,
|
|
|
|
|
"Cannot define a {} with the same name as function '{}'.\n"
|
|
|
|
|
" Defined here: {}",
|
|
|
|
|
element_description, function_name, function_loc );
|
2020-09-11 00:16:20 -07:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-10 03:20:29 -07:00
|
|
|
} // namespace Pol::Bscript::Compiler
|