#!/usr/bin/env python
# Copyright 2012 10gen 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 .
#
# 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.
"""Generate error_codes.{h,cpp} from error_codes.err.
Format of error_codes.err:
error_code("symbol1", code1)
error_code("symbol2", code2)
...
error_class("class1", ["symbol1", "symbol2, ..."])
Usage:
python generate_error_codes.py [options]
"""
usage_msg = "usage: %prog /path/to/error_codes.err [options]"
from optparse import OptionParser
import sys
def main(argv):
generator = argv[1]
error_codes, error_classes = parse_error_definitions_from_file(argv[2])
check_for_conflicts(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")
sys.exit(1)
def usage(message=None):
sys.stderr.write(__doc__)
die(message)
def parse_error_definitions_from_file(errors_filename):
errors_file = open(errors_filename, 'r')
errors_code = compile(errors_file.read(), errors_filename, 'exec')
error_codes = []
error_classes = []
eval(errors_code, dict(error_code=lambda *args: error_codes.append(args),
error_class=lambda *args: error_classes.append(args)))
error_codes.sort(key=lambda x: x[1])
return error_codes, error_classes
def check_for_conflicts(error_codes, error_classes):
failed = has_duplicate_error_codes(error_codes)
if has_duplicate_error_classes(error_classes):
failed = True
if has_missing_error_codes(error_codes, error_classes):
failed = True
if failed:
die()
def has_duplicate_error_codes(error_codes):
sorted_by_name = sorted(error_codes, key=lambda x: x[0])
sorted_by_code = sorted(error_codes, key=lambda x: x[1])
failed = False
prev_name, prev_code = sorted_by_name[0]
for name, code in sorted_by_name[1:]:
if name == prev_name:
sys.stdout.write('Duplicate name %s with codes %s and %s\n' % (name, code, prev_code))
failed = True
prev_name, prev_code = name, code
prev_name, prev_code = sorted_by_code[0]
for name, code in sorted_by_code[1:]:
if code == prev_code:
sys.stdout.write('Duplicate code %s with names %s and %s\n' % (code, name, prev_name))
failed = True
prev_name, prev_code = name, code
return failed
def has_duplicate_error_classes(error_classes):
names = sorted(ec[0] for ec in error_classes)
failed = False
prev_name = names[0]
for name in names[1:]:
if prev_name == name:
sys.stdout.write('Duplicate error class name %s\n' % name)
failed = True
prev_name = name
return failed
def has_missing_error_codes(error_codes, error_classes):
code_names = set(ec[0] for ec in error_codes)
failed = False
for class_name, class_code_names in error_classes:
for name in class_code_names:
if name not in code_names:
sys.stdout.write('Undeclared error code %s in class %s\n' % (name, class_name))
failed = True
return failed
class base_generator(object):
def __init__(self, error_codes, error_classes):
self.error_codes = error_codes
self.error_classes = 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
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(self):
self.generate_source()
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 .
*
* 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.
*
* 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 .
*
* 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.
*/
#pragma once
#include
#include
#include
#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:
// Explicitly 32-bits wide so that non-symbolic values,
// like uassert codes, are valid.
enum Error : std::int32_t {
%(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) {
return static_cast(code);
}
%(error_code_class_predicate_declarations)s;
};
std::ostream& operator<<(std::ostream& stream, ErrorCodes::Error code);
} // namespace mongo
'''
source_template = '''// AUTO-GENERATED FILE DO NOT EDIT
// See src/mongo/base/generate_error_codes.py
/* Copyright 2014 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 .
*
* 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.
*/
#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" << int(err);
}
}
ErrorCodes::Error ErrorCodes::fromString(StringData name) {
%(string_to_symbol_cases)s;
return UnknownError;
}
std::ostream& operator<<(std::ostream& stream, ErrorCodes::Error code) {
return stream << ErrorCodes::errorString(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) {
switch (err) {
%(cases)s
return true;
default:
return false;
}
}
'''
if __name__ == '__main__':
main(sys.argv)