Move scriptc to script_api/include.

Part 1 of the directory re-organization.  We're renaming the "api" directory
to "script_api" directory to distinguish between our control api (java or c++)
and our script api.

We're also moving the scriptc directory under that newly renamed directory,
and change its name to the more appropriate "include".

Test: scriptc/generate.sh
Test: compiled ImageProcessing_jb

Change-Id: I00c3bbf5728b652d1541ebe4123717f6ab639f09
diff --git a/script_api/Specification.cpp b/script_api/Specification.cpp
new file mode 100644
index 0000000..c103dc1
--- /dev/null
+++ b/script_api/Specification.cpp
@@ -0,0 +1,965 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <cctype>
+#include <cstdlib>
+#include <fstream>
+#include <functional>
+#include <iostream>
+#include <memory>
+#include <sstream>
+#include <strings.h>
+
+#include "Generator.h"
+#include "Scanner.h"
+#include "Specification.h"
+#include "Utilities.h"
+
+using namespace std;
+
+// API level when RenderScript was added.
+const unsigned int MIN_API_LEVEL = 9;
+
+const NumericalType TYPES[] = {
+            {"f16", "FLOAT_16", "half", "short", FLOATING_POINT, 11, 5},
+            {"f32", "FLOAT_32", "float", "float", FLOATING_POINT, 24, 8},
+            {"f64", "FLOAT_64", "double", "double", FLOATING_POINT, 53, 11},
+            {"i8", "SIGNED_8", "char", "byte", SIGNED_INTEGER, 7, 0},
+            {"u8", "UNSIGNED_8", "uchar", "byte", UNSIGNED_INTEGER, 8, 0},
+            {"i16", "SIGNED_16", "short", "short", SIGNED_INTEGER, 15, 0},
+            {"u16", "UNSIGNED_16", "ushort", "short", UNSIGNED_INTEGER, 16, 0},
+            {"i32", "SIGNED_32", "int", "int", SIGNED_INTEGER, 31, 0},
+            {"u32", "UNSIGNED_32", "uint", "int", UNSIGNED_INTEGER, 32, 0},
+            {"i64", "SIGNED_64", "long", "long", SIGNED_INTEGER, 63, 0},
+            {"u64", "UNSIGNED_64", "ulong", "long", UNSIGNED_INTEGER, 64, 0},
+};
+
+const int NUM_TYPES = sizeof(TYPES) / sizeof(TYPES[0]);
+
+static const char kTagUnreleased[] = "UNRELEASED";
+
+// Patterns that get substituted with C type or RS Data type names in function
+// names, arguments, return types, and inlines.
+static const string kCTypePatterns[] = {"#1", "#2", "#3", "#4"};
+static const string kRSTypePatterns[] = {"#RST_1", "#RST_2", "#RST_3", "#RST_4"};
+
+// The singleton of the collected information of all the spec files.
+SystemSpecification systemSpecification;
+
+// Returns the index in TYPES for the provided cType
+static int findCType(const string& cType) {
+    for (int i = 0; i < NUM_TYPES; i++) {
+        if (cType == TYPES[i].cType) {
+            return i;
+        }
+    }
+    return -1;
+}
+
+/* Converts a string like "u8, u16" to a vector of "ushort", "uint".
+ * For non-numerical types, we don't need to convert the abbreviation.
+ */
+static vector<string> convertToTypeVector(const string& input) {
+    // First convert the string to an array of strings.
+    vector<string> entries;
+    stringstream stream(input);
+    string entry;
+    while (getline(stream, entry, ',')) {
+        trimSpaces(&entry);
+        entries.push_back(entry);
+    }
+
+    /* Second, we look for present numerical types. We do it this way
+     * so the order of numerical types is always the same, no matter
+     * how specified in the spec file.
+     */
+    vector<string> result;
+    for (auto t : TYPES) {
+        for (auto i = entries.begin(); i != entries.end(); ++i) {
+            if (*i == t.specType) {
+                result.push_back(t.cType);
+                entries.erase(i);
+                break;
+            }
+        }
+    }
+
+    // Add the remaining; they are not numerical types.
+    for (auto s : entries) {
+        result.push_back(s);
+    }
+
+    return result;
+}
+
+// Returns true if each entry in typeVector is an RS numerical type
+static bool isRSTValid(const vector<string> &typeVector) {
+    for (auto type: typeVector) {
+        if (findCType(type) == -1)
+            return false;
+    }
+    return true;
+}
+
+void getVectorSizeAndBaseType(const string& type, string& vectorSize, string& baseType) {
+    vectorSize = "1";
+    baseType = type;
+
+    /* If it's a vector type, we need to split the base type from the size.
+     * We know that's it's a vector type if the last character is a digit and
+     * the rest is an actual base type.   We used to only verify the first part,
+     * which created a problem with rs_matrix2x2.
+     */
+    const int last = type.size() - 1;
+    const char lastChar = type[last];
+    if (lastChar >= '0' && lastChar <= '9') {
+        const string trimmed = type.substr(0, last);
+        int i = findCType(trimmed);
+        if (i >= 0) {
+            baseType = trimmed;
+            vectorSize = lastChar;
+        }
+    }
+}
+
+void ParameterDefinition::parseParameterDefinition(const string& type, const string& name,
+                                                   const string& testOption, int lineNumber,
+                                                   bool isReturn, Scanner* scanner) {
+    rsType = type;
+    specName = name;
+
+    // Determine if this is an output.
+    isOutParameter = isReturn || charRemoved('*', &rsType);
+
+    getVectorSizeAndBaseType(rsType, mVectorSize, rsBaseType);
+    typeIndex = findCType(rsBaseType);
+
+    if (mVectorSize == "3") {
+        vectorWidth = "4";
+    } else {
+        vectorWidth = mVectorSize;
+    }
+
+    /* Create variable names to be used in the java and .rs files.  Because x and
+     * y are reserved in .rs files, we prefix variable names with "in" or "out".
+     */
+    if (isOutParameter) {
+        variableName = "out";
+        if (!specName.empty()) {
+            variableName += capitalize(specName);
+        } else if (!isReturn) {
+            scanner->error(lineNumber) << "Should have a name.\n";
+        }
+        doubleVariableName = variableName + "Double";
+    } else {
+        variableName = "in";
+        if (specName.empty()) {
+            scanner->error(lineNumber) << "Should have a name.\n";
+        }
+        variableName += capitalize(specName);
+        doubleVariableName = variableName + "Double";
+    }
+    rsAllocName = "gAlloc" + capitalize(variableName);
+    javaAllocName = variableName;
+    javaArrayName = "array" + capitalize(javaAllocName);
+
+    // Process the option.
+    undefinedIfOutIsNan = false;
+    compatibleTypeIndex = -1;
+    if (!testOption.empty()) {
+        if (testOption.compare(0, 6, "range(") == 0) {
+            size_t pComma = testOption.find(',');
+            size_t pParen = testOption.find(')');
+            if (pComma == string::npos || pParen == string::npos) {
+                scanner->error(lineNumber) << "Incorrect range " << testOption << "\n";
+            } else {
+                minValue = testOption.substr(6, pComma - 6);
+                maxValue = testOption.substr(pComma + 1, pParen - pComma - 1);
+            }
+        } else if (testOption.compare(0, 6, "above(") == 0) {
+            size_t pParen = testOption.find(')');
+            if (pParen == string::npos) {
+                scanner->error(lineNumber) << "Incorrect testOption " << testOption << "\n";
+            } else {
+                smallerParameter = testOption.substr(6, pParen - 6);
+            }
+        } else if (testOption.compare(0, 11, "compatible(") == 0) {
+            size_t pParen = testOption.find(')');
+            if (pParen == string::npos) {
+                scanner->error(lineNumber) << "Incorrect testOption " << testOption << "\n";
+            } else {
+                compatibleTypeIndex = findCType(testOption.substr(11, pParen - 11));
+            }
+        } else if (testOption.compare(0, 11, "conditional") == 0) {
+            undefinedIfOutIsNan = true;
+        } else {
+            scanner->error(lineNumber) << "Unrecognized testOption " << testOption << "\n";
+        }
+    }
+
+    isFloatType = false;
+    if (typeIndex >= 0) {
+        javaBaseType = TYPES[typeIndex].javaType;
+        specType = TYPES[typeIndex].specType;
+        isFloatType = TYPES[typeIndex].exponentBits > 0;
+    }
+    if (!minValue.empty()) {
+        if (typeIndex < 0 || TYPES[typeIndex].kind != FLOATING_POINT) {
+            scanner->error(lineNumber) << "range(,) is only supported for floating point\n";
+        }
+    }
+}
+
+bool VersionInfo::scan(Scanner* scanner, unsigned int maxApiLevel) {
+    if (scanner->findOptionalTag("version:")) {
+        const string s = scanner->getValue();
+        if (s.compare(0, sizeof(kTagUnreleased), kTagUnreleased) == 0) {
+            // The API is still under development and does not have
+            // an official version number.
+            minVersion = maxVersion = kUnreleasedVersion;
+        } else {
+            sscanf(s.c_str(), "%u %u", &minVersion, &maxVersion);
+            if (minVersion && minVersion < MIN_API_LEVEL) {
+                scanner->error() << "Minimum version must >= 9\n";
+            }
+            if (minVersion == MIN_API_LEVEL) {
+                minVersion = 0;
+            }
+            if (maxVersion && maxVersion < MIN_API_LEVEL) {
+                scanner->error() << "Maximum version must >= 9\n";
+            }
+        }
+    }
+    if (scanner->findOptionalTag("size:")) {
+        sscanf(scanner->getValue().c_str(), "%i", &intSize);
+    }
+
+    if (maxVersion > maxApiLevel) {
+        maxVersion = maxApiLevel;
+    }
+
+    return minVersion == 0 || minVersion <= maxApiLevel;
+}
+
+Definition::Definition(const std::string& name)
+    : mName(name), mDeprecatedApiLevel(0), mHidden(false), mFinalVersion(-1) {
+}
+
+void Definition::updateFinalVersion(const VersionInfo& info) {
+    /* We set it if:
+     * - We have never set mFinalVersion before, or
+     * - The max version is 0, which means we have not expired this API, or
+     * - We have a max that's later than what we currently have.
+     */
+    if (mFinalVersion < 0 || info.maxVersion == 0 ||
+        (mFinalVersion > 0 && info.maxVersion > mFinalVersion)) {
+        mFinalVersion = info.maxVersion;
+    }
+}
+
+void Definition::scanDocumentationTags(Scanner* scanner, bool firstOccurence,
+                                       const SpecFile* specFile) {
+    if (scanner->findOptionalTag("hidden:")) {
+        scanner->checkNoValue();
+        mHidden = true;
+    }
+    if (scanner->findOptionalTag("deprecated:")) {
+        string value = scanner->getValue();
+        size_t pComma = value.find(", ");
+        if (pComma != string::npos) {
+            mDeprecatedMessage = value.substr(pComma + 2);
+            value.erase(pComma);
+        }
+        sscanf(value.c_str(), "%i", &mDeprecatedApiLevel);
+        if (mDeprecatedApiLevel <= 0) {
+            scanner->error() << "deprecated entries should have a level > 0\n";
+        }
+    }
+    if (firstOccurence) {
+        if (scanner->findTag("summary:")) {
+            mSummary = scanner->getValue();
+        }
+        if (scanner->findTag("description:")) {
+            scanner->checkNoValue();
+            while (scanner->findOptionalTag("")) {
+                mDescription.push_back(scanner->getValue());
+            }
+        }
+        mUrl = specFile->getDetailedDocumentationUrl() + "#android_rs:" + mName;
+    } else if (scanner->findOptionalTag("summary:")) {
+        scanner->error() << "Only the first specification should have a summary.\n";
+    }
+}
+
+Constant::~Constant() {
+    for (auto i : mSpecifications) {
+        delete i;
+    }
+}
+
+Type::~Type() {
+    for (auto i : mSpecifications) {
+        delete i;
+    }
+}
+
+Function::Function(const string& name) : Definition(name) {
+    mCapitalizedName = capitalize(mName);
+}
+
+Function::~Function() {
+    for (auto i : mSpecifications) {
+        delete i;
+    }
+}
+
+bool Function::someParametersAreDocumented() const {
+    for (auto p : mParameters) {
+        if (!p->documentation.empty()) {
+            return true;
+        }
+    }
+    return false;
+}
+
+void Function::addParameter(ParameterEntry* entry, Scanner* scanner) {
+    for (auto i : mParameters) {
+        if (i->name == entry->name) {
+            // It's a duplicate.
+            if (!entry->documentation.empty()) {
+                scanner->error(entry->lineNumber)
+                            << "Only the first occurence of an arg should have the "
+                               "documentation.\n";
+            }
+            return;
+        }
+    }
+    mParameters.push_back(entry);
+}
+
+void Function::addReturn(ParameterEntry* entry, Scanner* scanner) {
+    if (entry->documentation.empty()) {
+        return;
+    }
+    if (!mReturnDocumentation.empty()) {
+        scanner->error() << "ret: should be documented only for the first variant\n";
+    }
+    mReturnDocumentation = entry->documentation;
+}
+
+void ConstantSpecification::scanConstantSpecification(Scanner* scanner, SpecFile* specFile,
+                                                      unsigned int maxApiLevel) {
+    string name = scanner->getValue();
+    VersionInfo info;
+    if (!info.scan(scanner, maxApiLevel)) {
+        cout << "Skipping some " << name << " definitions.\n";
+        scanner->skipUntilTag("end:");
+        return;
+    }
+
+    bool created = false;
+    Constant* constant = systemSpecification.findOrCreateConstant(name, &created);
+    ConstantSpecification* spec = new ConstantSpecification(constant);
+    constant->addSpecification(spec);
+    constant->updateFinalVersion(info);
+    specFile->addConstantSpecification(spec, created);
+    spec->mVersionInfo = info;
+
+    if (scanner->findTag("value:")) {
+        spec->mValue = scanner->getValue();
+    }
+    constant->scanDocumentationTags(scanner, created, specFile);
+
+    scanner->findTag("end:");
+}
+
+void TypeSpecification::scanTypeSpecification(Scanner* scanner, SpecFile* specFile,
+                                              unsigned int maxApiLevel) {
+    string name = scanner->getValue();
+    VersionInfo info;
+    if (!info.scan(scanner, maxApiLevel)) {
+        cout << "Skipping some " << name << " definitions.\n";
+        scanner->skipUntilTag("end:");
+        return;
+    }
+
+    bool created = false;
+    Type* type = systemSpecification.findOrCreateType(name, &created);
+    TypeSpecification* spec = new TypeSpecification(type);
+    type->addSpecification(spec);
+    type->updateFinalVersion(info);
+    specFile->addTypeSpecification(spec, created);
+    spec->mVersionInfo = info;
+
+    if (scanner->findOptionalTag("simple:")) {
+        spec->mKind = SIMPLE;
+        spec->mSimpleType = scanner->getValue();
+    }
+    if (scanner->findOptionalTag("rs_object:")) {
+        spec->mKind = RS_OBJECT;
+    }
+    if (scanner->findOptionalTag("struct:")) {
+        spec->mKind = STRUCT;
+        spec->mStructName = scanner->getValue();
+        while (scanner->findOptionalTag("field:")) {
+            string s = scanner->getValue();
+            string comment;
+            scanner->parseDocumentation(&s, &comment);
+            spec->mFields.push_back(s);
+            spec->mFieldComments.push_back(comment);
+        }
+    }
+    if (scanner->findOptionalTag("enum:")) {
+        spec->mKind = ENUM;
+        spec->mEnumName = scanner->getValue();
+        while (scanner->findOptionalTag("value:")) {
+            string s = scanner->getValue();
+            string comment;
+            scanner->parseDocumentation(&s, &comment);
+            spec->mValues.push_back(s);
+            spec->mValueComments.push_back(comment);
+        }
+    }
+    if (scanner->findOptionalTag("attrib:")) {
+        spec->mAttribute = scanner->getValue();
+    }
+    type->scanDocumentationTags(scanner, created, specFile);
+
+    scanner->findTag("end:");
+}
+
+FunctionSpecification::~FunctionSpecification() {
+    for (auto i : mParameters) {
+        delete i;
+    }
+    delete mReturn;
+    for (auto i : mPermutations) {
+        delete i;
+    }
+}
+
+string FunctionSpecification::expandRSTypeInString(const string &s,
+                                                   const string &pattern,
+                                                   const string &cTypeStr) const {
+    // Find index of numerical type corresponding to cTypeStr.  The case where
+    // pattern is found in s but cTypeStr is not a numerical type is checked in
+    // checkRSTPatternValidity.
+    int typeIdx = findCType(cTypeStr);
+    if (typeIdx == -1) {
+        return s;
+    }
+    // If index exists, perform replacement.
+    return stringReplace(s, pattern, TYPES[typeIdx].rsDataType);
+}
+
+string FunctionSpecification::expandString(string s,
+                                           int replacementIndexes[MAX_REPLACEABLES]) const {
+
+
+    for (unsigned idx = 0; idx < mReplaceables.size(); idx ++) {
+        string toString = mReplaceables[idx][replacementIndexes[idx]];
+
+        // replace #RST_i patterns with RS datatype corresponding to toString
+        s = expandRSTypeInString(s, kRSTypePatterns[idx], toString);
+
+        // replace #i patterns with C type from mReplaceables
+        s = stringReplace(s, kCTypePatterns[idx], toString);
+    }
+
+    return s;
+}
+
+void FunctionSpecification::expandStringVector(const vector<string>& in,
+                                               int replacementIndexes[MAX_REPLACEABLES],
+                                               vector<string>* out) const {
+    out->clear();
+    for (vector<string>::const_iterator iter = in.begin(); iter != in.end(); iter++) {
+        out->push_back(expandString(*iter, replacementIndexes));
+    }
+}
+
+void FunctionSpecification::createPermutations(Function* function, Scanner* scanner) {
+    int start[MAX_REPLACEABLES];
+    int end[MAX_REPLACEABLES];
+    for (int i = 0; i < MAX_REPLACEABLES; i++) {
+        if (i < (int)mReplaceables.size()) {
+            start[i] = 0;
+            end[i] = mReplaceables[i].size();
+        } else {
+            start[i] = -1;
+            end[i] = 0;
+        }
+    }
+    int replacementIndexes[MAX_REPLACEABLES];
+    // TODO: These loops assume that MAX_REPLACEABLES is 4.
+    for (replacementIndexes[3] = start[3]; replacementIndexes[3] < end[3];
+         replacementIndexes[3]++) {
+        for (replacementIndexes[2] = start[2]; replacementIndexes[2] < end[2];
+             replacementIndexes[2]++) {
+            for (replacementIndexes[1] = start[1]; replacementIndexes[1] < end[1];
+                 replacementIndexes[1]++) {
+                for (replacementIndexes[0] = start[0]; replacementIndexes[0] < end[0];
+                     replacementIndexes[0]++) {
+                    auto p = new FunctionPermutation(function, this, replacementIndexes, scanner);
+                    mPermutations.push_back(p);
+                }
+            }
+        }
+    }
+}
+
+string FunctionSpecification::getName(int replacementIndexes[MAX_REPLACEABLES]) const {
+    return expandString(mUnexpandedName, replacementIndexes);
+}
+
+void FunctionSpecification::getReturn(int replacementIndexes[MAX_REPLACEABLES],
+                                      std::string* retType, int* lineNumber) const {
+    *retType = expandString(mReturn->type, replacementIndexes);
+    *lineNumber = mReturn->lineNumber;
+}
+
+void FunctionSpecification::getParam(size_t index, int replacementIndexes[MAX_REPLACEABLES],
+                                     std::string* type, std::string* name, std::string* testOption,
+                                     int* lineNumber) const {
+    ParameterEntry* p = mParameters[index];
+    *type = expandString(p->type, replacementIndexes);
+    *name = p->name;
+    *testOption = expandString(p->testOption, replacementIndexes);
+    *lineNumber = p->lineNumber;
+}
+
+void FunctionSpecification::getInlines(int replacementIndexes[MAX_REPLACEABLES],
+                                       std::vector<std::string>* inlines) const {
+    expandStringVector(mInline, replacementIndexes, inlines);
+}
+
+void FunctionSpecification::parseTest(Scanner* scanner) {
+    const string value = scanner->getValue();
+    if (value == "scalar" || value == "vector" || value == "noverify" || value == "custom" ||
+        value == "none") {
+        mTest = value;
+    } else if (value.compare(0, 7, "limited") == 0) {
+        mTest = "limited";
+        if (value.compare(7, 1, "(") == 0) {
+            size_t pParen = value.find(')');
+            if (pParen == string::npos) {
+                scanner->error() << "Incorrect test: \"" << value << "\"\n";
+            } else {
+                mPrecisionLimit = value.substr(8, pParen - 8);
+            }
+        }
+    } else {
+        scanner->error() << "Unrecognized test option: \"" << value << "\"\n";
+    }
+}
+
+bool FunctionSpecification::hasTests(unsigned int versionOfTestFiles) const {
+    if (mVersionInfo.maxVersion != 0 && mVersionInfo.maxVersion < versionOfTestFiles) {
+        return false;
+    }
+    if (mTest == "none") {
+        return false;
+    }
+    return true;
+}
+
+void FunctionSpecification::checkRSTPatternValidity(const string &inlineStr,  bool allow,
+                                                    Scanner *scanner) {
+    for (int i = 0; i < MAX_REPLACEABLES; i ++) {
+        bool patternFound = inlineStr.find(kRSTypePatterns[i]) != string::npos;
+
+        if (patternFound) {
+            if (!allow) {
+                scanner->error() << "RST_i pattern not allowed here\n";
+            }
+            else if (mIsRSTAllowed[i] == false) {
+                scanner->error() << "Found pattern \"" << kRSTypePatterns[i]
+                    << "\" in spec.  But some entry in the corresponding"
+                    << " parameter list cannot be translated to an RS type\n";
+            }
+        }
+    }
+}
+
+void FunctionSpecification::scanFunctionSpecification(Scanner* scanner, SpecFile* specFile,
+                                                      unsigned int maxApiLevel) {
+    // Some functions like convert have # part of the name.  Truncate at that point.
+    const string& unexpandedName = scanner->getValue();
+    string name = unexpandedName;
+    size_t p = name.find('#');
+    if (p != string::npos) {
+        if (p > 0 && name[p - 1] == '_') {
+            p--;
+        }
+        name.erase(p);
+    }
+    VersionInfo info;
+    if (!info.scan(scanner, maxApiLevel)) {
+        cout << "Skipping some " << name << " definitions.\n";
+        scanner->skipUntilTag("end:");
+        return;
+    }
+
+    bool created = false;
+    Function* function = systemSpecification.findOrCreateFunction(name, &created);
+    FunctionSpecification* spec = new FunctionSpecification(function);
+    function->addSpecification(spec);
+    function->updateFinalVersion(info);
+    specFile->addFunctionSpecification(spec, created);
+
+    spec->mUnexpandedName = unexpandedName;
+    spec->mTest = "scalar";  // default
+    spec->mVersionInfo = info;
+
+    if (scanner->findOptionalTag("internal:")) {
+        spec->mInternal = (scanner->getValue() == "true");
+    }
+    if (scanner->findOptionalTag("intrinsic:")) {
+        spec->mIntrinsic = (scanner->getValue() == "true");
+    }
+    if (scanner->findOptionalTag("attrib:")) {
+        spec->mAttribute = scanner->getValue();
+    }
+    if (scanner->findOptionalTag("w:")) {
+        vector<string> t;
+        if (scanner->getValue().find("1") != string::npos) {
+            t.push_back("");
+        }
+        if (scanner->getValue().find("2") != string::npos) {
+            t.push_back("2");
+        }
+        if (scanner->getValue().find("3") != string::npos) {
+            t.push_back("3");
+        }
+        if (scanner->getValue().find("4") != string::npos) {
+            t.push_back("4");
+        }
+        spec->mReplaceables.push_back(t);
+        // RST_i pattern not applicable for width.
+        spec->mIsRSTAllowed.push_back(false);
+    }
+
+    while (scanner->findOptionalTag("t:")) {
+        spec->mReplaceables.push_back(convertToTypeVector(scanner->getValue()));
+        spec->mIsRSTAllowed.push_back(isRSTValid(spec->mReplaceables.back()));
+    }
+
+    // Disallow RST_* pattern in function name
+    // FIXME the line number for this error would be wrong
+    spec->checkRSTPatternValidity(unexpandedName, false, scanner);
+
+    if (scanner->findTag("ret:")) {
+        ParameterEntry* p = scanner->parseArgString(true);
+        function->addReturn(p, scanner);
+        spec->mReturn = p;
+
+        // Disallow RST_* pattern in return type
+        spec->checkRSTPatternValidity(p->type, false, scanner);
+    }
+    while (scanner->findOptionalTag("arg:")) {
+        ParameterEntry* p = scanner->parseArgString(false);
+        function->addParameter(p, scanner);
+        spec->mParameters.push_back(p);
+
+        // Disallow RST_* pattern in parameter type or testOption
+        spec->checkRSTPatternValidity(p->type, false, scanner);
+        spec->checkRSTPatternValidity(p->testOption, false, scanner);
+    }
+
+    function->scanDocumentationTags(scanner, created, specFile);
+
+    if (scanner->findOptionalTag("inline:")) {
+        scanner->checkNoValue();
+        while (scanner->findOptionalTag("")) {
+            spec->mInline.push_back(scanner->getValue());
+
+            // Allow RST_* pattern in inline definitions
+            spec->checkRSTPatternValidity(spec->mInline.back(), true, scanner);
+        }
+    }
+    if (scanner->findOptionalTag("test:")) {
+        spec->parseTest(scanner);
+    }
+
+    scanner->findTag("end:");
+
+    spec->createPermutations(function, scanner);
+}
+
+FunctionPermutation::FunctionPermutation(Function* func, FunctionSpecification* spec,
+                                         int replacementIndexes[MAX_REPLACEABLES], Scanner* scanner)
+    : mReturn(nullptr), mInputCount(0), mOutputCount(0) {
+    // We expand the strings now to make capitalization easier.  The previous code preserved
+    // the #n
+    // markers just before emitting, which made capitalization difficult.
+    mName = spec->getName(replacementIndexes);
+    mNameTrunk = func->getName();
+    mTest = spec->getTest();
+    mPrecisionLimit = spec->getPrecisionLimit();
+    spec->getInlines(replacementIndexes, &mInline);
+
+    mHasFloatAnswers = false;
+    for (size_t i = 0; i < spec->getNumberOfParams(); i++) {
+        string type, name, testOption;
+        int lineNumber = 0;
+        spec->getParam(i, replacementIndexes, &type, &name, &testOption, &lineNumber);
+        ParameterDefinition* def = new ParameterDefinition();
+        def->parseParameterDefinition(type, name, testOption, lineNumber, false, scanner);
+        if (def->isOutParameter) {
+            mOutputCount++;
+        } else {
+            mInputCount++;
+        }
+
+        if (def->typeIndex < 0 && mTest != "none") {
+            scanner->error(lineNumber)
+                        << "Could not find " << def->rsBaseType
+                        << " while generating automated tests.  Use test: none if not needed.\n";
+        }
+        if (def->isOutParameter && def->isFloatType) {
+            mHasFloatAnswers = true;
+        }
+        mParams.push_back(def);
+    }
+
+    string retType;
+    int lineNumber = 0;
+    spec->getReturn(replacementIndexes, &retType, &lineNumber);
+    if (!retType.empty()) {
+        mReturn = new ParameterDefinition();
+        mReturn->parseParameterDefinition(retType, "", "", lineNumber, true, scanner);
+        if (mReturn->isFloatType) {
+            mHasFloatAnswers = true;
+        }
+        mOutputCount++;
+    }
+}
+
+FunctionPermutation::~FunctionPermutation() {
+    for (auto i : mParams) {
+        delete i;
+    }
+    delete mReturn;
+}
+
+SpecFile::SpecFile(const string& specFileName) : mSpecFileName(specFileName) {
+    string core = mSpecFileName;
+    // Remove .spec
+    size_t l = core.length();
+    const char SPEC[] = ".spec";
+    const int SPEC_SIZE = sizeof(SPEC) - 1;
+    const int start = l - SPEC_SIZE;
+    if (start >= 0 && core.compare(start, SPEC_SIZE, SPEC) == 0) {
+        core.erase(start);
+    }
+
+    // The header file name should have the same base but with a ".rsh" extension.
+    mHeaderFileName = core + ".rsh";
+    mDetailedDocumentationUrl = core + ".html";
+}
+
+void SpecFile::addConstantSpecification(ConstantSpecification* spec, bool hasDocumentation) {
+    mConstantSpecificationsList.push_back(spec);
+    if (hasDocumentation) {
+        Constant* constant = spec->getConstant();
+        mDocumentedConstants.insert(pair<string, Constant*>(constant->getName(), constant));
+    }
+}
+
+void SpecFile::addTypeSpecification(TypeSpecification* spec, bool hasDocumentation) {
+    mTypeSpecificationsList.push_back(spec);
+    if (hasDocumentation) {
+        Type* type = spec->getType();
+        mDocumentedTypes.insert(pair<string, Type*>(type->getName(), type));
+    }
+}
+
+void SpecFile::addFunctionSpecification(FunctionSpecification* spec, bool hasDocumentation) {
+    mFunctionSpecificationsList.push_back(spec);
+    if (hasDocumentation) {
+        Function* function = spec->getFunction();
+        mDocumentedFunctions.insert(pair<string, Function*>(function->getName(), function));
+    }
+}
+
+// Read the specification, adding the definitions to the global functions map.
+bool SpecFile::readSpecFile(unsigned int maxApiLevel) {
+    FILE* specFile = fopen(mSpecFileName.c_str(), "rt");
+    if (!specFile) {
+        cerr << "Error opening input file: " << mSpecFileName << "\n";
+        return false;
+    }
+
+    Scanner scanner(mSpecFileName, specFile);
+
+    // Scan the header that should start the file.
+    scanner.skipBlankEntries();
+    if (scanner.findTag("header:")) {
+        if (scanner.findTag("summary:")) {
+            mBriefDescription = scanner.getValue();
+        }
+        if (scanner.findTag("description:")) {
+            scanner.checkNoValue();
+            while (scanner.findOptionalTag("")) {
+                mFullDescription.push_back(scanner.getValue());
+            }
+        }
+        if (scanner.findOptionalTag("include:")) {
+            scanner.checkNoValue();
+            while (scanner.findOptionalTag("")) {
+                mVerbatimInclude.push_back(scanner.getValue());
+            }
+        }
+        scanner.findTag("end:");
+    }
+
+    while (1) {
+        scanner.skipBlankEntries();
+        if (scanner.atEnd()) {
+            break;
+        }
+        const string tag = scanner.getNextTag();
+        if (tag == "function:") {
+            FunctionSpecification::scanFunctionSpecification(&scanner, this, maxApiLevel);
+        } else if (tag == "type:") {
+            TypeSpecification::scanTypeSpecification(&scanner, this, maxApiLevel);
+        } else if (tag == "constant:") {
+            ConstantSpecification::scanConstantSpecification(&scanner, this, maxApiLevel);
+        } else {
+            scanner.error() << "Expected function:, type:, or constant:.  Found: " << tag << "\n";
+            return false;
+        }
+    }
+
+    fclose(specFile);
+    return scanner.getErrorCount() == 0;
+}
+
+SystemSpecification::~SystemSpecification() {
+    for (auto i : mConstants) {
+        delete i.second;
+    }
+    for (auto i : mTypes) {
+        delete i.second;
+    }
+    for (auto i : mFunctions) {
+        delete i.second;
+    }
+    for (auto i : mSpecFiles) {
+        delete i;
+    }
+}
+
+// Returns the named entry in the map.  Creates it if it's not there.
+template <class T>
+T* findOrCreate(const string& name, map<string, T*>* map, bool* created) {
+    auto iter = map->find(name);
+    if (iter != map->end()) {
+        *created = false;
+        return iter->second;
+    }
+    *created = true;
+    T* f = new T(name);
+    map->insert(pair<string, T*>(name, f));
+    return f;
+}
+
+Constant* SystemSpecification::findOrCreateConstant(const string& name, bool* created) {
+    return findOrCreate<Constant>(name, &mConstants, created);
+}
+
+Type* SystemSpecification::findOrCreateType(const string& name, bool* created) {
+    return findOrCreate<Type>(name, &mTypes, created);
+}
+
+Function* SystemSpecification::findOrCreateFunction(const string& name, bool* created) {
+    return findOrCreate<Function>(name, &mFunctions, created);
+}
+
+bool SystemSpecification::readSpecFile(const string& fileName, unsigned int maxApiLevel) {
+    SpecFile* spec = new SpecFile(fileName);
+    if (!spec->readSpecFile(maxApiLevel)) {
+        cerr << fileName << ": Failed to parse.\n";
+        return false;
+    }
+    mSpecFiles.push_back(spec);
+    return true;
+}
+
+
+static void updateMaxApiLevel(const VersionInfo& info, unsigned int* maxApiLevel) {
+    if (info.minVersion == VersionInfo::kUnreleasedVersion) {
+        // Ignore development API level in consideration of max API level.
+        return;
+    }
+    *maxApiLevel = max(*maxApiLevel, max(info.minVersion, info.maxVersion));
+}
+
+unsigned int SystemSpecification::getMaximumApiLevel() {
+    unsigned int maxApiLevel = 0;
+    for (auto i : mConstants) {
+        for (auto j: i.second->getSpecifications()) {
+            updateMaxApiLevel(j->getVersionInfo(), &maxApiLevel);
+        }
+    }
+    for (auto i : mTypes) {
+        for (auto j: i.second->getSpecifications()) {
+            updateMaxApiLevel(j->getVersionInfo(), &maxApiLevel);
+        }
+    }
+    for (auto i : mFunctions) {
+        for (auto j: i.second->getSpecifications()) {
+            updateMaxApiLevel(j->getVersionInfo(), &maxApiLevel);
+        }
+    }
+    return maxApiLevel;
+}
+
+bool SystemSpecification::generateFiles(bool forVerification, unsigned int maxApiLevel) const {
+    bool success = generateHeaderFiles("include") &&
+                   generateDocumentation("docs", forVerification) &&
+                   generateTestFiles("test", maxApiLevel) &&
+                   generateStubsWhiteList("slangtest", maxApiLevel);
+    if (success) {
+        cout << "Successfully processed " << mTypes.size() << " types, " << mConstants.size()
+             << " constants, and " << mFunctions.size() << " functions.\n";
+    }
+    return success;
+}
+
+string SystemSpecification::getHtmlAnchor(const string& name) const {
+    Definition* d = nullptr;
+    auto c = mConstants.find(name);
+    if (c != mConstants.end()) {
+        d = c->second;
+    } else {
+        auto t = mTypes.find(name);
+        if (t != mTypes.end()) {
+            d = t->second;
+        } else {
+            auto f = mFunctions.find(name);
+            if (f != mFunctions.end()) {
+                d = f->second;
+            } else {
+                return string();
+            }
+        }
+    }
+    ostringstream stream;
+    stream << "<a href='" << d->getUrl() << "'>" << name << "</a>";
+    return stream.str();
+}