diff --git a/src/Binder/Binder.cpp b/src/Binder/Binder.cpp index c928202..cb6b92a 100644 --- a/src/Binder/Binder.cpp +++ b/src/Binder/Binder.cpp @@ -73,15 +73,21 @@ BoundStatement* Binder::BindAssignmentStatement(const ParsedStatement *statement BoundStatement *Binder::BindIndexAssignmentStatement(const ParsedStatement *statement) { auto s = (ParsedIndexAssignmentStatement*) statement; - auto boundIndexExpression = this -> BindIndexExpression((IndexExpression*)s->GetIndexExpression(), true); + auto indexExp = s -> GetIndexExpression(); + const BoundExpression* indexable; + if (indexExp->GetKind() == ParsedExpressionKind::Indexer){ + indexable = this -> BindIndexExpression((IndexExpression*)indexExp, true); + } else{ + indexable = this -> BindPeriodIndexExpression((PeriodIndexExpression*)indexExp, true); + } auto valueExpression = this -> BindExpression(s->GetValueExpression()); - auto boundIndexType = boundIndexExpression -> GetType(); + auto boundIndexType = indexable -> GetType(); if (boundIndexType ->GetClass() != TypeClass ::Error && boundIndexType->operator!=(valueExpression->GetType().get())){ this -> _scriptData -> Diagnostics -> LogError(DiagnosticCode::InvalidTableValueType, statement->GetStartPosition(), statement->GetLength()); return new BoundBadStatement(); } - return new BoundIndexAssignmentStatement(boundIndexExpression, valueExpression); + return new BoundIndexAssignmentStatement(indexable, valueExpression); } std::shared_ptr ParseTypeIdentifier(HashedString s){ @@ -209,6 +215,8 @@ BoundExpression* Binder::BindExpression(const ParsedExpression* expression){ case ParsedExpressionKind ::Indexer: return this->BindIndexExpression((IndexExpression*)expression, false); + case ParsedExpressionKind::PeriodIndexer: + return this -> BindPeriodIndexExpression((PeriodIndexExpression*)expression, false); case ParsedExpressionKind::NumericalTable: return this -> BindNumericalTableExpression((ParsedNumericalTableExpression*)expression); case ParsedExpressionKind ::Table: @@ -449,6 +457,37 @@ BoundExpression *Binder::BindIndexExpression(const IndexExpression *expression, return new BoundIndexExpression(indexer, index, resultType, expression->GetStartPosition(), expression->GetLength()); } +BoundExpression* Binder::BindPeriodIndexExpression(const PeriodIndexExpression* expression, bool setter){ + auto indexer = this->BindExpression(expression->GetIndexer()); + const auto& identifier = expression->GetIndex(); + const auto& indexerType = indexer -> GetType(); + if (!indexerType -> CanBeIndexedWithIdentifier(identifier.GetHash())){ + this->_scriptData->Diagnostics->LogError(DiagnosticCode::CantIndex, expression->GetStartPosition(), + expression->GetLength()); + return new BoundBadExpression(expression->GetStartPosition(), expression->GetLength()); + } + if (indexerType -> GetClass() == TypeClass::UserData){ + auto field = dynamic_pointer_cast(indexerType) -> GetField(identifier . GetHash()); + if (!setter){ + if (!field->HasGetter()){ + this->_scriptData->Diagnostics->LogError(DiagnosticCode::UserDataFieldNoGetter, expression->GetStartPosition(), + expression->GetLength()); + return new BoundBadExpression(expression->GetStartPosition(), expression->GetLength()); + } + } else{ + if (!field->HasSetter()){ + this->_scriptData->Diagnostics->LogError(DiagnosticCode::UserDataFieldNoSetter, expression->GetStartPosition(), + expression->GetLength()); + return new BoundBadExpression(expression->GetStartPosition(), expression->GetLength()); + } + } + } + + auto resultType = indexer->GetType()->GetIndexedType(identifier . GetHash()); + return new BoundPeriodIndexExpression(indexer, identifier, resultType, expression->GetStartPosition(), expression->GetLength()); + +} + BoundExpression* Binder::BindNumericalTableExpression(const ParsedNumericalTableExpression* expression){ auto expressions = expression->GetExpressions(); auto boundExpressions = vector(expressions-> size()); diff --git a/src/Binder/Binder.hpp b/src/Binder/Binder.hpp index bcec464..baf13d7 100644 --- a/src/Binder/Binder.hpp +++ b/src/Binder/Binder.hpp @@ -36,6 +36,7 @@ class Binder { public: static BoundScriptStatement* Bind(Script* script, const ParsedScriptStatement* s, BoundScope* scriptScope); + BoundExpression *BindPeriodIndexExpression(const PeriodIndexExpression *expression, bool setter); }; diff --git a/src/Binder/BoundExpressions/BoundExpression.hpp b/src/Binder/BoundExpressions/BoundExpression.hpp index 21cbc82..50522e5 100644 --- a/src/Binder/BoundExpressions/BoundExpression.hpp +++ b/src/Binder/BoundExpressions/BoundExpression.hpp @@ -24,6 +24,7 @@ enum class BoundExpressionKind{ Binary, FunctionCall, Index, + PeriodIndex, NumericalTable, Table, }; @@ -279,6 +280,31 @@ public: } }; +class BoundPeriodIndexExpression : public BoundExpression { + BoundExpression* _indexableExpression; + HashedString _index; +public: + BoundPeriodIndexExpression(BoundExpression* indexableExpression, HashedString index, shared_ptr result, + unsigned int start, unsigned int length) + : BoundExpression(start, length, std::move(result)), _indexableExpression(indexableExpression), _index(index) {} + + ~BoundPeriodIndexExpression() final{ + delete _indexableExpression; + } + + const BoundExpressionKind GetKind() const final{ + return BoundExpressionKind ::PeriodIndex; + } + + const BoundExpression* GetIndexableExpression() const{ + return _indexableExpression; + } + + const HashedString GetIndex() const{ + return _index; + } +}; + class BoundNumericalTableExpression : public BoundExpression{ const vector _expressions; public: diff --git a/src/Binder/BoundStatements/BoundStatement.hpp b/src/Binder/BoundStatements/BoundStatement.hpp index d470eca..2fc342f 100644 --- a/src/Binder/BoundStatements/BoundStatement.hpp +++ b/src/Binder/BoundStatements/BoundStatement.hpp @@ -129,7 +129,7 @@ class BoundIndexAssignmentStatement : public BoundStatement{ const BoundExpression* _indexExpression; const BoundExpression* _valueExpression; public: - BoundIndexAssignmentStatement(BoundExpression* index, BoundExpression* value) + BoundIndexAssignmentStatement(const BoundExpression* index, BoundExpression* value) : _indexExpression(index), _valueExpression(value) { } diff --git a/src/Evaluator/EvalValues/EvalValue.hpp b/src/Evaluator/EvalValues/EvalValue.hpp index 9dd373c..fd0f5a6 100644 --- a/src/Evaluator/EvalValues/EvalValue.hpp +++ b/src/Evaluator/EvalValues/EvalValue.hpp @@ -41,6 +41,10 @@ public: throw EvaluationException("Can't index this EvalValue"); } + virtual shared_ptr IndexValue(uint32_t hash){ + throw EvaluationException("Can't index this EvalValue"); + } + virtual void SetIndexValue(EvalValue *key, shared_ptr value){ throw EvaluationException("Can't index this EvalValue"); } diff --git a/src/Evaluator/EvalValues/TableEvalValue.hpp b/src/Evaluator/EvalValues/TableEvalValue.hpp index 51b3c5d..a3628a8 100644 --- a/src/Evaluator/EvalValues/TableEvalValue.hpp +++ b/src/Evaluator/EvalValues/TableEvalValue.hpp @@ -41,6 +41,10 @@ public: return this -> _table->at(hash); } + shared_ptr IndexValue(uint32_t hash) final{ + return this -> _table->at(hash); + } + shared_ptr IndexValue(const char* val){ auto hash = HashedString::ConstHash(val); return this -> _table -> at(hash); diff --git a/src/Evaluator/Evaluator.cpp b/src/Evaluator/Evaluator.cpp index 075d80e..cdd10b6 100644 --- a/src/Evaluator/Evaluator.cpp +++ b/src/Evaluator/Evaluator.cpp @@ -135,6 +135,7 @@ shared_ptr Evaluator::EvaluateIntegerExpression(const BoundExp case BoundExpressionKind::Variable: return dynamic_pointer_cast(this->GetVariable((BoundVariableExpression*)expression)); case BoundExpressionKind ::FunctionCall: return dynamic_pointer_cast(this->EvaluateFunctionCallExpression(expression)); case BoundExpressionKind ::Index: return dynamic_pointer_cast(this->EvaluateIndexExpression(expression)); + case BoundExpressionKind ::PeriodIndex: return dynamic_pointer_cast(this->EvaluatePeriodIndexExpression(expression)); case BoundExpressionKind ::LiteralString: case BoundExpressionKind ::LiteralBool: @@ -153,6 +154,7 @@ shared_ptr Evaluator::EvaluateBoolExpression(const BoundExpres case BoundExpressionKind::Variable: return dynamic_pointer_cast(this->GetVariable((BoundVariableExpression*)expression)); case BoundExpressionKind ::FunctionCall: return dynamic_pointer_cast(this->EvaluateFunctionCallExpression(expression)); case BoundExpressionKind ::Index: return dynamic_pointer_cast(this->EvaluateIndexExpression(expression)); + case BoundExpressionKind ::PeriodIndex: return dynamic_pointer_cast(this->EvaluatePeriodIndexExpression(expression)); case BoundExpressionKind::Bad: case BoundExpressionKind::LiteralInteger: @@ -174,6 +176,7 @@ shared_ptr Evaluator::EvaluateStringExpression(const BoundExpre case BoundExpressionKind::Variable: return dynamic_pointer_cast(this->GetVariable((BoundVariableExpression*)expression)); case BoundExpressionKind ::FunctionCall: return dynamic_pointer_cast(this->EvaluateFunctionCallExpression(expression)); case BoundExpressionKind ::Index: return dynamic_pointer_cast(this->EvaluateIndexExpression(expression)); + case BoundExpressionKind ::PeriodIndex: return dynamic_pointer_cast(this->EvaluatePeriodIndexExpression(expression)); case BoundExpressionKind::Bad: case BoundExpressionKind::LiteralInteger: @@ -191,6 +194,7 @@ shared_ptr Evaluator::EvaluateFunctionExpression(const BoundExpressio switch (expression->GetKind()){ case BoundExpressionKind ::Variable: return this->GetVariable((BoundVariableExpression*)expression); case BoundExpressionKind ::Index: return this->EvaluateIndexExpression(expression); + case BoundExpressionKind ::PeriodIndex: return this->EvaluatePeriodIndexExpression(expression); default: throw; } } @@ -210,6 +214,7 @@ shared_ptr Evaluator::EvaluateTableExpression(const BoundExpression * case BoundExpressionKind ::Index: return this->EvaluateIndexExpression(expression); case BoundExpressionKind ::NumericalTable: return this-> EvaluateNumericTableExpression(expression); case BoundExpressionKind ::Table: return this -> EvaluateComplexTableExpression(expression); + case BoundExpressionKind ::PeriodIndex: return this->EvaluatePeriodIndexExpression(expression); default: throw; } @@ -276,6 +281,13 @@ shared_ptr Evaluator::EvaluateIndexExpression(const BoundExpression * return indexable -> IndexValue(index.get()) -> Clone(); } +shared_ptr Evaluator::EvaluatePeriodIndexExpression(const BoundExpression *expression) { + auto indexExpression = (BoundPeriodIndexExpression*)expression; + auto index = indexExpression -> GetIndex().GetHash(); + auto indexable = this -> EvaluateExpression(indexExpression->GetIndexableExpression()); + return indexable -> IndexValue(index) -> Clone(); +} + shared_ptr Evaluator::EvaluateNumericTableExpression(const BoundExpression *expression) { auto tableExpression = (BoundNumericalTableExpression*)expression; auto valueExpressions = tableExpression->GetExpressions(); diff --git a/src/Evaluator/Evaluator.hpp b/src/Evaluator/Evaluator.hpp index ebd5421..2fa5c86 100644 --- a/src/Evaluator/Evaluator.hpp +++ b/src/Evaluator/Evaluator.hpp @@ -47,6 +47,7 @@ class Evaluator { shared_ptr EvaluateBooleanUnary(const BoundUnaryExpression *expression); shared_ptr EvaluateFunctionCallExpression(const BoundExpression *expression); shared_ptr EvaluateIndexExpression(const BoundExpression* expression); + shared_ptr EvaluatePeriodIndexExpression(const BoundExpression* expression); shared_ptr EvaluateNumericTableExpression(const BoundExpression* expression); shared_ptr EvaluateComplexTableExpression(const BoundExpression *expression); shared_ptr EvaluateUserDataExpression(const BoundExpression *expression); diff --git a/src/Parser/ParsedExpressions/ParsedExpression.hpp b/src/Parser/ParsedExpressions/ParsedExpression.hpp index ae343d7..087c288 100644 --- a/src/Parser/ParsedExpressions/ParsedExpression.hpp +++ b/src/Parser/ParsedExpressions/ParsedExpression.hpp @@ -25,6 +25,7 @@ enum class ParsedExpressionKind{ Parenthesized, FunctionCall, Indexer, + PeriodIndexer, NumericalTable, Table, }; @@ -287,6 +288,34 @@ public: } }; +class PeriodIndexExpression : public ParsedExpression{ + const ParsedExpression* _indexerExpression; + const HashedString _index; +public: + PeriodIndexExpression(ParsedExpression* indexer, HashedString index, unsigned int start, unsigned int length) + :ParsedExpression(start, length), + _indexerExpression(indexer), _index(index) + { + } + + ~PeriodIndexExpression() final{ + delete _indexerExpression; + } + + const ParsedExpressionKind GetKind() const final{ + return ParsedExpressionKind::PeriodIndexer; + } + + const ParsedExpression* GetIndexer() const{ + return _indexerExpression; + } + + const HashedString& GetIndex() const{ + return _index; + } +}; + + class ParsedNumericalTableExpression : public ParsedExpression{ vector _expressions; public: diff --git a/src/Parser/Parser.cpp b/src/Parser/Parser.cpp index 987baf0..fd2a59c 100644 --- a/src/Parser/Parser.cpp +++ b/src/Parser/Parser.cpp @@ -46,7 +46,9 @@ ParsedStatement* Parser::ParseStatement(const IToken* current){ return ParseVariableAssignment(current); } auto expression = this -> ParseExpression(current); - if (expression->GetKind() == ParsedExpressionKind::Indexer && this -> Peek()->GetKind() == TokenKind::AssignmentToken){ + auto expKind = expression -> GetKind(); + if ((expKind == ParsedExpressionKind::Indexer || expKind == ParsedExpressionKind::PeriodIndexer) + && this -> Peek()->GetKind() == TokenKind::AssignmentToken){ return this -> ParseIndexAssignment(expression); } return new ParsedExpressionStatement(expression); @@ -206,7 +208,7 @@ ParsedExpression* Parser::ParseExpression(const IToken* current){ } else if (peekKind == TokenKind::OpenSquareBracket){ expression = this->ParseIndexExpression(expression); } else { - //TODO: index period expression + expression = this -> ParsePeriodIndexExpression(expression); } if (this -> _position >= this->_tokens.size()) break; @@ -379,6 +381,18 @@ ParsedExpression* Parser::ParseIndexExpression(ParsedExpression* indexingExpress return new IndexExpression(indexingExpression, indexExpression, start, closeBracket->GetEndPosition() - start); } +ParsedExpression* Parser::ParsePeriodIndexExpression(ParsedExpression* indexingExpression){ + this->Next(); // consume '.' token + auto identifier = this -> Next(); + if (identifier->GetKind() != TokenKind::Identifier){ + this->ScriptData->Diagnostics->LogError(DiagnosticCode::UnexpectedToken, identifier->GetStartPosition(), identifier->GetLength()); + return new BadExpression(indexingExpression->GetStartPosition(), identifier->GetEndPosition() - indexingExpression->GetStartPosition()); + } + auto start = indexingExpression->GetStartPosition(); + return new PeriodIndexExpression(indexingExpression, ((IdentifierToken*)identifier)->GetValue(), start, identifier->GetEndPosition() - start); +} + + ParsedExpression* Parser::ParseTableExpression(const IToken* current){ if (this -> Peek() -> GetKind() == TokenKind::CloseCurlyBracket){ this -> Next(); diff --git a/src/Parser/Parser.hpp b/src/Parser/Parser.hpp index 3952956..845ee01 100644 --- a/src/Parser/Parser.hpp +++ b/src/Parser/Parser.hpp @@ -41,6 +41,7 @@ class Parser { ParsedExpression* ParseFunctionCallExpression(ParsedExpression* functionExpression); ParsedExpression *ParseIndexExpression(ParsedExpression *indexingExpression); + ParsedExpression *ParsePeriodIndexExpression(ParsedExpression *indexingExpression); ParsedExpression *ParseTableExpression(const IToken *current); public: ParsedScriptStatement* Parse(); @@ -49,6 +50,7 @@ public: _position = 0; ScriptData = scriptData; } + }; diff --git a/src/ScriptType.cpp b/src/ScriptType.cpp index bf57108..d4fde09 100644 --- a/src/ScriptType.cpp +++ b/src/ScriptType.cpp @@ -1,11 +1,11 @@ #include "Script.hpp" -bool ScriptType::CanBeIndexedWith(ScriptType *indexer) { +const bool ScriptType::CanBeIndexedWith(ScriptType *indexer) const{ // String type is the only simple script type we want to return _class == TypeClass::String && indexer->_class == TypeClass::Number && !((NumericScriptType*)indexer)->IsFloat(); } -shared_ptr ScriptType::GetIndexedType(ScriptType *indexer) { +const shared_ptr ScriptType::GetIndexedType(ScriptType *indexer) const{ if (_class == TypeClass::String){ return make_shared(TypeClass::String); } diff --git a/src/ScriptType.hpp b/src/ScriptType.hpp index 7573426..796bb07 100644 --- a/src/ScriptType.hpp +++ b/src/ScriptType.hpp @@ -1,3 +1,5 @@ +#include + #ifndef PORYGONLANG_SCRIPTTYPE_HPP #define PORYGONLANG_SCRIPTTYPE_HPP @@ -30,28 +32,34 @@ public: virtual ~ScriptType() = default; - const TypeClass GetClass(){ + const TypeClass GetClass() const{ return _class; } - virtual bool operator ==(const ScriptType& b){ + virtual bool operator ==(const ScriptType& b) const{ return _class == b._class; }; - virtual bool operator ==(ScriptType* b){ + virtual bool operator ==(ScriptType* b) const{ return _class == b->_class; }; - virtual bool operator !=(const ScriptType& b){ + virtual bool operator !=(const ScriptType& b) const{ return ! (operator==(b)); } - virtual bool operator !=(ScriptType* b){ + virtual bool operator !=(ScriptType* b) const{ return ! (operator==(b)); } - virtual bool CanBeIndexedWith(ScriptType* indexer); + virtual const bool CanBeIndexedWith(ScriptType* indexer) const; + virtual const bool CanBeIndexedWithIdentifier(uint32_t hash) const{ + return false; + } - virtual shared_ptr GetIndexedType(ScriptType* indexer); + virtual const shared_ptr GetIndexedType(ScriptType* indexer) const; + virtual const shared_ptr GetIndexedType(uint32_t hash) const{ + throw "Shouldn't be possible"; + } }; class NumericScriptType : public ScriptType{ @@ -65,11 +73,11 @@ public: _isFloat = isFloat; } - bool IsAwareOfFloat(){ + const bool IsAwareOfFloat() const{ return _awareOfFloat; } - bool IsFloat(){ + const bool IsFloat() const{ return _isFloat; } }; @@ -83,11 +91,11 @@ public: _hashValue = hashValue; } - bool IsKnownAtBind(){ + const bool IsKnownAtBind() const{ return _isKnownAtBind; } - uint32_t GetHashValue(){ + const uint32_t GetHashValue() const{ return _hashValue; } }; @@ -106,23 +114,23 @@ public: _parameterKeys = std::move(parameterKeys); _scopeIndex = scopeIndex; } - shared_ptr GetReturnType(){ + const shared_ptr GetReturnType() const{ return _returnType; } void SetReturnType(shared_ptr t){ - _returnType = t; + _returnType = std::move(t); } - vector> GetParameterTypes(){ + const vector> GetParameterTypes() const{ return _parameterTypes; } - vector> GetParameterKeys(){ + const vector> GetParameterKeys() const{ return _parameterKeys; } - int GetScopeIndex(){ + const int GetScopeIndex() const{ return _scopeIndex; } }; @@ -135,11 +143,11 @@ public: _valueType = std::move(valueType); } - bool CanBeIndexedWith(ScriptType* indexer) final{ + const bool CanBeIndexedWith(ScriptType* indexer) const final{ return indexer->GetClass() == TypeClass ::Number; } - shared_ptr GetIndexedType(ScriptType* indexer) final{ + const shared_ptr GetIndexedType(ScriptType* indexer) const final{ return _valueType; } }; diff --git a/src/TableScriptType.hpp b/src/TableScriptType.hpp index 0cc515a..bc6929f 100644 --- a/src/TableScriptType.hpp +++ b/src/TableScriptType.hpp @@ -21,11 +21,15 @@ public: delete _values; } - bool CanBeIndexedWith(ScriptType* indexer) final{ + const bool CanBeIndexedWith(ScriptType* indexer) const final{ return indexer->GetClass() == TypeClass ::String; } - shared_ptr GetIndexedType(ScriptType* indexer) final{ + const bool CanBeIndexedWithIdentifier(uint32_t hash) const final{ + return true; + } + + const shared_ptr GetIndexedType(ScriptType* indexer) const final{ auto stringKey = (StringScriptType*)indexer; if (stringKey->IsKnownAtBind()){ return _values-> at(stringKey->GetHashValue())->GetType(); @@ -33,11 +37,15 @@ public: throw "TODO: indexing with dynamic keys"; } - const unordered_map* GetValues(){ + const shared_ptr GetIndexedType(uint32_t hash) const final{ + return _values-> at(hash)->GetType(); + } + + const unordered_map* GetValues() const{ return _values; } - const int GetLocalVariableCount(){ + const int GetLocalVariableCount() const{ return _localVariableCount; } diff --git a/src/UserData/UserDataScriptType.hpp b/src/UserData/UserDataScriptType.hpp index d625776..bd50211 100644 --- a/src/UserData/UserDataScriptType.hpp +++ b/src/UserData/UserDataScriptType.hpp @@ -18,7 +18,7 @@ public: _userData = std::move(ud); } - bool CanBeIndexedWith(ScriptType* indexer) final{ + const bool CanBeIndexedWith(ScriptType* indexer) const final{ if (indexer->GetClass() != TypeClass ::String){ return false; } @@ -28,17 +28,25 @@ public: return _userData->ContainsField(str->GetHashValue()); } + const bool CanBeIndexedWithIdentifier(uint32_t hash) const final{ + return true; + } + UserDataField* GetField(uint32_t id){ return _userData -> GetField(id); } - shared_ptr GetIndexedType(ScriptType* indexer) final{ + const shared_ptr GetIndexedType(ScriptType* indexer) const final{ auto stringKey = (StringScriptType*)indexer; if (stringKey->IsKnownAtBind()){ return _userData->GetField(stringKey->GetHashValue())->GetType(); } throw "TODO: indexing with dynamic keys"; } + + const shared_ptr GetIndexedType(uint32_t hash) const final{ + return _userData->GetField(hash)->GetType(); + } }; diff --git a/src/UserData/UserDataValue.hpp b/src/UserData/UserDataValue.hpp index a998584..8b85dfe 100644 --- a/src/UserData/UserDataValue.hpp +++ b/src/UserData/UserDataValue.hpp @@ -46,6 +46,11 @@ public: return shared_ptr(field->Get(_obj)); } + shared_ptr IndexValue(uint32_t hash) final{ + auto field = _userData->GetField(hash); + return shared_ptr(field->Get(_obj)); + } + void SetIndexValue(EvalValue *key, shared_ptr value) final{ auto fieldId = key->GetHashCode(); auto field = _userData->GetField(fieldId); diff --git a/tests/integration/IndexTests.cpp b/tests/integration/IndexTests.cpp index d027968..a60a895 100644 --- a/tests/integration/IndexTests.cpp +++ b/tests/integration/IndexTests.cpp @@ -11,4 +11,18 @@ TEST_CASE( "String indexing", "[integration]" ) { delete script; } +TEST_CASE( "Identifier Index", "[integration]" ) { + auto script = Script::Create(uR"( +foo = { + bar = "test" +} +return foo.bar +)"); + REQUIRE(!script->Diagnostics -> HasErrors()); + auto result = script->Evaluate(); + REQUIRE(*result->EvaluateString() == u"test"); + delete script; +} + + #endif