SERVER 10160 generate symbolic JS constants for ErrorCodes

Signed-off-by: Adam Midvidy <amidvidy@gmail.com>
This commit is contained in:
Calvin Chan
2015-08-17 11:17:57 -04:00
committed by Adam Midvidy
parent d34f1fe4e5
commit ea25f4c281
5 changed files with 173 additions and 61 deletions

View File

@@ -19,7 +19,7 @@
// from command interface
assert.commandFailedWithCode(t.runCommand("distinct", {"key": {a: 1}}),
14); // ErrorCodes::TypeMismatch
ErrorCodes.TypeMismatch);
//second argument should be a document or error
@@ -29,7 +29,7 @@
// from command interface
assert.commandFailedWithCode(t.runCommand("distinct", {"key": "a", "query": "a"}),
14); // ErrorCodes::TypeMismatch
ErrorCodes.TypeMismatch);

View File

@@ -5,7 +5,7 @@ Import("env")
generateErrorCodes = env.Command(
target=['error_codes.h', 'error_codes.cpp'],
source=['generate_error_codes.py', 'error_codes.err'],
action='$PYTHON $SOURCES $TARGETS')
action=['$PYTHON ${SOURCES[0]} cpp ${SOURCES[1]} --cpp-header=${TARGETS[0]} --cpp-source=${TARGETS[1]}'])
env.Alias('generated-sources', generateErrorCodes)
env.CppUnitTest('base_test',

View File

@@ -36,19 +36,29 @@ error_code("symbol2", code2)
error_class("class1", ["symbol1", "symbol2, ..."])
Usage:
python generate_error_codes.py <path to error_codes.err> <header file path> <source file path>
python generate_error_codes.py <cpp|js> <path to error_codes.err> [options]
"""
usage_msg = "usage: %prog <cpp|js> /path/to/error_codes.err [options]"
from optparse import OptionParser
import sys
def main(argv):
if len(argv) != 4:
usage("Wrong number of arguments.")
error_codes, error_classes = parse_error_definitions_from_file(argv[1])
generator = argv[1]
error_codes, error_classes = parse_error_definitions_from_file(argv[2])
check_for_conflicts(error_codes, error_classes)
generate_header(argv[2], error_codes, error_classes)
generate_source(argv[3], error_codes, error_classes)
if (generator == 'cpp'):
if (len(argv) != 5):
usage('Wrong number of arguments')
cpp_gen = cpp_generator(error_codes, error_classes)
cpp_gen.generate()
elif (generator == 'js'):
if (len(argv) != 4):
usage('Wrong number of arguments')
js_gen = js_generator(error_codes, error_classes)
js_gen.generate()
else:
usage('Must specify which generator(s) to use.')
def die(message=None):
sys.stderr.write(message or "Fatal error\n")
@@ -120,37 +130,147 @@ def has_missing_error_codes(error_codes, error_classes):
failed = True
return failed
def generate_header(filename, error_codes, error_classes):
class base_generator(object):
def __init__(self, error_codes, error_classes):
self.error_codes = error_codes
self.error_classes = error_classes
enum_declarations = ',\n '.join('%s = %s' % ec for ec in error_codes)
predicate_declarations = ';\n '.join(
'static bool is%s(Error err)' % ec[0] for ec in error_classes)
def parseOptions(self, options, usage_msg):
parser = OptionParser(usage=usage_msg)
for (f,d,n,m,h) in options:
parser.add_option(f,dest=d,nargs=n,metavar=m,help=h)
(options, args) = parser.parse_args()
return options
open(filename, 'wb').write(header_template % dict(
error_code_enum_declarations=enum_declarations,
error_code_class_predicate_declarations=predicate_declarations))
def generate_source(filename, error_codes, error_classes):
symbol_to_string_cases = ';\n '.join(
'case %s: return "%s"' % (ec[0], ec[0]) for ec in error_codes)
string_to_symbol_cases = ';\n '.join(
'if (name == "%s") return %s' % (ec[0], ec[0])
for ec in error_codes)
int_to_symbol_cases = ';\n '.join(
'case %s: return %s' % (ec[0], ec[0]) for ec in error_codes)
predicate_definitions = '\n '.join(
generate_error_class_predicate_definition(*ec) for ec in error_classes)
open(filename, 'wb').write(source_template % dict(
symbol_to_string_cases=symbol_to_string_cases,
string_to_symbol_cases=string_to_symbol_cases,
int_to_symbol_cases=int_to_symbol_cases,
error_code_class_predicate_definitions=predicate_definitions))
class js_generator(base_generator):
def __init__(self, error_codes, error_classes):
super(js_generator, self).__init__(error_codes, error_classes)
options = [('--js-source','js_source',1,'DEST_JS_SOURCE','specify dest JS source file to save to')]
options = self.parseOptions(options, usage_msg)
if (options.js_source):
self.js_source = options.js_source
else:
usage('Must specify JS source files')
def generate_error_class_predicate_definition(class_name, code_names):
cases = '\n '.join('case %s:' % c for c in code_names)
return error_class_predicate_template % dict(class_name=class_name, cases=cases)
def generate(self):
self.generate_source()
header_template = '''// AUTO-GENERATED FILE DO NOT EDIT
def generate_source(self):
string_to_int_cases = ',\n '.join(
'%s: %s' % (ec[0], ec[1]) for ec in self.error_codes)
int_to_string_cases = ',\n '.join(
'%s: \'%s\'' % (ec[1], ec[0]) for ec in self.error_codes)
predicate_definitions = '\n\n'.join(
self.generate_error_class_predicate_definition(*ec) for ec in self.error_classes)
open(self.js_source, 'wb').write(self.source_template % dict(
string_to_int_cases=string_to_int_cases,
int_to_string_cases=int_to_string_cases
))
def generate_error_class_predicate_definition(self, class_name, code_names):
cases = '\n '.join('case \'%s\':' % c for c in code_names)
return self.error_class_predicate_template % dict(class_name=class_name, cases=cases)
source_template = '''// AUTO-GENERATED FILE DO NOT EDIT
// See src/mongo/base/generate_error_codes.py
/* Copyright 2015 MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the GNU Affero General Public License in all respects
* for all of the code used other than as permitted herein. If you modify
* file(s) with this exception, you may extend this exception to your
* version of the file(s), but you are not obligated to do so. If you do not
* wish to do so, delete this exception statement from your version. If you
* delete this exception statement from all source files in the program,
* then also delete it in the license file.
*/
var ErrorCodes = {
%(string_to_int_cases)s
};
var ErrorCodeStrings = {
%(int_to_string_cases)s
};
'''
error_class_predicate_template = '''function is%(class_name)s(err) {
if (typeof err === 'string') {
error = err;
} else if (typeof err === 'number') {
error = ErrorCodeStrings[err];
}
switch (error) {
%(cases)s
return true;
default:
return false;
}
}
'''
class cpp_generator(base_generator):
def __init__(self, error_codes, error_classes):
super(cpp_generator, self).__init__(error_codes, error_classes)
options = [('--cpp-header','cpp_header',1,'DEST_CPP_HEADER','specify dest CPP header file to save to'), ('--cpp-source','cpp_source',1,'DEST_CPP_SOURCE','specify dest CPP source file to save to')]
options = self.parseOptions(options, usage_msg)
if (options.cpp_header and options.cpp_source):
self.cpp_header = options.cpp_header
self.cpp_source = options.cpp_source
else:
usage('Must specify CPP header and source files')
def generate(self):
self.generate_header()
self.generate_source()
def generate_header(self):
enum_declarations = ',\n '.join('%s = %s' % ec for ec in self.error_codes)
predicate_declarations = ';\n '.join(
'static bool is%s(Error err)' % ec[0] for ec in self.error_classes)
open(self.cpp_header, 'wb').write(self.header_template % dict(
error_code_enum_declarations=enum_declarations,
error_code_class_predicate_declarations=predicate_declarations))
def generate_source(self):
symbol_to_string_cases = ';\n '.join(
'case %s: return "%s"' % (ec[0], ec[0]) for ec in self.error_codes)
string_to_symbol_cases = ';\n '.join(
'if (name == "%s") return %s' % (ec[0], ec[0])
for ec in self.error_codes)
int_to_symbol_cases = ';\n '.join(
'case %s: return %s' % (ec[0], ec[0]) for ec in self.error_codes)
predicate_definitions = '\n '.join(
self.generate_error_class_predicate_definition(*ec) for ec in self.error_classes)
open(self.cpp_source, 'wb').write(self.source_template % dict(
symbol_to_string_cases=symbol_to_string_cases,
string_to_symbol_cases=string_to_symbol_cases,
int_to_symbol_cases=int_to_symbol_cases,
error_code_class_predicate_definitions=predicate_definitions))
def generate_error_class_predicate_definition(self, class_name, code_names):
cases = '\n '.join('case %s:' % c for c in code_names)
return self.error_class_predicate_template % dict(class_name=class_name, cases=cases)
header_template = '''// AUTO-GENERATED FILE DO NOT EDIT
// See src/mongo/base/generate_error_codes.py
/* Copyright 2014 MongoDB, Inc.
*
@@ -178,52 +298,41 @@ header_template = '''// AUTO-GENERATED FILE DO NOT EDIT
* delete this exception statement from all source files in the program,
* then also delete it in the license file.
*/
#pragma once
#include <string>
#include "mongo/base/string_data.h"
namespace mongo {
/**
* This is a generated class containing a table of error codes and their corresponding error
* strings. The class is derived from the definitions in src/mongo/base/error_codes.err file.
*
* Do not update this file directly. Update src/mongo/base/error_codes.err instead.
*/
class ErrorCodes {
public:
enum Error {
%(error_code_enum_declarations)s,
MaxError
};
static std::string errorString(Error err);
/**
* Parses an Error from its "name". Returns UnknownError if "name" is unrecognized.
*
* NOTE: Also returns UnknownError for the string "UnknownError".
*/
static Error fromString(StringData name);
/**
* Casts an integer "code" to an Error. Unrecognized codes are preserved, meaning
* that the result of a call to fromInt() may not be one of the values in the
* Error enumeration.
*/
static Error fromInt(int code);
%(error_code_class_predicate_declarations)s;
};
} // namespace mongo
'''
source_template = '''// AUTO-GENERATED FILE DO NOT EDIT
source_template = '''// AUTO-GENERATED FILE DO NOT EDIT
// See src/mongo/base/generate_error_codes.py
/* Copyright 2014 MongoDB, Inc.
*
@@ -251,39 +360,30 @@ source_template = '''// AUTO-GENERATED FILE DO NOT EDIT
* delete this exception statement from all source files in the program,
* then also delete it in the license file.
*/
#include "mongo/base/error_codes.h"
#include "mongo/util/mongoutils/str.h"
namespace mongo {
std::string ErrorCodes::errorString(Error err) {
switch (err) {
%(symbol_to_string_cases)s;
default: return mongoutils::str::stream() << "Location" << err;
}
}
ErrorCodes::Error ErrorCodes::fromString(StringData name) {
%(string_to_symbol_cases)s;
return UnknownError;
}
ErrorCodes::Error ErrorCodes::fromInt(int code) {
return static_cast<Error>(code);
}
%(error_code_class_predicate_definitions)s
namespace {
static_assert(sizeof(ErrorCodes::Error) == sizeof(int), "sizeof(ErrorCodes::Error) == sizeof(int)");
} // namespace
} // namespace mongo
'''
error_class_predicate_template = '''bool ErrorCodes::is%(class_name)s(Error err) {
error_class_predicate_template = '''bool ErrorCodes::is%(class_name)s(Error err) {
switch (err) {
%(cases)s
return true;
@@ -292,5 +392,7 @@ error_class_predicate_template = '''bool ErrorCodes::is%(class_name)s(Error err)
}
}
'''
if __name__ == '__main__':
main(sys.argv)

View File

@@ -286,6 +286,7 @@ extern const JSFile utils;
extern const JSFile utils_sh;
extern const JSFile utils_auth;
extern const JSFile bulk_api;
extern const JSFile error_codes;
}
void Scope::execCoreFiles() {
@@ -297,6 +298,7 @@ void Scope::execCoreFiles() {
execSetup(JSFiles::mr);
execSetup(JSFiles::query);
execSetup(JSFiles::bulk_api);
execSetup(JSFiles::error_codes);
execSetup(JSFiles::collection);
execSetup(JSFiles::crud_api);
execSetup(JSFiles::explain_query);

View File

@@ -2,8 +2,14 @@
Import("env")
generateJSErrorCodes = env.Command(
target=['error_codes.js'],
source=['$BUILD_DIR/mongo/base/generate_error_codes.py', '$BUILD_DIR/mongo/base/error_codes.err'],
action=['$PYTHON ${SOURCES[0]} js ${SOURCES[1]} --js-source=${TARGETS[0]}'])
env.Alias('generated-sources', generateJSErrorCodes)
# Files added here need to be added in scripting/engine.cpp and buildscripts/vcxproj.header as well.
env.JSHeader(
js_header = env.JSHeader(
target="mongo.cpp",
source=[
"assert.js",
@@ -13,6 +19,7 @@ env.JSHeader(
"db.js",
"explain_query.js",
"explainable.js",
"error_codes.js",
"mongo.js",
"mr.js",
"query.js",
@@ -21,8 +28,9 @@ env.JSHeader(
"utils.js",
"utils_sh.js",
"utils_auth.js",
],
]
)
env.Depends(js_header, env.Alias('generated-sources'))
# Files added here need to be added in shell/shell_utils.cpp and buildscripts/vcxproj.header as
# well.