dvander@195: /* dvander@195: * Copyright (C) 2012 David Anderson dvander@195: * dvander@251: * This file is part of SourcePawn. dvander@195: * dvander@251: * SourcePawn is free software: you can redistribute it and/or modify it under dvander@195: * the terms of the GNU General Public License as published by the Free dvander@195: * Software Foundation, either version 3 of the License, or (at your option) dvander@195: * any later version. dvander@195: * dvander@251: * SourcePawn is distributed in the hope that it will be useful, but WITHOUT ANY dvander@195: * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS dvander@195: * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. dvander@195: * dvander@195: * You should have received a copy of the GNU General Public License along with dvander@251: * SourcePawn. If not, see http://www.gnu.org/licenses/. dvander@195: */ dvander@195: #include dvander@252: #include dvander@195: #include "../Zone.h" dvander@195: #include "../Interpreter.h" dvander@251: #include "../Packages.h" dvander@251: #include "../FileSystem.h" dvander@256: #include "../FixedArray.h" dvander@256: #include "../Vector.h" dvander@195: #include "Scanner.h" dvander@195: #include "CompileContext.h" dvander@195: #include "Parser.h" dvander@195: #include "NameBinding.h" dvander@195: #include "SemanticAnalysis.h" dvander@195: #include "../Spaces-inl.h" dvander@195: #include "../Heap-inl.h" dvander@195: dvander@195: using namespace ke; dvander@195: asherkin@262: CompileContext::CompileContext(Zone *zone) dvander@195: : zone_(zone), dvander@250: active_(NULL) dvander@195: { dvander@195: } dvander@195: dvander@195: CompileContext::~CompileContext() dvander@195: { dvander@250: for (size_t i = 0; i < units_.length(); i++) dvander@251: units_[i]->destroy(); dvander@195: } dvander@195: dvander@195: bool dvander@251: CompileContext::initialize(Handle package, const char *name) dvander@195: { dvander@251: size_t len = strlen(name); dvander@251: AutoFree path((char *)zone_->malloc(len + 1)); dvander@251: if (!*path) dvander@251: return false; dvander@251: dvander@251: strcpy(*path, name); dvander@251: if (len > 3 && strcmp(&name[len - 3], ".sp") == 0) dvander@251: (*path)[len - 3] = '\0'; dvander@251: dvander@251: Local strname(zone_, zone_->makeSymbol(*path)); dvander@251: if (!strname) dvander@195: return false; dvander@195: dvander@256: PathComponent p(new (zone_->pool()) ScopedRoot(strname), NULL); dvander@251: dvander@251: Local importable(zone_); dvander@251: if (!zone_->packages()->findOrAdd(package, &p, importable.address())) dvander@251: return false; dvander@251: dvander@251: // If we couldn't find anything on the filesystem to match, we error out dvander@251: // early. dvander@251: if (!importable) { dvander@251: zone_->reportError(Message_CannotImportPath, *path); dvander@195: return false; dvander@251: } dvander@251: dvander@251: if (importable->isModule()) { dvander@251: Local module(zone_, Module::cast(importable)); dvander@251: if (!add(module)) dvander@251: return true; dvander@251: } else { dvander@251: // nyi dvander@251: assert(false); dvander@251: } dvander@195: dvander@195: return true; dvander@195: } dvander@195: dvander@195: char * dvander@251: CompileContext::loadSource(Handle module, size_t *lengthp) dvander@195: { dvander@195: char *bytes = NULL; dvander@195: size_t length; dvander@195: dvander@251: FILE *fp = fopen(module->path()->chars(), "rb"); dvander@195: if (!fp) { dvander@195: reportError(SourcePosition(), Message_CantOpenFile); dvander@195: return NULL; dvander@195: } dvander@195: dvander@195: if (fseek(fp, 0, SEEK_END)) { dvander@195: reportError(SourcePosition(), Message_ErrorSeekingFile); dvander@195: fclose(fp); dvander@195: return NULL; dvander@195: } dvander@195: long size = ftell(fp); dvander@195: if (size < 0) { dvander@195: reportError(SourcePosition(), Message_ErrorSeekingFile); dvander@195: fclose(fp); dvander@195: return NULL; dvander@195: } dvander@195: rewind(fp); dvander@195: length = size_t(size); dvander@195: bytes = new char[length + 1]; dvander@195: if (fread(bytes, 1, length, fp) != length) { dvander@195: reportError(SourcePosition(), Message_ErrorReadingFile); dvander@195: fclose(fp); dvander@195: delete [] bytes; dvander@195: return NULL; dvander@195: } dvander@195: fclose(fp); dvander@195: dvander@195: *lengthp = length; dvander@195: return bytes; dvander@195: } dvander@195: dvander@195: bool dvander@195: CompileContext::parse(TranslationUnit *unit) dvander@195: { dvander@195: assert(!unit->tree_); dvander@195: dvander@195: size_t length; dvander@251: char *source = loadSource(unit->module(), &length); dvander@195: if (!source) dvander@195: return false; dvander@195: dvander@195: Parser parser(ZONE(), *this, source, length); dvander@195: if ((unit->tree_ = parser.parse()) == NULL) dvander@195: return false; dvander@195: dvander@195: return true; dvander@195: } dvander@195: dvander@251: bool dvander@195: CompileContext::evaluateImports(TranslationUnit *unit) dvander@195: { dvander@251: PoolList *imports = unit->tree()->imports(); dvander@251: dvander@256: if (!imports->length()) dvander@256: return true; dvander@256: dvander@256: // We're allowed to use Handle<> here instead of ScopedRoot<> because dvander@256: // we get the handle from ImportStatement which has a ScopedRoot<>. dvander@256: Vector > vec; dvander@256: dvander@251: for (size_t i = 0; i < imports->length(); i++) { dvander@251: ImportStatement *import = imports->at(i); dvander@251: dvander@256: // Crawl the path until we find a valid file to load. dvander@251: Local obj(zone_); dvander@251: Local package(zone_, unit->module()->parent()); dvander@251: if (!zone_->packages()->findOrAdd(package, import->path(), obj.address())) dvander@251: return false; dvander@251: if (!obj) { dvander@251: PathComponent *path = import->path(); dvander@251: while (path->next) dvander@251: path = path->next; dvander@251: reportError(import->pos(), Message_CannotImportPath, path->name->chars()); dvander@251: return false; dvander@251: } dvander@195: dvander@251: // If the object is a package, we will attempt to execute it dvander@251: // immediately after resolving imports. dvander@251: if (obj->isPackage()) { dvander@251: assert(false); dvander@251: } else { dvander@251: Local module(zone_, Module::cast(obj)); dvander@251: if (!add(module)) dvander@251: return false; dvander@251: } dvander@256: dvander@256: import->setSource(obj); dvander@256: dvander@256: // See if there are any duplicates. This is O(n^2), but we expect dvander@256: // |n| to be small. dvander@256: unsigned index = 0; dvander@256: for (; index < vec.length(); index++) { dvander@256: if (vec[index] == obj) dvander@256: break; dvander@256: } dvander@256: if (index == vec.length() && !vec.append(import->source())) dvander@256: return false; dvander@256: dvander@256: import->setImportIndex(index); dvander@256: } dvander@256: dvander@256: if (!IsUint32MultiplySafe(vec.length(), 2)) { dvander@256: zone_->reportAllocationOverflow(); dvander@256: return false; dvander@251: } dvander@195: dvander@256: // Create an array of the import and paths. dvander@256: Local importables(zone_, FixedArray::New(zone_, vec.length() * 2, Heap::Tenure_Default)); dvander@256: if (!importables) dvander@256: return false; dvander@256: dvander@256: for (unsigned i = 0; i < vec.length(); i++) { dvander@256: importables->set(zone_, i * 2, vec[i]); dvander@256: dvander@256: unsigned count = 0; dvander@256: PathComponent *path = imports->at(i)->path(); dvander@256: for (PathComponent *iter = path; iter; iter = iter->next) dvander@256: count++; dvander@256: dvander@256: Local list(zone_, FixedArray::New(zone_, count, Heap::Tenure_Default)); dvander@256: if (!list) dvander@256: return false; dvander@256: dvander@256: count = 0; dvander@256: for (PathComponent *iter = path; iter; iter = iter->next, count++) dvander@256: list->set(zone_, count, iter->name); dvander@256: dvander@256: importables->set(zone_, i * 2 + 1, list); dvander@256: } dvander@256: dvander@256: unit->module()->setImports(importables); dvander@251: return true; dvander@251: } dvander@195: dvander@251: bool dvander@251: CompileContext::add(Handle module) dvander@251: { dvander@260: if (module->compiled() || module->translationUnit()) dvander@251: return true; dvander@251: dvander@251: TranslationUnit *tu = new (zone_->pool()) TranslationUnit(module); dvander@251: dvander@251: if (!unparsed_.append(tu) || !units_.append(tu)) dvander@195: return false; dvander@195: dvander@260: module->setTranslationUnit(tu); dvander@260: dvander@251: return true; dvander@195: } dvander@195: dvander@195: bool dvander@195: CompileContext::propagateFailure() dvander@195: { dvander@195: Vector worklist; dvander@195: for (size_t i = 0; i < units_.length(); i++) { dvander@195: if (units_[i]->failed() && !worklist.append(units_[i])) dvander@195: return false; dvander@195: } dvander@195: dvander@195: while (!worklist.empty()) { dvander@195: TranslationUnit *unit = worklist.popCopy(); dvander@195: for (size_t i = 0; i < unit->dependents().length(); i++) { dvander@195: TranslationUnit *other = unit->dependents().at(i); dvander@195: if (other->failed()) dvander@195: continue; dvander@195: dvander@250: other->setDependencyFailed(); dvander@195: if (!worklist.append(other)) dvander@195: return false; dvander@195: } dvander@195: } dvander@195: dvander@195: return true; dvander@195: } dvander@195: dvander@195: bool dvander@195: CompileContext::compile() dvander@195: { dvander@195: // Phase 1. Parse all modules we're importing. This process may load new dvander@258: // modules off disk and add them to the parse queue. dvander@195: while (!unparsed_.empty()) { dvander@195: TranslationUnit *unit = unparsed_.popCopy(); dvander@195: dvander@251: AutoEnterCompileContext enter(*this, unit); dvander@195: if (!parse(unit)) dvander@250: continue; dvander@195: dvander@250: evaluateImports(unit); dvander@195: } dvander@195: dvander@195: // Phase 2. Create scopes and symbol tables for every lexical region with dvander@195: // a declaration. This phase excludes declarations created via imports. dvander@195: for (size_t i = 0; i < units_.length(); i++) { dvander@195: TranslationUnit *unit = units_[i]; dvander@195: if (unit->failed()) dvander@195: continue; dvander@195: dvander@250: PopulateNamesAndTypes(zone_, *this, unit); dvander@195: } dvander@195: dvander@195: // Phase 3. Complete imports and bind all names. dvander@195: for (size_t i = 0; i < units_.length(); i++) { dvander@195: TranslationUnit *unit = units_[i]; dvander@195: if (unit->failed()) dvander@195: continue; dvander@195: dvander@258: BindGlobals(zone_, *this, unit); dvander@195: dvander@195: if (unit->failed()) dvander@195: continue; dvander@195: dvander@258: BindNamesAndTypes(zone_, *this, unit); dvander@195: } dvander@195: dvander@250: // Phase 4. Now we can finally perform SemA+bytecode compilation. dvander@195: for (size_t i = 0; i < units_.length(); i++) { dvander@195: TranslationUnit *unit = units_[i]; dvander@195: if (unit->failed()) dvander@195: continue; dvander@195: dvander@250: AutoEnterCompileContext enter(*this, unit); dvander@195: SemanticAnalysis sema(zone_, *this, unit); dvander@195: sema.analyze(); dvander@195: } dvander@195: dvander@250: // Phase 5. Run modules. dvander@195: for (size_t i = 0; i < units_.length(); i++) { dvander@195: TranslationUnit *unit = units_[i]; dvander@258: dvander@258: // Mark the unit as no longer being translated. dvander@258: unit->module()->setTranslationUnit(NULL); dvander@258: dvander@195: if (unit->failed()) dvander@250: break; dvander@195: dvander@195: InvokeArgs args; dvander@195: if (!zone_->stack().pushModuleFrame(unit->module_, args)) dvander@195: assert(false); dvander@195: dvander@195: ke::LocalSlot slot; dvander@195: if (!Interpret(zone_, NULL, &slot)) dvander@195: assert(false); dvander@195: } dvander@195: dvander@250: if (Ke::IMessageReporter *reporter = zone_->errorReporter()) { dvander@250: for (size_t i = 0; i < units_.length(); i++) { dvander@250: TranslationUnit *unit = units_[i]; dvander@250: dvander@250: // We don't explicitly report errors for dependency failures or OOM dvander@250: // (OOM is reported separately (:TODO:) and dep failures are redundant. dvander@250: if (!unit->errors()->length()) dvander@250: continue; dvander@237: dvander@251: reporter->beginCompilationErrors(unit->module()->path()->chars()); dvander@250: for (size_t j = 0; j < unit->errors()->length(); j++) { dvander@250: const CompileError &error = unit->errors()->at(j); dvander@250: reporter->reportCompilationError(error.message, error.line, error.col); dvander@250: } dvander@250: reporter->endCompilationErrors(); dvander@250: } dvander@250: } dvander@250: dvander@250: return !units_[0]->failed(); dvander@250: } dvander@250: dvander@250: void dvander@250: CompileContext::enter(TranslationUnit *unit) dvander@250: { dvander@250: assert(!active_); dvander@250: active_ = unit; dvander@250: } dvander@250: dvander@250: void dvander@250: CompileContext::leave() dvander@250: { dvander@250: assert(active_); dvander@250: active_ = NULL; dvander@195: } dvander@195: dvander@195: void dvander@195: CompileContext::reportErrorVa(const SourcePosition &pos, Message msg, va_list ap) dvander@195: { dvander@250: const char *report = BuildErrorMessage(Messages[msg].format, ap); dvander@250: if (!report) { dvander@250: active_->setOutOfMemory(); dvander@250: return; dvander@250: } dvander@195: dvander@250: active_->appendError(report, pos.line, pos.col); dvander@195: } dvander@195: dvander@195: void dvander@195: CompileContext::reportError(const SourcePosition &pos, Message msg, ...) dvander@195: { dvander@195: va_list ap; dvander@195: va_start(ap, msg); dvander@195: reportErrorVa(pos, msg, ap); dvander@195: va_end(ap); dvander@195: } dvander@250: dvander@251: void dvander@251: TranslationUnit::destroy() dvander@250: { dvander@250: for (size_t i = 0; i < errors_.length(); i++) dvander@250: FreeErrorMessage(errors_[i].message); dvander@250: }