blob: 26c30c07187075ebc5eb0c22eba9f36c5de01917 [file]
# Copyright 2023 Google LLC
#
# 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
#
# https://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.
"""Build rules to create C++ code from an Antlr4 grammar."""
load("@rules_cc//cc:cc_library.bzl", "cc_library")
def antlr4_cc_lexer(
name,
src,
namespaces = None,
deps = None):
"""Generates the C++ source corresponding to an antlr4 lexer definition.
Args:
name: The name of the package to use for the cc_library.
src: The antlr4 g4 file containing the lexer rules.
namespaces: The namespace used by the generated files. Uses an array to
support nested namespaces. Defaults to [name].
deps: Dependencies for the generated code.
"""
namespaces = namespaces or [name]
deps = deps or []
if not src.endswith(".g4"):
fail("Grammar must end with .g4", "src")
file_prefix = _label_basename(src[:-3])
base_file_prefix = _strip_end(file_prefix, "Lexer")
out_files = [
"%sLexer.h" % base_file_prefix,
"%sLexer.cpp" % base_file_prefix,
]
command = ";\n".join([
# Use the first namespace, we'll add the others afterwards.
_make_tool_invocation_command(namespaces[0]),
_make_namespace_adjustment_command(namespaces, out_files),
_make_bazel_cleanup_command(out_files),
])
native.genrule(
name = name + "_source",
srcs = [src],
outs = out_files,
cmd = command,
heuristic_label_expansion = 0,
tools = [Label("@org_antlr_tool//file")],
)
cc_library(
name = name,
srcs = [f for f in out_files if f.endswith(".cpp")],
hdrs = [f for f in out_files if f.endswith(".h")],
deps = [Label("@org_antlr4_cpp_runtime//:antlr4")] + deps,
copts = [
"-fexceptions",
"-iquote external/org_antlr4_cpp_runtime",
],
features = ["-use_header_modules"], # Incompatible with -fexceptions.
)
def antlr4_cc_parser(
name,
src,
namespaces = None,
listener = True,
visitor = False,
deps = None):
"""Generates the C++ source corresponding to an antlr4 parser definition.
Args:
name: The name of the package to use for the cc_library.
src: The antlr4 g4 file containing the parser rules.
namespaces: The namespace used by the generated files. Uses an array to
support nested namespaces. Defaults to [name].
listener: Whether or not to include listener generated files.
visitor: Whether or not to include visitor generated files.
deps: Dependencies for the generated code.
"""
suffixes = ()
if listener:
suffixes += (
"%sBaseListener.cpp",
"%sListener.cpp",
"%sBaseListener.h",
"%sListener.h",
)
if visitor:
suffixes += (
"%sBaseVisitor.cpp",
"%sVisitor.cpp",
"%sBaseVisitor.h",
"%sVisitor.h",
)
namespaces = namespaces or [name]
deps = deps or []
if not src.endswith(".g4"):
fail("Grammar must end with .g4", "src")
file_prefix = _label_basename(src[:-3])
base_file_prefix = _strip_end(file_prefix, "Parser")
out_files = [
"%sParser.h" % base_file_prefix,
"%sParser.cpp" % base_file_prefix,
] + _make_outs(file_prefix, suffixes)
command = ";\n".join([
# Use the first namespace, we'll add the others afterward.
_make_tool_invocation_command(namespaces[0], listener, visitor),
_make_namespace_adjustment_command(namespaces, out_files),
_make_bazel_cleanup_command(out_files),
])
native.genrule(
name = name + "_source",
srcs = [src],
outs = out_files,
cmd = command,
heuristic_label_expansion = 0,
tools = [Label("@org_antlr_tool//file")],
)
cc_library(
name = name,
srcs = [f for f in out_files if f.endswith(".cpp")],
hdrs = [f for f in out_files if f.endswith(".h")],
deps = [Label("@org_antlr4_cpp_runtime//:antlr4")] + deps,
copts = [
"-fexceptions",
"-Wno-nonnull",
"-iquote external/org_antlr4_cpp_runtime",
],
features = ["-use_header_modules"],
)
def _make_tool_invocation_command(package, listener = False, visitor = False):
return "java -jar $(location @org_antlr_tool//file) " + \
"$(SRCS)" + \
(" -visitor" if visitor else " -no-visitor") + \
(" -listener" if listener else " -no-listener") + \
" -Dlanguage=Cpp" + \
" -package " + package + \
" -o $(@D)" + \
" -Xexact-output-dir"
def _make_bazel_cleanup_command(out_files):
# Clean up imports and usage of the #pragma once directive.
return ";\n".join([
";\n".join([
"sed -i'.bak' '/antlr4-runtime.h/s//antlr4-runtime\\/antlr4-runtime.h/' $(@D)/%s" % filepath,
"grep -q '^#pragma once' $(@D)/%s && echo '\n#endif // %s\n' >> $(@D)/%s" % (filepath, _to_c_macro_name(filepath), filepath),
"sed -i'.bak' '/#pragma once/s//#ifndef %s\\n#define %s/' $(@D)/%s" % (_to_c_macro_name(filepath), _to_c_macro_name(filepath), filepath),
])
for filepath in out_files
])
def _make_namespace_adjustment_command(namespaces, out_files):
if len(namespaces) == 1:
return "true"
commands = []
extra_header_namespaces = "\\\n".join(["namespace %s {" % namespace for namespace in namespaces[1:]])
for filepath in out_files:
if filepath.endswith(".h"):
commands.append("sed -i'.bak' '/namespace %s {/ a\\\n%s' $(@D)/%s" % (namespaces[0], extra_header_namespaces, filepath))
for namespace in namespaces[1:]:
commands.append("sed -i'.bak' '/} \\/\\/ namespace %s/i\\\n} \\/\\/ namespace %s\n' $(@D)/%s" % (namespaces[0], namespace, filepath))
else:
commands.append("sed -i'.bak' 's/using namespace %s;/using namespace %s;/' $(@D)/%s" % (namespaces[0], "::".join(namespaces), filepath))
return ";\n".join(commands)
def _make_outs(file_prefix, suffixes):
return [file_suffix % file_prefix for file_suffix in suffixes]
def _strip_end(text, suffix):
if not text.endswith(suffix):
return text
return text[:len(text) - len(suffix)]
def _to_c_macro_name(filename):
# Convert the filenames to a format suitable for C preprocessor definitions.
char_list = [filename[i].upper() for i in range(len(filename))]
return "ANTLR4_GEN_" + "".join(
[a if (("A" <= a) and (a <= "Z")) else "_" for a in char_list],
)
def _label_basename(label):
"""Returns the basename of a Blaze label."""
return label.split(":")[-1]