From 6a3693ffd6dcc1702742d23943cecb4686c7aa4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20=27Bubu=27=20Busi?= Date: Tue, 10 Jul 2018 09:19:09 +0200 Subject: [PATCH] feat(build): Also support python3 --- CMakeLists.txt | 5 +- ast/ast.py | 7 + ast/c_impl_py3.py | 61 +++++++++ ast/c_py3.py | 100 +++++++++++++++ ast/c_visitor_impl_py3.py | 39 ++++++ ast/cxx_impl_py3.py | 61 +++++++++ ast/cxx_json_visitor_header_py3.py | 42 ++++++ ast/cxx_json_visitor_impl_py3.py | 80 ++++++++++++ ast/cxx_py3.py | 197 +++++++++++++++++++++++++++++ ast/cxx_visitor_py3.py | 64 ++++++++++ ast/js_py3.py | 65 ++++++++++ 11 files changed, 717 insertions(+), 4 deletions(-) create mode 100644 ast/c_impl_py3.py create mode 100644 ast/c_py3.py create mode 100644 ast/c_visitor_impl_py3.py create mode 100644 ast/cxx_impl_py3.py create mode 100644 ast/cxx_json_visitor_header_py3.py create mode 100644 ast/cxx_json_visitor_impl_py3.py create mode 100644 ast/cxx_py3.py create mode 100644 ast/cxx_visitor_py3.py create mode 100644 ast/js_py3.py diff --git a/CMakeLists.txt b/CMakeLists.txt index c4c8b3e..d07a05d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,10 +7,7 @@ INCLUDE(version) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11") -FIND_PACKAGE(PythonInterp 2 REQUIRED) -IF (NOT PYTHON_VERSION_MAJOR EQUAL 2) - MESSAGE(FATAL_ERROR "Python 2 is required.") -ENDIF() +FIND_PACKAGE(PythonInterp 2) FIND_PROGRAM(CTYPESGEN_FOUND ctypesgen.py) diff --git a/ast/ast.py b/ast/ast.py index dbe539c..99ce1d9 100644 --- a/ast/ast.py +++ b/ast/ast.py @@ -54,6 +54,13 @@ def print_ast(lang_module, input_file): if __name__ == '__main__': import sys lang = sys.argv[1] + + try: + if sys.version_info >= (3,0): + lang = "%s_py3" % lang + except: + pass + filename = sys.argv[2] lang_module = load_lang(lang) diff --git a/ast/c_impl_py3.py b/ast/c_impl_py3.py new file mode 100644 index 0000000..aa17765 --- /dev/null +++ b/ast/c_impl_py3.py @@ -0,0 +1,61 @@ +# Copyright (c) 2015-present, Facebook, Inc. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from c_py3 import field_prototype, return_type, struct_name +from casing import title +from license import C_LICENSE_COMMENT + +class Printer(object): + '''Printer for the implementation of the pure C interface to the AST. + ''' + + def __init__(self): + self._current_type = None + + def start_file(self): + print(C_LICENSE_COMMENT + '''/** @generated */ + +#include "GraphQLAst.h" +#include "../Ast.h" + +using namespace facebook::graphql::ast; // NOLINT +''') + + def end_file(self): + pass + + def start_type(self, name): + self._current_type = name + + def field(self, type, name, nullable, plural): + print(field_prototype(self._current_type, type, name, nullable, plural) + ' {') + print(' const auto *realNode = reinterpret_cast(node);' % self._current_type) + title_name = title(name) + call_get = 'realNode->get%s()' % title_name + if plural: + if nullable: + print(' return %s ? %s->size() : 0;' % (call_get, call_get)) + else: + print(' return %s.size();' % call_get) + else: + if type in ['string', 'OperationKind', 'boolean']: + print(' return %s;' % call_get) + else: + fmt = ' return reinterpret_cast(%s%s);' + print(fmt % (struct_name(type), '' if nullable else '&', call_get)) + + print('}') + + def end_type(self, name): + pass + + def start_union(self, name): + pass + + def union_option(self, option): + pass + + def end_union(self, name): + pass diff --git a/ast/c_py3.py b/ast/c_py3.py new file mode 100644 index 0000000..bbcb653 --- /dev/null +++ b/ast/c_py3.py @@ -0,0 +1,100 @@ +# Copyright (c) 2015-present, Facebook, Inc. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from casing import snake + +from license import C_LICENSE_COMMENT + +def struct_name(type): + return 'GraphQLAst' + type + + +def return_type(type): + if type == 'OperationKind' or type == 'string': + return 'const char *' + + if type == 'boolean': + return 'int' + + return 'const struct %s *' % struct_name(type) + + +def field_prototype(owning_type, type, name, nullable, plural): + st_name = struct_name(owning_type) + if plural: + return 'int %s_get_%s_size(const struct %s *node)' % ( + st_name, snake(name), st_name) + else: + ret_type = return_type(type) + return '%s %s_get_%s(const struct %s *node)' % ( + ret_type, st_name, snake(name), st_name) + + +class Printer(object): + '''Printer for the pure C interface to the AST. + + Merely a set of wrappers around the C++ interface; makes it possible + to use the AST from C code and simplifies the task of writing + bindings for other langugages. + + The mapping is as follows: + + - For each concrete type, you get an opaque C struct type, + accessible only by pointer. + + - For each singular field of a concrete type, you get an accessor + function, returning said field in the obvious way. + + - For each plural field of a concrete type, you get an accessor + function telling you its size. For access to elements of a plural + field, you can use the visitor API. + + - For each union type, you get nothing specific (REVIEW), but you + can use the visitor API to work around this entirely. + + ''' + + def __init__(self): + self._current_type = None + + def start_file(self): + print(C_LICENSE_COMMENT + '''/** @generated */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +''') + + def end_file(self): + print(''' + +#ifdef __cplusplus +} +#endif +''') + + def start_type(self, name): + # Forward declarations for AST nodes. + st_name = struct_name(name) + print('struct ' + st_name + ';') + self._current_type = name + + def field(self, type, name, nullable, plural): + print(field_prototype(self._current_type, type, name, nullable, plural) + ';') + + def end_type(self, name): + print() + + def start_union(self, name): + print('struct ' + struct_name(name) + ';') + + def union_option(self, option): + pass + + def end_union(self, name): + print() diff --git a/ast/c_visitor_impl_py3.py b/ast/c_visitor_impl_py3.py new file mode 100644 index 0000000..bbf84f0 --- /dev/null +++ b/ast/c_visitor_impl_py3.py @@ -0,0 +1,39 @@ +# Copyright (c) 2015-present, Facebook, Inc. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from casing import snake +from license import C_LICENSE_COMMENT + +class Printer(object): + '''Printer for a simple list of types to be visited by the C visitor. + ''' + + def __init__(self): + self._types = [] + + def start_file(self): + print(C_LICENSE_COMMENT + '/** @generated */') + print('#define FOR_EACH_CONCRETE_TYPE(MACRO) \\') + + def start_type(self, name): + self._types.append(name) + + def field(self, type, name, nullable, plural): + pass + + def end_type(self, name): + pass + + def end_file(self): + print(' \\\n'.join('MACRO(%s, %s)' % (name, snake(name)) for name in self._types)) + + def start_union(self, name): + pass + + def union_option(self, option): + pass + + def end_union(self, name): + pass diff --git a/ast/cxx_impl_py3.py b/ast/cxx_impl_py3.py new file mode 100644 index 0000000..299ed74 --- /dev/null +++ b/ast/cxx_impl_py3.py @@ -0,0 +1,61 @@ +# Copyright (c) 2015-present, Facebook, Inc. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from license import C_LICENSE_COMMENT + +class Printer(object): + def __init__(self): + pass + + def start_file(self): + print(C_LICENSE_COMMENT + '''/** @generated */ + +#include "Ast.h" +#include "AstVisitor.h" + +namespace facebook { +namespace graphql { +namespace ast { +''') + + def end_file(self): + print('} // namespace ast') + print('} // namespace graphql') + print('} // namespace facebook') + + def start_type(self, name): + print('''void %s::accept(visitor::AstVisitor *visitor) const { + if (visitor->visit%s(*this)) { +''' % (name, name)) + + def field(self, type, name, nullable, plural): + if type in ['OperationKind', 'string', 'boolean']: + return + + if plural: + accept = '{ for (const auto &x : *%s_) { x->accept(visitor); } }' % name + if nullable: + accept = 'if (%s_) %s' % (name, accept) + print(' ' + accept) + else: + accept = '%s_->accept(visitor);' % name + if nullable: + accept = 'if (%s_) { %s }' % (name, accept) + print(' ' + accept) + + def end_type(self, name): + print(''' } + visitor->endVisit%s(*this); +} +''' % name) + + def start_union(self, name): + pass + + def union_option(self, option): + pass + + def end_union(self, name): + pass diff --git a/ast/cxx_json_visitor_header_py3.py b/ast/cxx_json_visitor_header_py3.py new file mode 100644 index 0000000..8ce7349 --- /dev/null +++ b/ast/cxx_json_visitor_header_py3.py @@ -0,0 +1,42 @@ +# Copyright (c) 2016-present, Facebook, Inc. +# All rights reserved. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from casing import title +from license import C_LICENSE_COMMENT + +class Printer(object): + def __init__(self): + self._anyFieldIsANode = False + + def start_file(self): + print(C_LICENSE_COMMENT + '/** @generated */') + + def end_file(self): + pass + + def start_type(self, name): + self._anyFieldIsANode = False + + def end_type(self, name): + titleName = title(name) + if self._anyFieldIsANode: + print('bool visit%s(const %s &node) override;' % (titleName, titleName)) + print('void endVisit%s(const %s &node) override;' % (titleName, titleName)) + print() + + def field(self, type, name, nullable, plural): + if (not self._anyFieldIsANode and + type not in ('OperationKind', 'string', 'boolean')): + self._anyFieldIsANode = True + + def start_union(self, name): + pass + + def union_option(self, option): + pass + + def end_union(self, name): + pass diff --git a/ast/cxx_json_visitor_impl_py3.py b/ast/cxx_json_visitor_impl_py3.py new file mode 100644 index 0000000..c96efdf --- /dev/null +++ b/ast/cxx_json_visitor_impl_py3.py @@ -0,0 +1,80 @@ +# Copyright (c) 2016-present, Facebook, Inc. +# All rights reserved. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from casing import title +from license import C_LICENSE_COMMENT + +class Printer(object): + def __init__(self): + self._fields = [] + + def start_file(self): + print(C_LICENSE_COMMENT + '/** @generated */') + + def end_file(self): + pass + + def start_type(self, name): + self._fields = [] + + def field(self, type, name, nullable, plural): + if type == 'OperationKind': + type = 'string' + self._fields.append((type, name, nullable, plural)) + + def end_type(self, name): + titleName = title(name) + anyFieldIsANode = any(type not in ('string, boolean') + for (type, _, _ ,_) in self._fields) + if anyFieldIsANode: + print('''bool JsonVisitor::visit%s(const %s &node) { + visitNode(); + return true; +} +''' % (titleName, titleName)) + print('''void JsonVisitor::endVisit%(tn)s(const %(tn)s &node) { + NodeFieldPrinter fields(*this, "%(tn)s", node);''' % {'tn': titleName}) + + for (type, fieldName, nullable, plural) in self._fields: + funcName = None + if type == 'string': + assert not plural, 'plural string fields not supported yet' + funcName = 'printSingularPrimitiveField' + elif type == 'boolean': + assert not plural, 'plural boolean fields not supported yet' + funcName = 'printSingularBooleanField' + elif not nullable and not plural: + # Special case: singular object fields don't need the value passed. + print(' fields.printSingularObjectField("%s");' % fieldName) + continue + else: + nullable_str = 'Nullable' if nullable else '' + plural_str = 'Plural' if plural else 'SingularObject' + funcName = 'print%s%sField' % (nullable_str, plural_str) + + assert funcName is not None + print(' fields.%s("%s", node.get%s());' % ( + funcName, fieldName, title(fieldName))) + + if anyFieldIsANode: + print(''' + endVisitNode(fields.finishPrinting()); +} +''') + else: + print(''' + printed_.back().emplace_back(fields.finishPrinting()); +} +''') + + def start_union(self, name): + pass + + def union_option(self, option): + pass + + def end_union(self, name): + pass diff --git a/ast/cxx_py3.py b/ast/cxx_py3.py new file mode 100644 index 0000000..cd868be --- /dev/null +++ b/ast/cxx_py3.py @@ -0,0 +1,197 @@ +# Copyright (c) 2015-present, Facebook, Inc. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import io as StringIO + +from casing import title +from license import C_LICENSE_COMMENT + +class Printer(object): + def __init__(self): + self._type_name = None + # Map concrete type to base class + self._bases = {} + # HACK: Defer everything we print so that forward declarations for + # all classes come first. Avoids having to do 2 passes over the + # input file. + self._deferredOutput = StringIO.StringIO() + + self._fields = [] + + def start_file(self): + print(C_LICENSE_COMMENT + '''/** @generated */ +#pragma once + +#include "AstNode.h" + +#include +#include +#include +#include + +namespace facebook { +namespace graphql { +namespace ast { + +// The parser uses strdup to move from yytext to the heap, so we need +// to use free instead of delete. +struct CDeleter { + void operator()(const char *p) const { free((void *)p); } +}; +''') + + def end_file(self): + print() + print(self._deferredOutput.getvalue()) + print('}') + print('}') + print('}') + + def _base_class(self, type): + return self._bases.get(type, 'Node') + + def start_type(self, name): + self._type_name = name + base = self._base_class(name) + # non-deferred! + print('class %s;' % name) + print('class %s : public %s {' % (name, base), file=self._deferredOutput) + self._fields = [] + + def field(self, type, name, nullable, plural): + if type == 'OperationKind': + type = 'string' + self._fields.append((type, name, nullable, plural)) + + def end_type(self, name): + self._print_fields() + print(' public:', file=self._deferredOutput) + self._print_constructor() + print(file=self._deferredOutput) + self._print_destructor_prototype() + print(file=self._deferredOutput) + self._print_noncopyable() + print(file=self._deferredOutput) + self._print_getters() + print(' void accept(visitor::AstVisitor *visitor) const override;', file=self._deferredOutput) + print('};', file=self._deferredOutput) + print(file=self._deferredOutput) + print(file=self._deferredOutput) + self._type_name = None + self._fields = [] + + def _storage_type(self, type): + if type == 'string': + return 'std::unique_ptr' + elif type == 'boolean': + return 'bool' + else: + return 'std::unique_ptr<%s>' % type + + def _print_fields(self): + for (type, name, nullable, plural) in self._fields: + storage_type = self._storage_type(type) + if plural: + storage_type = 'std::unique_ptr>' % storage_type + print(' %s %s_;' % (storage_type, name), file=self._deferredOutput) + + def _ctor_singular_type(self, type): + if type == 'string': + return 'const char *' + elif type == 'boolean': + return 'bool' + else: + return '%s *' % type + + def _ctor_plural_type(self, type): + return 'std::vector<%s> *' % self._storage_type(type) + + def _print_constructor(self): + print(' explicit %s(' % self._type_name, file=self._deferredOutput) + print(' const yy::location &location%s' % (',' if self._fields else ''), file=self._deferredOutput) + def ctor_arg(type, name, plural): + if plural: + ctor_type = self._ctor_plural_type(type) + else: + ctor_type = self._ctor_singular_type(type) + return ' %s %s' % (ctor_type, name) + print(',\n'.join(ctor_arg(type, name, plural) + for (type, name, nullable, plural) in self._fields), file=self._deferredOutput) + print(' )', file=self._deferredOutput) + def ctor_init(type, name, plural): + # Strings are const char *, just pass. + # Vectors are passed by pointer and we take ownership. + # Node types are passed in by pointer and we take ownership. + value = name + return ' %s_(%s)' % (name, value) + print(' : %s(location)%s' % (self._base_class(self._type_name), ',' if self._fields else ''), file=self._deferredOutput) + print(',\n'.join(ctor_init(type, name, plural) + for (type, name, nullable, plural) + in self._fields), file=self._deferredOutput) + print(' {}', file=self._deferredOutput) + + def _getter_type(self, type, nullable, plural): + if plural and nullable: + return 'const std::vector<%s>*' % self._storage_type(type) + elif plural: + return 'const std::vector<%s>&' % self._storage_type(type) + + if type == 'string': + assert not nullable + return 'const char *' + elif type == 'boolean': + assert not nullable + return 'bool' + elif nullable: + return 'const %s*' % type + else: + return 'const %s&' % type + + def _getter_value_to_return(self, raw_value, type, nullable, plural): + if plural and nullable: + return raw_value + '.get()' + elif plural: + return '*%s' % raw_value + elif type == 'boolean': + return raw_value + elif nullable or type == 'string': + return '%s.get()' % raw_value + else: + return '*%s' % raw_value + + def _print_getters(self): + for (type, name, nullable, plural) in self._fields: + print(' %s get%s() const' % ( + self._getter_type(type, nullable, plural), + title(name)), file=self._deferredOutput) + print(' { return %s; }' % ( + self._getter_value_to_return(name + '_', type, nullable, plural)), file=self._deferredOutput) + print(file=self._deferredOutput) + + def _print_destructor_prototype(self): + print(' ~%s() {}' % self._type_name, file=self._deferredOutput) + + def _print_noncopyable(self): + print(' %s(const %s&) = delete;' % ( + self._type_name, self._type_name), file=self._deferredOutput) + print(' %s& operator=(const %s&) = delete;' % ( + self._type_name, self._type_name), file=self._deferredOutput) + + def start_union(self, name): + self._type_name = name + # non-deferred! + print('class %s;' % name) + print('class %s : public Node {' % name, file=self._deferredOutput) + print(' public:', file=self._deferredOutput) + self._print_constructor() + print('};', file=self._deferredOutput) + print(file=self._deferredOutput) + + def union_option(self, type): + assert type not in self._bases, '%s cannot appear in more than one union!' % type + self._bases[type] = self._type_name + + def end_union(self, name): + pass diff --git a/ast/cxx_visitor_py3.py b/ast/cxx_visitor_py3.py new file mode 100644 index 0000000..ac15cae --- /dev/null +++ b/ast/cxx_visitor_py3.py @@ -0,0 +1,64 @@ +# Copyright (c) 2015-present, Facebook, Inc. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from casing import camel, title +from license import C_LICENSE_COMMENT + +class Printer(object): + def __init__(self): + pass + + def start_file(self): + print(C_LICENSE_COMMENT + '''/** @generated */ + +#pragma once + +#include "Ast.h" + +namespace facebook { +namespace graphql { +namespace ast { +namespace visitor { + +class AstVisitor { +public: + virtual ~AstVisitor() {} +''') + + def end_file(self): + print('};') # end AstVisitor + print() + print('}') + print('}') + print('}') + print('}') + + def start_type(self, name): + titleName = title(name) + camelName = camel(titleName) + print(' virtual bool visit%s(const %s &%s) { return true; }' % ( + titleName, + titleName, + camelName)) + print(' virtual void endVisit%s(const %s &%s) { }' % ( + titleName, + titleName, + camelName)) + print() + + def end_type(self, name): + pass + + def field(self, type, name, nullable, plural): + pass + + def start_union(self, name): + pass + + def union_option(self, option): + pass + + def end_union(self, name): + pass diff --git a/ast/js_py3.py b/ast/js_py3.py new file mode 100644 index 0000000..68aee6e --- /dev/null +++ b/ast/js_py3.py @@ -0,0 +1,65 @@ +# Copyright (c) 2015-present, Facebook, Inc. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + + +class Printer(object): + def __init__(self): + pass + + def start_file(self): + print('''/* @flow */ +/* @generated */ +/* jshint ignore:start */ + +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +type Node = { + kind: string; + start?: ?number; + end?: ?number; +}; + +type OperationKind = 'query' | 'mutation' | 'subscription';''') + + def end_file(self): + pass + + def start_type(self, name): + print() + print('type %s = Node & {' % name) + kind = name + if kind == 'GenericType': + kind = 'Type' + print(' kind: \'%s\';' % kind) + + def end_type(self, name): + print('}') + + def _js_type(self, type, plural): + if plural: + type = 'Array<%s>' % type + return type + + def field(self, type, name, nullable, plural): + nullable_char = '?' if nullable else '' + js_type = self._js_type(type, plural) + print(' %(name)s%(nullable_char)s: %(nullable_char)s%(js_type)s;' % locals()) + + def start_union(self, name): + print(('type %s = ' % name), end=' ') + self._current_options = [] + + def union_option(self, type): + self._current_options.append(type) + + def end_union(self, name): + print('\n | '.join(self._current_options)) + print() + self._current_options = None