├── .mailmap ├── LICENSE ├── Makefile ├── README.adoc_ ├── README.md ├── README.md.in ├── class.lua ├── compile_commands_reader.lua ├── compile_commands_util.lua ├── config.make ├── dev ├── ParseClangLangOptionsDef.lua ├── PchRelevantLangOptions.lua ├── app-prefix.sh.in ├── app-suffix.sh.in ├── app.sh.in ├── createheader.lua ├── cxx_headers.hpp ├── empty.cpp ├── exdecl_macro_def.lua ├── exdecl_struct_def.lua ├── exdecl_struct_property.lua ├── exdecl_surrogate_struct.lua ├── exdecl_system_fingerprint.lua ├── ljclang_extracted_enums.lua.in ├── ljclang_linux_decls.lua.in ├── posix.h ├── posix_decls.lua.in ├── posix_types.lua.in └── sys.h ├── diagnostics_util.lua ├── docker ├── Dockerfile.in ├── Makefile ├── vars.alpine.sed ├── vars.debian.sed ├── vars.ubuntu-bionic.sed └── vars.ubuntu.sed ├── error_util.lua ├── extractdecls.lua ├── extractrange.lua ├── hacks.lua ├── inclusion_graph.lua ├── inotify.lua ├── ljclang.lua ├── ljclang_Index_h.lua ├── ljclang_extracted_enums.lua ├── ljclang_support.c ├── make_docs.lua ├── mgrep.lua ├── mkapp.lua ├── mkdecls.sh ├── parsecmdline_pk.lua ├── posix.lua ├── run_tests.sh ├── symbol_index.lua ├── terminal_colors.lua ├── test_data ├── defines.hpp ├── enums.hpp ├── indexopt.cpp ├── simple.hpp └── virtual.hpp ├── tests.lua ├── text_editor_bindings ├── LICENSE.txt └── ljclang-wcc-flycheck.el ├── util.lua ├── watch_compile_commands.lua ├── wcc-client.sh └── wcc-server.sh /.mailmap: -------------------------------------------------------------------------------- 1 | # A number of commits accidentally used a wrong email address. 2 | Philipp Kutin Philipp Kutin 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright (C) 2013-2020 Philipp Kutin 3 | 4 | (Portions of the documentation copied or adapted from luaclang-parser, Copyright 5 | (C) 2012 Michal Kottman) 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | # From the 'GNU make' manual: 3 | # "recipes will be invoked as if the shell had been passed the '-e' flag: 4 | # the first failing command in a recipe will cause the recipe to fail 5 | # immediately." 6 | .POSIX: 7 | 8 | OS := $(shell uname -s) 9 | THIS_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) 10 | 11 | # User configuration 12 | include config.make 13 | 14 | llvm-config := $(shell which $(LLVM_CONFIG)) 15 | 16 | markdown := $(shell which $(MARKDOWN)) 17 | 18 | ifeq ($(llvm-config),) 19 | $(error "$(LLVM_CONFIG) not found, use LLVM_CONFIG= make") 20 | endif 21 | 22 | full_llvm_version := $(shell $(llvm-config) --version) 23 | llvm_version := $(full_llvm_version:git=) 24 | 25 | ########## PATHS ########## 26 | 27 | ifneq ($(OS),Linux) 28 | $(error "Unsupported OS") 29 | endif 30 | 31 | bindir := $(shell $(llvm-config) --bindir) 32 | incdir := $(shell $(llvm-config) --includedir) 33 | libdir := $(shell $(llvm-config) --libdir) 34 | lib := -L$(libdir) -lclang 35 | 36 | # TODO: error or warn if directory does not exist. Ideally, remove. 37 | llvm_libdir_include := $(libdir)/clang/$(llvm_version)/include 38 | 39 | ########## COMPILER OPTIONS ########## 40 | 41 | common_flags := -I$(incdir) -fPIC -O2 42 | common_flags += -DLJCLANG_LLVM_VERSION='"$(llvm_version)"' 43 | common_flags += -Werror -Wall -Wextra -pedantic 44 | 45 | cflags := -std=c99 $(common_flags) 46 | 47 | # Development convenience, for test_data/*.cpp only: 48 | cxxflags := -std=c++17 $(common_flags) -Wold-style-cast 49 | ifneq ($(findstring clang,$(CXX)),) 50 | cxxflags += -Wno-unused-const-variable 51 | endif 52 | 53 | ########## RULES ########## 54 | 55 | INDEX_H_LUA := ljclang_Index_h.lua 56 | LIBDIR_INCLUDE_LUA := ./llvm_libdir_include.lua 57 | EXTRACTED_ENUMS_LUA := ljclang_extracted_enums.lua 58 | EXTRACTED_ENUMS_LUA_TMP := $(EXTRACTED_ENUMS_LUA).tmp 59 | linux_decls_lua := ljclang_linux_decls.lua 60 | linux_decls_lua_tmp := $(linux_decls_lua).tmp 61 | posix_decls_lua := posix_decls.lua 62 | posix_decls_lua_tmp := $(posix_decls_lua).tmp 63 | posix_types_lua := posix_types.lua 64 | posix_types_lua_tmp := $(posix_types_lua).tmp 65 | 66 | LJCLANG_SUPPORT_SO := libljclang_support.so 67 | SHARED_LIBRARIES := $(LJCLANG_SUPPORT_SO) 68 | 69 | GENERATED_FILES_STAGE_1 := $(INDEX_H_LUA) $(LIBDIR_INCLUDE_LUA) 70 | GENERATED_FILES_STAGE_2 := $(GENERATED_FILES_STAGE_1) $(EXTRACTED_ENUMS_LUA) $(posix_types_lua) 71 | 72 | .PHONY: all app_dependencies apps clean veryclean bootstrap doc test test-loop 73 | .PHONY: install install-dev _install_common 74 | .PHONY: committed-generated extractdecls_deps print-extractdecls-library-path 75 | .PHONY: docker-ljclang-dev-native clean-all-temp 76 | 77 | all: $(SHARED_LIBRARIES) $(GENERATED_FILES_STAGE_2) 78 | 79 | apps := extractdecls.app.lua watch_compile_commands.app.lua 80 | apps: $(apps) 81 | 82 | committed-generated: $(INDEX_H_LUA) $(EXTRACTED_ENUMS_LUA) 83 | 84 | clean: 85 | rm -f $(SHARED_LIBRARIES) $(apps) 86 | 87 | veryclean: clean 88 | rm -f $(GENERATED_FILES_STAGE_2) $(EXTRACTED_ENUMS_LUA_TMP) $(EXTRACTED_ENUMS_LUA).reject \ 89 | $(linux_decls_lua) $(linux_decls_lua_tmp) $(linux_decls_lua).reject \ 90 | $(posix_decls_lua) $(posix_decls_lua_tmp) $(posix_decls_lua).reject 91 | 92 | bootstrap: $(EXTRACTED_ENUMS_LUA) 93 | 94 | # ---------- Docker ---------- 95 | 96 | docker-ljclang-dev-native: 97 | @$(MAKE) --silent -C docker ljclang-dev-native/alpine 98 | 99 | clean-all-temp: 100 | @$(MAKE) -C docker clean-all-temp 101 | 102 | # ---------- Build ---------- 103 | 104 | $(LJCLANG_SUPPORT_SO): ljclang_support.c Makefile 105 | $(CC) $(cflags) -shared $< $(lib) -o $@ 106 | 107 | $(INDEX_H_LUA): ./dev/createheader.lua $(incdir)/clang-c/* 108 | @$(luajit) ./dev/createheader.lua $(incdir)/clang-c > $@ 109 | @printf "* \033[1mGenerated $@ from files in $(incdir)/clang-c \033[0m\n" 110 | 111 | $(LIBDIR_INCLUDE_LUA): Makefile config.make 112 | @echo "return { '$(llvm_libdir_include)' }" > $@ 113 | 114 | EXTRACT_CMD_ENV := LD_LIBRARY_PATH="$(libdir):$(THIS_DIR)" 115 | # For using an in-tree (non-installed) extractdecls.lua or mkdecls.sh 116 | # from outside: 117 | print-extractdecls-library-path: 118 | @echo "$(libdir):$(THIS_DIR)" 119 | 120 | CHECK_EXTRACTED_ENUMS_CMD := $(EXTRACT_CMD_ENV) $(luajit) \ 121 | -e "require('ffi').cdef[[typedef int time_t;]]" \ 122 | -e "require '$(subst .lua,,$(INDEX_H_LUA))'" \ 123 | -e "l=require '$(subst .lua,,$(EXTRACTED_ENUMS_LUA))'" \ 124 | -e "assert(l.CursorKindName[1] ~= nil)" 125 | 126 | # Because we have comments in the executable portion of the rule. 127 | .SILENT: $(EXTRACTED_ENUMS_LUA) 128 | 129 | $(EXTRACTED_ENUMS_LUA): $(SHARED_LIBRARIES) $(GENERATED_FILES_STAGE_1) 130 | $(EXTRACTED_ENUMS_LUA): ./dev/$(EXTRACTED_ENUMS_LUA).in $(incdir)/clang-c/* 131 | # Make loading ljclang.lua not fail. We must not use any "extracted enums" though since 132 | # we are about to generate them. 133 | echo 'return {}' > $(EXTRACTED_ENUMS_LUA) 134 | # Do the extraction. 135 | $(EXTRACT_CMD_ENV) ./mkdecls.sh $< -A -I"${incdir}" "${incdir}/clang-c/Index.h" > $(EXTRACTED_ENUMS_LUA_TMP) 136 | # Check that we can load the generated file in Lua. 137 | mv $(EXTRACTED_ENUMS_LUA_TMP) $@ 138 | ($(CHECK_EXTRACTED_ENUMS_CMD) && \ 139 | printf "* \033[1mGenerated $@\033[0m\n") \ 140 | || (printf "* \033[1;31mError\033[0m generating $@\n" && \ 141 | mv $@ $@.reject && false) 142 | 143 | # Linux-specific functionality exposed to us 144 | 145 | sys_h := ./dev/sys.h 146 | 147 | CHECK_EXTRACTED_INOTIFY_CMD := $(EXTRACT_CMD_ENV) $(luajit) \ 148 | -e "require'ljclang_linux_decls'" 149 | 150 | $(linux_decls_lua): ./dev/ljclang_linux_decls.lua.in $(EXTRACTED_ENUMS_LUA) $(sys_h) Makefile 151 | @$(EXTRACT_CMD_ENV) ./mkdecls.sh $< | sed 's/\/ANONYMOUS/g' > $(linux_decls_lua_tmp) 152 | @mv $(linux_decls_lua_tmp) $@ 153 | @($(CHECK_EXTRACTED_INOTIFY_CMD) && \ 154 | printf "* \033[1mGenerated $@\033[0m\n") \ 155 | || (printf "* \033[1;31mError\033[0m generating $@\n" && \ 156 | mv $@ $@.reject && false) 157 | 158 | # POSIX functionality exposed to us 159 | 160 | $(posix_types_lua): ./dev/posix_types.lua.in $(EXTRACTED_ENUMS_LUA) $(sys_h) Makefile 161 | @$(EXTRACT_CMD_ENV) ./mkdecls.sh $< > $(posix_types_lua_tmp) 162 | @mv $(posix_types_lua_tmp) $@ 163 | @($(EXTRACT_CMD_ENV) $(luajit) -e "require'posix_types'" && \ 164 | printf "* \033[1mGenerated $@\033[0m\n") \ 165 | || (printf "* \033[1;31mError\033[0m generating $@\n" && \ 166 | mv $@ $@.reject && false) 167 | 168 | $(posix_decls_lua): ./dev/posix_decls.lua.in $(EXTRACTED_ENUMS_LUA) $(sys_h) Makefile $(posix_types_lua) 169 | @$(EXTRACT_CMD_ENV) ./mkdecls.sh $< > $(posix_decls_lua_tmp) 170 | @mv $(posix_decls_lua_tmp) $@ 171 | @($(EXTRACT_CMD_ENV) $(luajit) -e "require'posix_decls'" && \ 172 | printf "* \033[1mGenerated $@\033[0m\n") \ 173 | || (printf "* \033[1;31mError\033[0m generating $@\n" && \ 174 | mv $@ $@.reject && false) 175 | 176 | # ---------- Post-build ---------- 177 | 178 | .SILENT: doc 179 | 180 | doc: README.md.in ljclang.lua ./make_docs.lua 181 | $(luajit) ./make_docs.lua $^ > README.md \ 182 | && printf "* \033[1mGenerated README.md\033[0m\n" 183 | ifneq ($(markdown),) 184 | $(markdown) README.md > README.html \ 185 | && printf "* \033[1mGenerated README.html\033[0m\n" 186 | else 187 | echo "* Did not generate README.html: '$(MARKDOWN)' not installed" 188 | endif 189 | 190 | app_dependencies: $(linux_decls_lua) $(posix_decls_lua) 191 | 192 | define do_test = 193 | LLVM_LIBDIR="$(libdir)" $(SHELL) ./run_tests.sh 194 | endef 195 | 196 | test: $(SHARED_LIBRARIES) $(GENERATED_FILES_STAGE_2) $(linux_decls_lua) $(posix_decls_lua) 197 | $(do_test) 198 | 199 | TEST_LOOP_COUNT ?= 10 200 | test-loop: test 201 | @echo "INFO: Repeating for a total of $(TEST_LOOP_COUNT) runs." 202 | @i=1; while test $$i -lt $(TEST_LOOP_COUNT); do i=$$((i+1)); $(do_test); done 203 | 204 | sed_common_commands := s|@LJCLANG_DEV_DIR@|$(THIS_DIR)|g; s|@LLVM_BINDIR@|$(bindir)|g; s|@LLVM_LIBDIR@|$(libdir)|g; 205 | 206 | extractdecls_deps: mkapp.lua $(GENERATED_FILES_STAGE_1) app_dependencies 207 | 208 | extractdecls.app.lua: extractdecls.lua extractdecls_deps 209 | @$(EXTRACT_CMD_ENV) $(luajit) -l mkapp $< -Q > /dev/null && \ 210 | printf "* \033[1mCreated $@\033[0m\n" 211 | 212 | watch_compile_commands.app.lua: watch_compile_commands.lua mkapp.lua $(GENERATED_FILES_STAGE_2) app_dependencies 213 | @$(EXTRACT_CMD_ENV) $(luajit) -l mkapp $< -x > /dev/null && \ 214 | printf "* \033[1mCreated $@\033[0m\n" 215 | 216 | pre := dev/app-prefix.sh.in 217 | post := dev/app-suffix.sh.in 218 | 219 | _install_common: 220 | install $(THIS_DIR)/wcc-server.sh $(BINDIR)/wcc-server 221 | install $(THIS_DIR)/wcc-client.sh $(BINDIR)/wcc-client 222 | 223 | # Notes: 224 | # - the check using grep for 'EOF' is stricter than necessary -- we append 80 '_' chars. 225 | # - the overhead of the generated Bash script (reading the here document line-by-line?) is 226 | # noticable on a Raspberry Pi (approx. 100ms on a Pi 4). 227 | install: $(SHARED_LIBRARIES) $(GENERATED_FILES_STAGE_2) apps _install_common 228 | @if grep -c EOF extractdecls.app.lua > /dev/null; then echo "ERROR: 'EOF' in Lua source!"; false; else true; fi 229 | sed "$(sed_common_commands)" $(pre) | cat - extractdecls.app.lua $(post) > $(BINDIR)/extractdecls 230 | @rm -f extractdecls.app.lua 231 | @chmod +x $(BINDIR)/extractdecls 232 | @if grep -c EOF watch_compile_commands.app.lua > /dev/null; then echo "ERROR: 'EOF' in Lua source!"; false; else true; fi 233 | sed "$(sed_common_commands)" $(pre) | cat - watch_compile_commands.app.lua $(post) > $(BINDIR)/watch_compile_commands 234 | @rm -f watch_compile_commands.app.lua 235 | @chmod +x $(BINDIR)/watch_compile_commands 236 | 237 | install-dev: $(SHARED_LIBRARIES) $(GENERATED_FILES_STAGE_2) app_dependencies _install_common 238 | sed "$(sed_common_commands) s|@APPLICATION@|extractdecls|g" ./dev/app.sh.in > $(BINDIR)/extractdecls 239 | @chmod +x $(BINDIR)/extractdecls 240 | sed "$(sed_common_commands) s|@APPLICATION@|watch_compile_commands|g" ./dev/app.sh.in > $(BINDIR)/watch_compile_commands 241 | @chmod +x $(BINDIR)/watch_compile_commands 242 | 243 | # This target is merely there to create compile_commands.json entries for the test 244 | # source files in case we are invoked with 'bear'. 245 | compile_commands.json: $(patsubst %.cpp,%.o,$(wildcard test_data/*.cpp)) 246 | 247 | test_data/%.o: test_data/%.cpp 248 | $(CXX) -c $(subst -Werror,,$(cxxflags)) $< -o /dev/null 249 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | LJClang -- A LuaJIT-based interface to libclang 3 | =============================================== 4 | 5 | ### Table of Contents 6 | 7 | **[Introduction](#introduction)**\ 8 | **[Requirements](#requirements)**\ 9 | **[Building](#building)**\ 10 | **[Overview](#overview)**\ 11 | **[Example programs](#example-programs)**\ 12 | **[Reference](#reference)**\ 13 | **[License](#license)** 14 | 15 | 16 | Introduction 17 | ------------ 18 | 19 | [LuaJIT]: https://luajit.org/ 20 | [libclang]: https://clang.llvm.org/doxygen/group__CINDEX.html 21 | [luaclang-parser]: https://github.com/mkottman/luaclang-parser 22 | 23 | LJClang is an interface to [libclang] for [LuaJIT], modeled after and mostly 24 | API-compatible with [luaclang-parser] by Michal Kottman. 25 | 26 | 27 | Requirements 28 | ------------ 29 | 30 | * LuaJIT 2.0 or greater 31 | 32 | * LLVM/Clang -- from the Linux distribution or 33 | [here](https://apt.llvm.org/). Development is done using the latest stable 34 | version, but older versions should work mostly fine (except that interfaces 35 | exposed by newer version are not available, of course). 36 | 37 | 38 | Building 39 | -------- 40 | 41 | Invoking `make` builds the required support library `libljclang_support.so`, 42 | converts libclang C headers into a form that can be used by LuaJIT (using a Lua 43 | program that essentially strips text that would not be understood by LuaJIT's 44 | `ffi.cdef`) and finally extracts additional information using LJClang itself. 45 | 46 | The file `config.make` contains some user-facing configuration. 47 | 48 | 49 | Overview 50 | -------- 51 | 52 | LJClang provides a cursor-based, callback-driven API to the abstract syntax 53 | tree (AST) of C/C++ source files. These are the main classes: 54 | 55 | * `Index` -- represents a set of translation units that could be linked together 56 | * `TranslationUnit` -- a source file together with everything included by it 57 | either directly or transitively 58 | * `Cursor` -- points to an element in the AST in a translation unit such as a 59 | `typedef` declaration or a statement 60 | * `Type` -- the type of an element (for example, that of a variable, structure 61 | member, or a function's input argument or return value) 62 | 63 | To make something interesting happen, you usually create a single `Index` 64 | object, parse into it one or more translation units, and define a callback 65 | function to be invoked on each visit of a `Cursor` by libclang. 66 | 67 | 68 | Example programs 69 | ---------------- 70 | 71 | ### `extractdecls.lua` 72 | 73 | [`enum CXCursorKind`]: 74 | https://clang.llvm.org/doxygen/group__CINDEX.html#gaaccc432245b4cd9f2d470913f9ef0013 75 | 76 | The `extractdecls.lua` script accompanied by LJClang can be used to extract 77 | various kinds of C declarations from (usually) headers and print them in 78 | various forms usable as FFI C declarations or descriptive tables with LuaJIT. 79 | 80 | ~~~~~~~~~~ 81 | Usage: 82 | extractdecls.lua [our options...] [-- [Clang command line args ...]] 83 | 84 | (Our options may also come after the file name.) 85 | 86 | Exits with a non-zero code if there were errors or no match, or if filter 87 | patterns (-p) were provided and not all of them produced matches. 88 | 89 | Options: 90 | -e (enums only) 91 | -p [-p ] ... (logically OR'd) 92 | -x [-x ] ... (logically OR'd) 93 | -s 94 | -1 95 | -2 96 | -A (same as if specified as positional arg) 97 | -C: print lines like 98 | static const int membname = 123; (enums/macros only) 99 | -R: reverse mapping, only if one-to-one. Print lines like 100 | [123] = \"membname\"; (enums/macros only) 101 | -m : name of a Lua module to 'require()' which should return a 102 | function taking the LJClang cursor as a first argument and a table of strings collected 103 | from the -a option instances as the second argument. In the context of the call to 104 | 'require()' and the module function, the functions 'check' and 'printf' are available. 105 | The function 'printf' must not be called at module load time. 106 | Incompatible with -1, -2, -C, -R, -f and -w. 107 | -a [-a ] ...: arguments passed to the 108 | as a table. 109 | Can only be used with -m. 110 | -f : user-provided body for formatting function (enums/macros only) 111 | Arguments to that function are named 112 | * 'k' (enum constant / macro name) 113 | * 'v' (its numeric value) 114 | * 'enumName' (the name in 'enum ', or the empty string) 115 | * 'enumIntTypeName' (the name of the underlying integer type of an enum) 116 | * 'enumPrefixLength' (the length of the common prefix of all names; enums only) 117 | Also, the following is provided: 118 | * 'f' as a shorthand for 'string.format' 119 | Must return a formatted line. 120 | Example: 121 | "return f('%s = %s%s,', k, k:find('KEY_') and '65536+' or '', v)" 122 | Incompatible with -C, -R or -f. 123 | -Q: be quiet 124 | -w: extract what? Can be 125 | E+M, EnumConstantDecl (default), MacroDefinition, TypedefDecl, FunctionDecl 126 | 127 | ~~~~~~~~~~ 128 | 129 | In fact, the file `ljclang_cursor_kind.lua` is generated by this program and is 130 | used by LJClang to map values of the enumeration [`enum CXCursorKind`] to their 131 | names. The `bootstrap` target in the `Makefile` extracts the relevant 132 | information using these options: 133 | 134 | ~~~~~~~~~~ 135 | -Q -R -e 'CXCursorKind' -p '^CXCursor_' -s '^CXCursor_' \ 136 | -x '_First' -x '_Last' -x '_GCCAsmStmt' -x '_MacroInstantiation' \ 137 | -1 'CursorKindName = {' -2 '},' 138 | ~~~~~~~~~~ 139 | 140 | 141 | Thus, the enum constant names are filtered to be taken from `enum CXCursorKind`, 142 | beginning with `CXCursor_` (that prefix being stripped) and all "secondary" names 143 | aliasing the one considered the main one are rejected. (For example, 144 | `CXCursor_AsmStmt` and `CXCursor_GCCAsmStmt` have the same value.) This yields 145 | lines like 146 | 147 | ~~~~~~~~~~ 148 | [215] = "AsmStmt"; 149 | ~~~~~~~~~~ 150 | 151 | ### `watch_compile_commands.lua` 152 | 153 | ~~~~~~~~~~ 154 | Usage: 155 | watch_compile_commands.lua [options...] 156 | 157 | In this help text, single quotes ("'") are for exposition purposes only. 158 | They are never to be spelled in actual option arguments. 159 | 160 | Options: 161 | -a: Enable automatic generation and usage of precompiled headers. For each PCH configuration 162 | (state of relevant compiler options) meeting a certain threshold of compile commands that 163 | it is used with, a PCH file is generated that includes all standard library headers. 164 | Note that this will remove errors due to forgetting to include a standard library header. 165 | Only supported for C++11 upwards. 166 | Precompiled headers are stored in '$HOME/.cache/ljclang'. 167 | -c : set number of parallel parser invocations. (Minimum: 1) 168 | 'auto' means use hardware concurrency (the default). 169 | -i : Enable incremental mode. Stop processing further compile commands on the first 170 | diagnostic matching the severity specification. Its syntax one of: 171 | 1. a comma-separated list, (,)* 172 | where each is one of 'note', 'warning', 'error' or 'fatal'. 173 | 2. a single severity suffixed by '+', meaning to select the specified severity 174 | and more serious ones. 175 | As a convenience, the specification can also be '-', meaning 'error+'. 176 | -g [includes|isIncludedBy]: Print inclusion graph as a DOT (of Graphviz) file to stdout and exit. 177 | Argument specifies the relation between graph nodes (which are file names). 178 | -l : edge count limit for the graph produced by -g isIncludedBy. 179 | If exceeded, a placeholder node is placed. 180 | -r [c|s]: report progress after the specified number of 181 | processed compile commands or the given time interval. 182 | Specifying any of 'c0', 'c1' or '0s' effectively prints progress with each compile command. 183 | -s [-] [-s [-] ...]: Select compile command(s) to process. 184 | Selectors are processed in the order they appear on the command line. Each selector can 185 | be prefixed by '-', which means to remove the matching set of compile commands from the 186 | current set. If a removal appears first, the initial set contains all compile commands, 187 | otherwise it is empty. 188 | Each can be one of: 189 | - '@...': by index (see below). 190 | - '{}': by Lua pattern matching the absolute file name in a compile command. 191 | -N: Print all diagnostics. This disables omission of: 192 | - diagnostics that follow a Parse Issue error, and 193 | - diagnostics that were seen in previous compile commands. 194 | -P: Disable color output. 195 | -v: Be verbose. Currently: output compiler invocations for Auto-PCH generation failures. 196 | -x: exit after parsing and displaying diagnostics once. 197 | 198 | If the selector to an option -s starts with '@', it must have one of the following forms, 199 | where the integral starts with a decimal digit distinct from zero: 200 | - '@': single compile command, or 201 | - '@..': range starting with the specified index, or 202 | - '@..': inclusive range. 203 | ~~~~~~~~~~ 204 | 205 | 206 | Reference 207 | --------- 208 | 209 | The module returned by `require("ljclang")` -- called `clang` from here on -- 210 | contains the following: 211 | 212 | #### `index = clang.createIndex([excludeDeclarationsFromPCH [, displayDiagnostics]])` 213 | 214 | [`clang_createIndex`]: 215 | http://clang.llvm.org/doxygen/group__CINDEX.html#ga51eb9b38c18743bf2d824c6230e61f93 216 | 217 | Binding for [`clang_createIndex`]. Will create an `Index` into which you can 218 | parse `TranslationUnit`s. Both input arguments are optional and default to 219 | **false**. 220 | 221 | #### `clang.ChildVisitResult` 222 | 223 | [`enum CXChildVisitResult`]: 224 | https://clang.llvm.org/doxygen/group__CINDEX__CURSOR__TRAVERSAL.html#ga99a9058656e696b622fbefaf5207d715 225 | 226 | An object mapping names to values to be returned 227 | from cursor visitor callbacks. The names are identical with those in [`enum 228 | CXChildVisitResult`] with the "`CXChildVisit_`" prefix removed: `Break`, 229 | `Continue`, `Recurse`. 230 | 231 | #### `visitorHandle = clang.regCursorVisitor(visitorFunc)` 232 | 233 | Registers a child visitor callback function `visitorFunc` with LJClang, 234 | returning a handle which can be passed to `Cursor:children()`. The callback 235 | function receives two input arguments, `(cursor, parent)` -- with the cursors 236 | of the currently visited entity as well as its parent, and must return a value 237 | from the `ChildVisitResult` enumeration to indicate whether or how libclang 238 | should carry on AST visiting. 239 | 240 | CAUTION: The `cursor` passed to the visitor callback is only valid during one 241 | particular callback invocation. If it is to be used after the function has 242 | returned, it **must** be copied using the `Cursor` constructor mentioned below. 243 | 244 | #### `permanentCursor = clang.Cursor(cursor)` 245 | 246 | Creates a permanent cursor from one received by the visitor callback. 247 | 248 | #### `clang.ErrorCode` 249 | 250 | [`enum CXErrorCode`]: 251 | https://clang.llvm.org/doxygen/CXErrorCode_8h.html#adba17f287f8184fc266f2db4e669bf0f 252 | 253 | An object mapping names to values representing success or various 254 | error conditions. The names are identical to those in [`enum CXErrorCode`] with 255 | the "`CXError_`" prefix removed. 256 | 257 | ### Index 258 | 259 | #### `translationUnit, errorCode = index:parse(sourceFileName, cmdLineArgs [, opts])` 260 | 261 | [`clang_parseTranslationUnit2`]: 262 | http://clang.llvm.org/doxygen/group__CINDEX__TRANSLATION__UNIT.html#ga494de0e725c5ae40cbdea5fa6081027d 263 | 264 | [`CXTranslationUnit_*`]: 265 | http://clang.llvm.org/doxygen/group__CINDEX__TRANSLATION__UNIT.html#enum-members 266 | 267 | Binding for [`clang_parseTranslationUnit2`]. This will parse a given source 268 | file named `sourceFileName` with the command line arguments `cmdLineArgs` given 269 | to the compiler, containing e.g. include paths or defines. If `sourceFile` is 270 | the empty string, the source file is expected to be named in `cmdLineArgs`. 271 | 272 | The optional argument `opts` is expected to be a sequence containing 273 | [`CXTranslationUnit_*`] enum names without the `"CXTranslationUnit_"` prefix, 274 | for example `{ "DetailedPreprocessingRecord", "SkipFunctionBodies" }`. 275 | 276 | NOTE: Both `cmdLineArgs` and `opts` (if given) must not contain an element at index 0. 277 | 278 | On failure, `translationUnit` is `nil` and `errorCode` (comparable against 279 | values in `clang.ErrorCode`) can be examined. 280 | 281 | 282 | License 283 | ------- 284 | 285 | Copyright (C) 2013-2020 Philipp Kutin. MIT licensed. See LICENSE for details. 286 | -------------------------------------------------------------------------------- /README.md.in: -------------------------------------------------------------------------------- 1 | 2 | LJClang -- A LuaJIT-based interface to libclang 3 | =============================================== 4 | 5 | ### Table of Contents 6 | 7 | @@[toc] 8 | 9 | 10 | Introduction 11 | ------------ 12 | 13 | [LuaJIT]: https://luajit.org/ 14 | [libclang]: https://clang.llvm.org/doxygen/group__CINDEX.html 15 | [luaclang-parser]: https://github.com/mkottman/luaclang-parser 16 | 17 | LJClang is an interface to [libclang] for [LuaJIT], modeled after and mostly 18 | API-compatible with [luaclang-parser] by Michal Kottman. 19 | 20 | 21 | Requirements 22 | ------------ 23 | 24 | * LuaJIT 2.0 or greater 25 | 26 | * LLVM/Clang -- from the Linux distribution or 27 | [here](https://apt.llvm.org/). Development is done using the latest stable 28 | version, but older versions should work mostly fine (except that interfaces 29 | exposed by newer version are not available, of course). 30 | 31 | 32 | Building 33 | -------- 34 | 35 | Invoking `make` builds the required support library `libljclang_support.so`, 36 | converts libclang C headers into a form that can be used by LuaJIT (using a Lua 37 | program that essentially strips text that would not be understood by LuaJIT's 38 | `ffi.cdef`) and finally extracts additional information using LJClang itself. 39 | 40 | The file `config.make` contains some user-facing configuration. 41 | 42 | 43 | Overview 44 | -------- 45 | 46 | LJClang provides a cursor-based, callback-driven API to the abstract syntax 47 | tree (AST) of C/C++ source files. These are the main classes: 48 | 49 | * `Index` -- represents a set of translation units that could be linked together 50 | * `TranslationUnit` -- a source file together with everything included by it 51 | either directly or transitively 52 | * `Cursor` -- points to an element in the AST in a translation unit such as a 53 | `typedef` declaration or a statement 54 | * `Type` -- the type of an element (for example, that of a variable, structure 55 | member, or a function's input argument or return value) 56 | 57 | To make something interesting happen, you usually create a single `Index` 58 | object, parse into it one or more translation units, and define a callback 59 | function to be invoked on each visit of a `Cursor` by libclang. 60 | 61 | 62 | Example programs 63 | ---------------- 64 | 65 | ### `extractdecls.lua` 66 | 67 | [`enum CXCursorKind`]: 68 | https://clang.llvm.org/doxygen/group__CINDEX.html#gaaccc432245b4cd9f2d470913f9ef0013 69 | 70 | The `extractdecls.lua` script accompanied by LJClang can be used to extract 71 | various kinds of C declarations from (usually) headers and print them in 72 | various forms usable as FFI C declarations or descriptive tables with LuaJIT. 73 | 74 | @@[run]./extractdecls.lua 75 | 76 | In fact, the file `ljclang_cursor_kind.lua` is generated by this program and is 77 | used by LJClang to map values of the enumeration [`enum CXCursorKind`] to their 78 | names. The `bootstrap` target in the `Makefile` extracts the relevant 79 | information using these options: 80 | 81 | ~~~~~~~~~~ 82 | -Q -R -e 'CXCursorKind' -p '^CXCursor_' -s '^CXCursor_' \ 83 | -x '_First' -x '_Last' -x '_GCCAsmStmt' -x '_MacroInstantiation' \ 84 | -1 'CursorKindName = {' -2 '},' 85 | ~~~~~~~~~~ 86 | 87 | 88 | Thus, the enum constant names are filtered to be taken from `enum CXCursorKind`, 89 | beginning with `CXCursor_` (that prefix being stripped) and all "secondary" names 90 | aliasing the one considered the main one are rejected. (For example, 91 | `CXCursor_AsmStmt` and `CXCursor_GCCAsmStmt` have the same value.) This yields 92 | lines like 93 | 94 | ~~~~~~~~~~ 95 | [215] = "AsmStmt"; 96 | ~~~~~~~~~~ 97 | 98 | ### `watch_compile_commands.lua` 99 | 100 | @@[run]./watch_compile_commands.lua 101 | 102 | 103 | Reference 104 | --------- 105 | 106 | The module returned by `require("ljclang")` -- called `clang` from here on -- 107 | contains the following: 108 | 109 | @@clang.createIndex 110 | 111 | #### `clang.ChildVisitResult` 112 | 113 | [`enum CXChildVisitResult`]: 114 | https://clang.llvm.org/doxygen/group__CINDEX__CURSOR__TRAVERSAL.html#ga99a9058656e696b622fbefaf5207d715 115 | 116 | An object mapping names to values to be returned 117 | from cursor visitor callbacks. The names are identical with those in [`enum 118 | CXChildVisitResult`] with the "`CXChildVisit_`" prefix removed: `Break`, 119 | `Continue`, `Recurse`. 120 | 121 | @@clang.regCursorVisitor 122 | 123 | @@clang.Cursor 124 | 125 | #### `clang.ErrorCode` 126 | 127 | [`enum CXErrorCode`]: 128 | https://clang.llvm.org/doxygen/CXErrorCode_8h.html#adba17f287f8184fc266f2db4e669bf0f 129 | 130 | An object mapping names to values representing success or various 131 | error conditions. The names are identical to those in [`enum CXErrorCode`] with 132 | the "`CXError_`" prefix removed. 133 | 134 | ### Index 135 | 136 | @@index:parse 137 | 138 | 139 | License 140 | ------- 141 | 142 | Copyright (C) 2013-2020 Philipp Kutin. MIT licensed. See LICENSE for details. 143 | -------------------------------------------------------------------------------- /class.lua: -------------------------------------------------------------------------------- 1 | 2 | local ffi = require("ffi") 3 | 4 | local assert = assert 5 | local error = error 6 | local pairs = pairs 7 | local setmetatable = setmetatable 8 | local tostring = tostring 9 | local type = type 10 | 11 | local check = require("error_util").check 12 | 13 | ---------- 14 | 15 | local api = {} 16 | 17 | function api.class(tab) 18 | check(type(tab) == "table", "argument must be a table", 2) 19 | 20 | -- The generated metatable 21 | local mt = { __metatable="class" } 22 | local ctor 23 | 24 | -- Whether we have "plain" string keys, that is, ones not starting with two underscores. 25 | local havePlainKeys = false 26 | 27 | -- check tab contents 28 | for k,v in pairs(tab) do 29 | if (k == 1) then 30 | -- constructor: a function that returns a table, or string 31 | -- containing struct definition. 32 | local isCType = (type(v) == "cdata" and tostring(v):match("^ctype<")) 33 | check(type(v) == "function" or type(v) == "string" or isCType, 34 | "tab[1] must be a function, string or ctype", 2) 35 | ctor = v 36 | elseif (type(k) == "string") then 37 | check(type(v) == "function" or type(v) == "string", 38 | "tab[] must be a function or a string", 2) 39 | if (k:sub(1,2) == "__") then 40 | if (k == "__index") then 41 | check(type(v) == "function", "tab.__index must be a function", 2) 42 | elseif (k == "__gc") then 43 | error("__gc does not work, use ffi.gc() instead", 2) 44 | end 45 | else 46 | havePlainKeys = true 47 | end 48 | else 49 | error("tab can contain entries at [1], or string keys", 2) 50 | end 51 | end 52 | 53 | local __index_tab = {} 54 | local __index_func = tab.__index 55 | 56 | if (__index_func ~= nil and havePlainKeys) then 57 | -- The case where 'tab' has both key '__index' (which then must be a function, as 58 | -- checked above), as well as convenience string keys. 59 | error("tab has both __index and convenience __index entries", 2) 60 | elseif (havePlainKeys) then 61 | mt.__index = __index_tab 62 | elseif (__index_func ~= nil) then 63 | mt.__index = __index_func 64 | end 65 | 66 | check(ctor ~= nil, "must provide a constructor in tab[1]") 67 | tab[1] = nil 68 | 69 | -- Create the metatable by taking over the contents of the one passed to us. 70 | for k,v in pairs(tab) do 71 | assert(type(k) == "string") 72 | 73 | if (type(v) == "string") then -- alias 74 | v = tab[v] 75 | end 76 | 77 | if (k:sub(1,2) == "__") then 78 | if (k ~= "__index") then 79 | mt[k] = v 80 | end 81 | else 82 | __index_tab[k] = v 83 | end 84 | end 85 | 86 | if (type(ctor) == "function") then 87 | local factory = function(...) 88 | local t = ctor(...) 89 | check(t == nil or type(t) == "table", "constructor must return nil or a table", 2) 90 | if (t ~= nil) then 91 | return setmetatable(t, mt) 92 | end 93 | end 94 | 95 | return factory 96 | else 97 | local ct = (type(ctor) == "string") and "struct {"..ctor.."}" or ctor 98 | return ffi.metatype(ct, mt) 99 | end 100 | end 101 | 102 | -- Done! 103 | return api 104 | -------------------------------------------------------------------------------- /compile_commands_reader.lua: -------------------------------------------------------------------------------- 1 | local io = require("io") 2 | 3 | local math = require("math") 4 | local util = require("util") 5 | 6 | local compile_commands_util = require("compile_commands_util") 7 | 8 | local check = require("error_util").check 9 | 10 | local assert = assert 11 | local ipairs = ipairs 12 | local loadstring = loadstring 13 | local pairs = pairs 14 | local pcall = pcall 15 | local setfenv = setfenv 16 | local type = type 17 | 18 | ---------- 19 | 20 | local api = {} 21 | 22 | local function tweak_json_string_for_load_as_lua_table(str) 23 | -- replace a lone '[]' with '{}' so that an empty compile_commands.json is handled 24 | str = str:gsub("^%[%]", "{}") 25 | -- replace leading/trailing matching '[ (...) ]' with '{ (...) }' 26 | str = str:gsub("^%[\n", "{\n"):gsub("\n%]\n?$", "\n}") 27 | -- replace any other '[]' (expected: of 'arguments' key, if present) 28 | str = str:gsub(": %[\n", ": {\n"):gsub("%], *\n", "},\n") 29 | -- replace '"": ' by 'key= ' (expected to occur in what would now be a Lua table) 30 | return "return "..str:gsub('\n( *)"([a-z]+)": ', '\n%1%2= ') 31 | end 32 | 33 | local PREFIX = "ERROR: Unexpected input: parsed result " 34 | 35 | local function validate_compile_commands_table(cmds) 36 | if (type(cmds) ~= "table") then 37 | return PREFIX.."is not a Lua table" 38 | end 39 | 40 | local numCmds = #cmds 41 | 42 | for k,_ in pairs(cmds) do 43 | if (type(k) ~= "number") then 44 | return PREFIX.."contains non-numeric keys" 45 | end 46 | 47 | if (math.floor(k) ~= k) then 48 | return PREFIX.."contains non-integral numeric keys" 49 | end 50 | 51 | if (not (k >= 1 and k <= numCmds)) then 52 | return PREFIX.."contains numeric keys inconsistent with #table" 53 | end 54 | end 55 | 56 | local hasArgs = (numCmds > 0 and cmds[1].arguments ~= nil) 57 | local hasCommand = (numCmds > 0 and cmds[1].command ~= nil) 58 | 59 | if (numCmds > 0 and not hasArgs and not hasCommand) then 60 | return PREFIX.."is non-empty but its first element contains neither key 'arguments' nor 'command'" 61 | end 62 | 63 | local key = hasArgs and "arguments" or "command" 64 | local expectedKeyType = hasArgs and "table" or "string" 65 | local expectedMissingKey = hasArgs and "command" or "arguments" 66 | 67 | for _, cmd in ipairs(cmds) do 68 | if (type(cmd) ~= "table") then 69 | return PREFIX.."contains missing or non-table elements" 70 | end 71 | 72 | if (type(cmd.directory) ~= "string") then 73 | return PREFIX.."contains an element with key 'directory' missing or not of string type" 74 | end 75 | 76 | if (type(cmd.file) ~= "string") then 77 | return PREFIX.."contains an element with key 'file' missing or not of string type" 78 | end 79 | 80 | if (type(cmd[key]) ~= expectedKeyType) then 81 | return PREFIX.."contains an element with key '"..key.. 82 | "' missing or not of "..expectedKeyType.." type" 83 | end 84 | 85 | if (cmd[expectedMissingKey] ~= nil) then 86 | return PREFIX.."contains and element with key '"..expectedMissingKey.. 87 | "' unexpectedly present" 88 | end 89 | 90 | if (hasCommand and cmd.command:match("\\%s")) then 91 | -- We will split the command by whitespace, so escaped whitespace characters 92 | -- would throw us off the track. For now, bail out if we come across that case. 93 | return PREFIX.."contains an element with key 'command' ".. 94 | "containing a backslash followed by whitespace (not implemented)" 95 | end 96 | end 97 | 98 | return nil, hasCommand 99 | end 100 | 101 | -- If the entries have key 'command' (and thus do not have key 'args', since we validated 102 | -- mutual exclusion), add key 'args'. Also make keys 'file' absolute file names by prefixing 103 | -- them with key 'directory' whenever they are not already absolute. 104 | local function tweak_compile_commands_table(cmds, hasCommand) 105 | assert(type(cmds) == "table") 106 | 107 | if (hasCommand) then 108 | for _, cmd in ipairs(cmds) do 109 | local argv = util.splitAtWhitespace(cmd.command) 110 | local arguments = {} 111 | 112 | for i = 2,#argv do 113 | -- Keep only the arguments, not the invoked compiler executable name. 114 | assert(type(argv[i]) == "string") 115 | arguments[i - 1] = argv[i] 116 | end 117 | 118 | cmd.arguments = arguments 119 | cmd.compiler_executable = argv[1] 120 | cmd.command = nil 121 | end 122 | else 123 | for _, cmd in ipairs(cmds) do 124 | local args = cmd.arguments 125 | 126 | cmd.compiler_executable = args[1] 127 | 128 | for i = 1, #args do 129 | -- Shift elements of 'args' one left. 130 | args[i] = args[i+1] 131 | end 132 | end 133 | end 134 | 135 | for _, cmd in ipairs(cmds) do 136 | -- The key 'file' as it appears in the compile_commands.json: 137 | local compiledFileName = cmd.file 138 | -- Absify it: 139 | local absoluteFileName = compile_commands_util.absify(cmd.file, cmd.directory) 140 | assert(type(absoluteFileName) == "string") 141 | cmd.file = absoluteFileName 142 | 143 | -- And also absify it appearing in the argument list. 144 | 145 | local matchCount = 0 146 | 147 | for ai, arg in ipairs(cmd.arguments) do 148 | if (arg == compiledFileName) then 149 | cmd.arguments[ai] = absoluteFileName 150 | matchCount = matchCount + 1 151 | end 152 | end 153 | 154 | cmd.arguments, cmd.pchArguments = 155 | compile_commands_util.sanitize_args(cmd.arguments, cmd.directory) 156 | 157 | -- NOTE: "== 1" is overly strict. I'm just curious about the situation in the wild. 158 | if (matchCount ~= 1) then 159 | return nil, PREFIX.."contains an entry for which the name of ".. 160 | "the compiled file is not found exactly once in the compiler arguments" 161 | end 162 | 163 | for ai, arg in ipairs(cmd.arguments) do 164 | if (arg == absoluteFileName) then 165 | cmd.fileNameIdx = ai 166 | break 167 | end 168 | end 169 | 170 | assert(cmd.fileNameIdx ~= nil) 171 | end 172 | 173 | return cmds 174 | end 175 | 176 | local function load_json_as_lua_string(str) 177 | local func, errmsg = loadstring(str, "compile_commands.json as Lua table") 178 | 179 | if (func == nil) then 180 | return nil, errmsg 181 | end 182 | 183 | -- Completely empty the function's environment as an additional safety measure, 184 | -- then run the chunk protected. 185 | local ok, result = pcall(setfenv(func, {})) 186 | if (not ok) then 187 | assert(type(result) == "string") -- error message 188 | return nil, result 189 | end 190 | 191 | local errmsg, hasCommand = validate_compile_commands_table(result) 192 | if (errmsg ~= nil) then 193 | return nil, errmsg 194 | end 195 | 196 | return tweak_compile_commands_table(result, hasCommand) 197 | end 198 | 199 | -- Parses a compile_commands.json file, returning a Lua table. 200 | -- On failure, returns nil and an error message. 201 | -- 202 | -- Supported formats: 203 | -- 204 | -- [ 205 | -- , 206 | -- , 207 | -- ... 208 | -- ] 209 | -- 210 | -- with being either (1) 211 | -- 212 | -- { 213 | -- "arguments": [ , , ... ], 214 | -- "directory": , 215 | -- "file": 216 | -- } 217 | -- 218 | -- or (2) 219 | -- 220 | -- { 221 | -- "command": (compiler executable followed by its arguments, whitespace-separated) 222 | -- "directory": , 223 | -- "file": 224 | -- } 225 | -- 226 | -- The returned table always contains entries of the form (1). Backslashes followed by 227 | -- whitespace in the "command" key in form (2) are rejected. 228 | function api.parse_compile_commands(compile_commands_string) 229 | check(type(compile_commands_string) == "string", 230 | " must be a string", 2) 231 | 232 | local str = tweak_json_string_for_load_as_lua_table(compile_commands_string) 233 | return load_json_as_lua_string(str) 234 | end 235 | 236 | function api.read_compile_commands(filename) 237 | check(type(filename) == "string", " must be a string", 2) 238 | local f, msg = io.open(filename) 239 | 240 | if (f == nil) then 241 | return nil, msg 242 | end 243 | 244 | local str, msg = f:read("*a") 245 | f:close() 246 | 247 | if (str == nil) then 248 | return nil, msg 249 | end 250 | 251 | return api.parse_compile_commands(str) 252 | end 253 | 254 | -- Done! 255 | return api 256 | -------------------------------------------------------------------------------- /config.make: -------------------------------------------------------------------------------- 1 | 2 | # Directory to install applications. The resulting scripts reference THIS_DIR (i.e. the 3 | # development directory) if doing 'install-dev'. 4 | BINDIR ?= $(HOME)/bin 5 | 6 | LLVM_CONFIG ?= llvm-config-12 7 | 8 | luajit := luajit 9 | 10 | # Will use this Markdown processor for .md -> .html if it is found: 11 | MARKDOWN := cmark 12 | -------------------------------------------------------------------------------- /dev/ParseClangLangOptionsDef.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env luajit 2 | 3 | -- This is a development utility program: it parses LLVM's LangOptions.def and produces the 4 | -- precursor of a list of PCH-relevant options that need to be handled in addition to the 5 | -- handling of selected options in compile_commands_util.lua's sanitize_args(). 6 | -- 7 | -- After manually looking at the dependency semantics of the various options (default value, 8 | -- is it self-contained or dependent on others?, ...), PchRelevantLangOptions.lua results. 9 | 10 | local io = require("io") 11 | local os = require("os") 12 | 13 | local string = require("string") 14 | local format = string.format 15 | 16 | local ipairs = ipairs 17 | local arg = arg 18 | 19 | ---------- 20 | 21 | local function errprint(str) 22 | io.stderr:write(str.."\n") 23 | end 24 | 25 | local function errprintf(fmt, ...) 26 | errprint(format(fmt, ...)) 27 | end 28 | 29 | ---------- 30 | 31 | local filename = arg[1] 32 | 33 | if (filename == nil) then 34 | errprintf("Usage: %s path/to//clang/include/clang/Basic/LangOptions.def", arg[0]) 35 | os.exit(1) 36 | end 37 | 38 | local f, msg = io.open(filename) 39 | 40 | if (f == nil) then 41 | errprintf("Error opening %s: %s", filename, msg) 42 | os.exit(1) 43 | end 44 | 45 | local IgnoredLangOptPatterns = { 46 | -- Already handled by '-std=...'. 47 | "^CPlusPlus", 48 | 49 | -- Not C++. 50 | -- TODO: might some be PCH-relevant even in C++ after all? 51 | "^ObjC", "^OpenCL", "^CUDA", "^OpenMP", 52 | "^NativeHalf", 53 | } 54 | 55 | local IsIgnoredLangOpt = { 56 | -- Not C++. 57 | ["GC"] = true, 58 | ["HIP"] = true, 59 | 60 | -- Already handled. 61 | ["Optimize"] = true, 62 | ["OptimizeSize"] = true, 63 | ["PICLevel"] = true, 64 | ["PIE"] = true, 65 | 66 | -- Strictly language-dependent. 67 | ["C99"] = true, 68 | ["C11"] = true, 69 | ["C17"] = true, 70 | ["LineComment"] = true, 71 | ["Bool"] = true, 72 | ["Half"] = true, 73 | ["WChar"] = true, 74 | 75 | ["GNUMode"] = true, 76 | ["GNUInline"] = true, 77 | 78 | ["LaxVectorConversions"] = true, -- OpenCL only (and then conditional) 79 | ["DoubleSquareBracketAttributes"] = true, -- always true in C++11+ 80 | 81 | -- Not applicable to C++. 82 | ["HalfArgsAndReturns"] = true, 83 | ["RenderScript"] = true, 84 | ["GPURelocatableDeviceCode"] = true, 85 | 86 | -- Gave errors for C++ in my (admittedly not exhaustive) testing. 87 | 88 | -- May be partly because not applicable to C++, and partly because of incomplete 89 | -- understanding. 90 | ["AddressSpaceMapMangling"] = true, 91 | ["AlignedAllocationUnavailable"] = true, 92 | ["BlocksRuntimeOptional"] = true, 93 | ["ConceptsTS"] = true, 94 | ["ConstStrings"] = true, 95 | ["Deprecated"] = true, 96 | ["DefaultCallingConv"] = true, 97 | ["DllExportInlines"] = true, 98 | ["ExternCNoUnwind"] = true, 99 | ["FakeAddressSpaceMap"] = true, 100 | ["FunctionAlignment"] = true, 101 | ["IncludeDefaultHeader"] = true, 102 | ["NoBitFieldTypeAlign"] = true, 103 | ["NoMathBuiltin"] = true, 104 | ["Static"] = true, 105 | ["VtorDispMode"] = true, 106 | ["WCharSize"] = true, 107 | ["WCharIsSigned"] = true, 108 | 109 | -- Always false for C++, see LLVM's clang/lib/Frontend/CompilerInvocation.cpp 110 | ["FixedPoint"] = true, 111 | ["PaddingOnUnsignedFixedPoint"] = true, 112 | } 113 | 114 | while (true) do 115 | local l = f:read("*l") 116 | 117 | if (l == nil) then 118 | break 119 | end 120 | 121 | local langoptType, langoptName = l:match("^([A-Z_]+)%(([A-Za-z_][A-Za-z_0-9]*)") 122 | 123 | if (langoptType == nil) then 124 | goto next 125 | end 126 | 127 | if (langoptType:match("BENIGN")) then 128 | goto next 129 | end 130 | 131 | for _, pattern in ipairs(IgnoredLangOptPatterns) do 132 | if (langoptName:match(pattern)) then 133 | goto next 134 | end 135 | end 136 | 137 | if (IsIgnoredLangOpt[langoptName]) then 138 | goto next 139 | end 140 | 141 | io.stdout:write(langoptName, '\n') 142 | 143 | ::next:: 144 | end 145 | -------------------------------------------------------------------------------- /dev/PchRelevantLangOptions.lua: -------------------------------------------------------------------------------- 1 | 2 | -- List of Clang compiler options relevant to precompiled headers. (That is, options 3 | -- determining generation-use compatibility between precompiled headers.) 4 | 5 | -- NOTE: false/true as default values refers to positive form of the -f* option, even if the 6 | -- LangOption name (which is only kept for reference, but not in the code) is negated and 7 | -- even if only the '-fno-*' form exists. 8 | -- 9 | -- Examples: 10 | -- 11 | -- 1. Negated LangOption name: 12 | -- { "NoConstantCFStrings", "-fconstant-cfstrings", true }, 13 | -- means that -fconstant-cfstrings is true by default, not NoConstantCFStrings 14 | -- (-fno-constant-cfstrings). 15 | -- 16 | -- 2. Only '-fno-*' exists: 17 | -- { "CXXOperatorNames", "-foperator-names", ONLY_NEGATIVE }, 18 | -- means that -fno-operator-names is not enabled by default (logically), but for our 19 | -- purposes we say that (the nonexistent option) "-foperator-names" has default 'true'. 20 | 21 | local assert = assert 22 | local type = type 23 | 24 | ---------- 25 | 26 | local MAKE_NO_ASSUMPTION = {} 27 | 28 | -- Tags for the option behavior. 29 | local DEPENDENT = MAKE_NO_ASSUMPTION -- Dependent on other options (and potentially something else) 30 | local LANG = MAKE_NO_ASSUMPTION -- Dependent on the language 31 | local TRUE_IN_CXX = true -- Always true in C++ 32 | -- Only the '-fno-*' form exists. Note: in the table below the argument is still 33 | -- '-f', for indexing purposes from the Lua code. 34 | local ONLY_NEGATIVE = true 35 | local UNKNOWN = MAKE_NO_ASSUMPTION -- Could not determine default empirically 36 | local EMPIRICAL_TRUE = true -- Default is 'true' empirically, but that seems to contradict the code 37 | local COMPUTED = MAKE_NO_ASSUMPTION -- Default value is computed. (Not "constant" in a trivial way.) 38 | 39 | ---------- 40 | 41 | -- { , , , [computed] } 42 | -- 43 | -- NOTE: argument names for one given LangOption may not be exhaustive. In particular, if 44 | -- one option depends on an argument name that another option also depends on, it is listed 45 | -- only once. 46 | local LangOptions = { 47 | { "MSVCCompat", "-fms-compatibility", false }, 48 | { "MicrosoftExt", "-fms-extensions", DEPENDENT }, 49 | { "AsmBlocks", "-fasm-blocks", DEPENDENT }, 50 | { "Borland", "-fborland-extensions", false }, 51 | { "AppExt", "-fapplication-extension", false }, 52 | { "Trigraphs", "-ftrigraphs", DEPENDENT }, 53 | { "Char8", "-fchar8_t", LANG }, 54 | { "DeclSpecKeyword", "-fdeclspec", DEPENDENT }, 55 | --< 56 | { "GNUKeywords", "-fgnu-keywords", LANG }, 57 | { "GNUKeywords", "-fasm", LANG }, 58 | --> 59 | { "Digraphs", "-fdigraphs", TRUE_IN_CXX }, 60 | { "CXXOperatorNames", "-foperator-names", ONLY_NEGATIVE }, 61 | { "AppleKext", "-fapple-kext", false }, 62 | { "WritableStrings", "-fwritable-string", false }, 63 | { "AltiVec", "-maltivec", UNKNOWN }, 64 | { "ZVector", "-fzvector", false }, 65 | { "Exceptions", "-fexceptions", EMPIRICAL_TRUE }, 66 | { "CXXExceptions", "-fcxx-exceptions", EMPIRICAL_TRUE }, 67 | { "DWARFExceptions", "-fdwarf-exceptions", false }, 68 | { "SjLjExceptions", "-fsjlj-exceptions", false }, 69 | { "SEHExceptions", "-fseh-exceptions", false }, 70 | { "TraditionalCPP", "-traditional-cpp", false }, 71 | { "RTTI", "-frtti", true }, 72 | { "RTTIData", "-frtti-data", ONLY_NEGATIVE }, 73 | { "MSBitfields", "-mms-bitfields", false }, 74 | { "Freestanding", "-ffreestanding", false }, 75 | { "NoBuiltin", "-fbuiltin", DEPENDENT }, 76 | { "GNUAsm", "-fgnu-inline-asm", true }, 77 | { "CoroutinesTS", "-fcoroutines-ts", false }, 78 | { "RelaxedTemplateTemplateArgs", "-frelaxed-template-template-args", false }, 79 | { "POSIXThreads", "-pthread", false }, 80 | { "Blocks", "-fblocks", false }, -- dependent, but only on OpenCL 81 | { "MathErrno", "-fmath-errno", true }, 82 | { "Modules", "-fmodules", DEPENDENT }, 83 | { "ModulesTS", "-fmodules-ts", false }, 84 | { "ModulesDeclUse", "-fmodules-decluse", DEPENDENT }, 85 | { "ModulesStrictDeclUse", "-fmodules-strict-decluse", false }, 86 | { "ModulesLocalVisibility", "-fmodules-local-submodule-visibility", DEPENDENT }, 87 | { "PackStruct", "-fpack-struct=", 0 }, 88 | { "MaxTypeAlign", "-fmax-type-align=", 0 }, 89 | { "AlignDouble", "-malign-double", UNKNOWN }, 90 | -- PIC-related options are parsed in the driver and passed down to the compiler frontend by 91 | -- what appear to be internal options (such as -pic-level). See LLVM's 92 | -- clang/lib/Driver/ToolChains/CommonArgs.cpp 93 | -- and its uses in 94 | -- clang/lib/Driver/ToolChains/Clang.cpp 95 | -- So, do not attempt any smartness at our side, just collect them as they are. 96 | -- Note that we cannot just strip them: for example, __PIC__ is defined when PIC is enabled. 97 | { "", "-fPIC", DEPENDENT }, 98 | { "", "-fpic", DEPENDENT }, 99 | { "", "-fPIE", DEPENDENT }, 100 | { "", "-fpie", DEPENDENT }, 101 | --< 102 | { "NoInlineDefine", "-finline", DEPENDENT }, 103 | { "NoInlineDefine", "-finline-functions", DEPENDENT }, 104 | { "NoInlineDefine", "-finline-hint-functions", DEPENDENT }, 105 | --> 106 | { "FastMath", "-ffast-math", DEPENDENT }, 107 | { "FiniteMathOnly", "-ffinite-math-only", DEPENDENT }, 108 | --< 109 | { "UnsafeFPMath", "-menable-unsafe-fp-math", DEPENDENT }, 110 | { "UnsafeFPMath", "-cl-unsafe-math-optimizations", DEPENDENT }, 111 | --> 112 | { "CharIsSigned", "-fsigned-char", TRUE_IN_CXX }, 113 | { "MSPointerToMemberRepresentationMethod", "-fms-memptr-rep=", UNKNOWN }, 114 | { "ShortEnums", "-fshort-enums", false }, 115 | { "SizedDeallocation", "-fsized-deallocation", false }, 116 | { "AlignedAllocation", "-faligned-allocation", EMPIRICAL_TRUE }, 117 | { "NewAlignOverride", "-fnew-alignment=", 0 }, 118 | { "NoConstantCFStrings", "-fconstant-cfstrings", true }, 119 | { "GlobalAllocationFunctionVisibilityHidden", "-fvisibility-global-new-delete-hidden", false }, 120 | { "SinglePrecisionConstants", "-cl-single-precision-constant", false }, 121 | { "FastRelaxedMath", "-cl-fast-relaxed-math", false }, 122 | { "DefaultFPContractMode", "-ffp-contract=", "off" }, 123 | { "HexagonQdsp6Compat", "-mqdsp6_compat", UNKNOWN }, 124 | --< NOTE: duplicate name, once as a flag and once as an option with value. 125 | { "CFProtectionBranch", "-fcf-protection", false }, 126 | { "CFProtectionBranch", "-fcf-protection=", UNKNOWN }, 127 | --> 128 | { "ValueVisibilityMode", "-fvisibility=", "default" }, 129 | { "TypeVisibilityMode", "-ftype-visibility", DEPENDENT }, 130 | { "StackProtector", "-fstack-protector", false }, 131 | { "TrivialAutoVarInit", "-ftrivial-auto-var-init=", "uninitialized" }, 132 | --< 133 | { "SignedOverflowBehavior", "-ftrapv", false }, 134 | { "SignedOverflowBehavior", "-fwrapv", false }, 135 | --> 136 | { "MSCompatibilityVersion", "-fms-compatibility-version=", 0 }, 137 | { "ApplePragmaPack", "-fapple-pragma-pack", false }, 138 | { "RetainCommentsFromSystemHeaders", "-fretain-comments-from-system-headers", false }, 139 | { "SanitizeAddressFieldPadding", "-fsanitize-address-field-padding=", UNKNOWN }, 140 | { "XRayInstrument", "-fxray-instrument", false }, 141 | { "XRayAlwaysEmitCustomEvents", "-fxray-always-emit-customevents", UNKNOWN }, 142 | { "XRayAlwaysEmitTypedEvents", "-fxray-always-emit-typedevents", UNKNOWN }, 143 | { "ForceEmitVTables", "-fforce-emit-vtables", false }, 144 | { "ClangABICompat", "-fclang-abi-compat=", COMPUTED }, 145 | { "RegisterStaticDestructors", "-fno-c++-static-destructors", false }, 146 | } 147 | 148 | -- Generate abbreviated argument names 149 | 150 | -- [] = nil or count 151 | local abbrevCount = {} 152 | 153 | for i = 1, #LangOptions do 154 | local triple = LangOptions[i] 155 | local carg = triple[2] 156 | 157 | -- CAUTION LUA_PATTERN: in a Lua pattern, '-' is in general a magic character, *unless* 158 | -- (it seems) when its interpretation as such would be meaningless. (At least, this is 159 | -- the case when it is not preceded by a character class). 160 | -- 161 | -- So, patterns of the form "^-" (as opposed to the more proper "^%-") do match a 162 | -- literal '-' at the beginning of a string. 163 | 164 | assert(carg:match("^-[a-z]+")) 165 | -- Check expectations that are relied on at the usage site. 166 | assert(not carg:match('=') or carg:match("^-f")) -- only 'f' args have '=' 167 | assert(not carg:match("^-.no%-") or carg:match("^-fno-")) -- only 'f' args have negation 168 | 169 | local abbrevPrefix = carg:match('^%-([a-z]+)') 170 | local abbrev 171 | 172 | if (abbrevCount[abbrevPrefix] == nil) then 173 | abbrev = abbrevPrefix 174 | abbrevCount[abbrevPrefix] = 1 175 | else 176 | abbrev = abbrevPrefix .. abbrevCount[abbrevPrefix] 177 | abbrevCount[abbrevPrefix] = abbrevCount[abbrevPrefix] + 1 178 | end 179 | 180 | triple[4] = abbrev 181 | end 182 | 183 | -- Generate map of compiler argument ("key") to index into LangOptions. 184 | local ArgToOptIdx = {} 185 | 186 | for i = 1, #LangOptions do 187 | local quad = LangOptions[i] 188 | local carg, behavior = quad[2], quad[3] 189 | assert(type(carg) == "string") 190 | ArgToOptIdx[carg] = i 191 | end 192 | 193 | -- Done! 194 | return { Opts = LangOptions, ArgToOptIdx = ArgToOptIdx } 195 | -------------------------------------------------------------------------------- /dev/app-prefix.sh.in: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | d=@LJCLANG_DEV_DIR@ # FIXME: remove! 4 | export LD_LIBRARY_PATH="@LLVM_LIBDIR@:$d" 5 | export LLVM_BINDIR="@LLVM_BINDIR@" 6 | 7 | # CAUTION: the quotes here are crucial! 8 | exec luajit - "$@" <<"EOF________________________________________________________________________________" 9 | -------------------------------------------------------------------------------- /dev/app-suffix.sh.in: -------------------------------------------------------------------------------- 1 | EOF________________________________________________________________________________ 2 | -------------------------------------------------------------------------------- /dev/app.sh.in: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | d=@LJCLANG_DEV_DIR@ 3 | LUA_PATH=";;$d/?.lua" LD_LIBRARY_PATH="@LLVM_LIBDIR@:$d" LLVM_BINDIR="@LLVM_BINDIR@" luajit "$d/@APPLICATION@.lua" "$@" 4 | -------------------------------------------------------------------------------- /dev/createheader.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env luajit 2 | 3 | local io = require("io") 4 | local os = require("os") 5 | 6 | local dir = arg[1] 7 | 8 | if (dir == nil) then 9 | print("Usage: "..arg[0].." /usr/path/to/clang-c/ > ljclang_Index_h.lua") 10 | os.exit(1) 11 | end 12 | 13 | local function loadandstrip(filename) 14 | local f, errmsg = io.open(dir.."/"..filename) 15 | if (f==nil) then 16 | print("Error opening file: "..errmsg) 17 | os.exit(2) 18 | end 19 | 20 | local str = f:read("*a") 21 | f:close() 22 | 23 | -- Remove... 24 | return str:gsub("#ifdef __.-#endif\n", "") -- #ifdef __cplusplus/__have_feature ... #endif 25 | :gsub("#define.-[^\\]\n", "") -- multi-line #defines 26 | :gsub("/%*%*.-%*/", "") -- comments, but keep headers with license ref 27 | :gsub("#[^\n]-\n", "") -- single-line preprocessor directives 28 | :gsub("CINDEX_LINKAGE","") 29 | :gsub("CINDEX_DEPRECATED","") 30 | :gsub("LLVM_CLANG_C_EXTERN_C_BEGIN","") 31 | :gsub("LLVM_CLANG_C_EXTERN_C_END","") 32 | :gsub("time_t clang_getFileTime.-\n", "// REMOVED: clang_getFileTime\n") 33 | :gsub(" *\n+", "\n") 34 | end 35 | 36 | local cxstring_h = loadandstrip("CXString.h") 37 | local cxcompdb_h = loadandstrip("CXCompilationDatabase.h") 38 | local cxerrorcode_h = loadandstrip("CXErrorCode.h") 39 | local index_h = loadandstrip("Index.h") 40 | 41 | print("require('ffi').cdef[==========[\n", 42 | cxstring_h, cxcompdb_h, cxerrorcode_h, index_h, "]==========]") 43 | print("return {}") 44 | -------------------------------------------------------------------------------- /dev/cxx_headers.hpp: -------------------------------------------------------------------------------- 1 | // This file is used with the Auto-PCH feature ('-a') of watch_compile_commands. 2 | 3 | //#include // Depends on NDEBUG 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #if !defined _LIBCPP_VERSION 11 | # include // Not in libc++ 12 | #endif 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #if !defined _LIBCPP_VERSION 31 | # include // Not in libc++ 32 | #endif 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | //#include // In neither 46 | #ifdef _LIBCPP_VERSION 47 | # include // Not in libstdc++ 48 | #endif 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | #include 59 | #include 60 | #include 61 | #include 62 | #include 63 | #include 64 | #include 65 | //#include // In neither 66 | #include 67 | #include 68 | #include 69 | #include 70 | #include 71 | #include 72 | #include 73 | #include 74 | #include 75 | #include 76 | #include 77 | #include 78 | #include 79 | #include 80 | #include 81 | #include 82 | #include 83 | #include 84 | #ifdef _LIBCPP_VERSION 85 | # include // Deprecation warning with libstdc++ 86 | #endif 87 | #include 88 | #include 89 | #include 90 | #include 91 | #include 92 | #include 93 | #include 94 | #include 95 | #include 96 | #include 97 | #include 98 | #include 99 | -------------------------------------------------------------------------------- /dev/empty.cpp: -------------------------------------------------------------------------------- 1 | // This file is used with the Auto-PCH feature ('-a') of watch_compile_commands 2 | // to pre-test usage of a generated PCH file. 3 | -------------------------------------------------------------------------------- /dev/exdecl_macro_def.lua: -------------------------------------------------------------------------------- 1 | #!/bin/false extractdecls.lua modspec 2 | 3 | local check = check 4 | local concat = concat 5 | local printf = printf 6 | 7 | local printed = false 8 | 9 | return function(cur) 10 | if (not cur:haskind("MacroDefinition")) then 11 | return 12 | end 13 | 14 | check(not printed, "Found more than one match") 15 | printed = true 16 | 17 | local tokens = cur:_tokens() 18 | printf("%s", concat(tokens, "", 2, #tokens)) 19 | end 20 | -------------------------------------------------------------------------------- /dev/exdecl_struct_def.lua: -------------------------------------------------------------------------------- 1 | #!/bin/false extractdecls.lua modspec 2 | 3 | local check = check 4 | local concat = concat 5 | local printf = printf 6 | 7 | local printed = false 8 | 9 | return function(cur) 10 | if (not (cur:haskind("StructDecl") and cur:isDefinition())) then 11 | return 12 | end 13 | 14 | -- TODO: FreeType: why are there seemingly multiple definitions of 'FT_Vector_'? 15 | if (not printed) then 16 | printed = true 17 | 18 | local tokens = cur:_tokens() 19 | printf("%s", concat(tokens, ' ', 2, #tokens)) 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /dev/exdecl_struct_property.lua: -------------------------------------------------------------------------------- 1 | #!/bin/false extractdecls.lua modspec 2 | 3 | local check = check 4 | local printf = printf 5 | 6 | local printed = false 7 | 8 | return function(cur, args) 9 | local spec = args[1] 10 | local property, printHow = spec:match("^([^:]+)(.*)$") 11 | check(printHow == "" or printHow == ":name=value", 12 | "argument #1 must be suffixed with ':name=value' or not at all") 13 | check(property == "size" or property == "alignment" or property == "offset", 14 | "argument #1 must be 'size', 'alignment' or 'offset'") 15 | check(property ~= "offset" or #args == 2, "Must pass two user arguments") 16 | check(property == "offset" or #args == 1, "Must pass exactly one user arguments") 17 | 18 | if (not (cur:haskind("StructDecl") and cur:isDefinition())) then 19 | return 20 | end 21 | 22 | if (printHow == "") then 23 | check(not printed, "Found more than one match") 24 | end 25 | printed = true 26 | 27 | local ty = cur:type() 28 | local prop = 29 | (property == "size") and ty:size() or 30 | (property == "alignment") and ty:alignment() or 31 | ty:byteOffsetOf(args[2]) 32 | 33 | check(prop >= 0, "Error obtaining property") 34 | 35 | if (printHow == "") then 36 | printf("%d", prop) 37 | else 38 | printf("[%q]=%d,", ty:name(), prop) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /dev/exdecl_surrogate_struct.lua: -------------------------------------------------------------------------------- 1 | #!/bin/false extractdecls.lua modspec 2 | 3 | local check = check 4 | local printf = printf 5 | 6 | ---------- 7 | 8 | local TypeStrForByteCount = { 9 | [1] = "uint8_t", 10 | [2] = "uint16_t", 11 | [4] = "uint32_t", 12 | [8] = "uint64_t" 13 | } 14 | 15 | local printed = false 16 | 17 | return function(cur) 18 | if (not cur:isDefinition()) then 19 | return 20 | end 21 | 22 | -- TODO: allow batch usage. 23 | check(not printed, "Found more than one match") 24 | printed = true 25 | 26 | local ty = cur:type() 27 | local align = ty:alignment() 28 | local size = ty:size() 29 | 30 | check(align >= 0, "Error obtaining alignment") 31 | check(size >= 0, "Error obtaining size") 32 | 33 | check(size % align == 0, 34 | "Unsupported size: not evenly divisible by alignment") 35 | local alignTypeStr = TypeStrForByteCount[align] 36 | check(alignTypeStr ~= nil, "Unexpected or overlarge alignment") 37 | 38 | printf("struct { %s v_[%d]; }", alignTypeStr, size/align) 39 | end 40 | -------------------------------------------------------------------------------- /dev/exdecl_system_fingerprint.lua: -------------------------------------------------------------------------------- 1 | #!/bin/false extractdecls.lua modspec 2 | 3 | local ffi = ffi 4 | local printf = printf 5 | 6 | local printed = false 7 | 8 | return function(cur) 9 | if (not printed) then 10 | printed = true 11 | -- KEEPINSYNC posix_types.lua.in 12 | -- NOTE: for all processor architectures that LuaJIT supports, 13 | -- the string 'ffi.arch' implies bitness and endianness. 14 | -- See the definitions of macro 'LJ_ARCH_NAME' in its 'src/lj_arch.h'. 15 | printf("%s-%s", ffi.os, ffi.arch) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /dev/ljclang_extracted_enums.lua.in: -------------------------------------------------------------------------------- 1 | local ffi=require"ffi" 2 | return { 3 | ErrorCode = ffi.new[[struct{ 4 | @@ -C -e ^CXErrorCode$ -s ^CXError_ 5 | }]], 6 | SaveError = ffi.new[[struct{ 7 | @@ -C -e ^CXSaveError$ -s ^CXSaveError_ 8 | }]], 9 | DiagnosticSeverity = ffi.new[[struct{ 10 | @@ -C -e ^CXDiagnosticSeverity$ -s ^CXDiagnostic_ 11 | }]], 12 | ChildVisitResult = ffi.new[[struct{ 13 | @@ -C -e ^CXChildVisitResult$ -s ^CXChildVisit_ 14 | }]], 15 | -- NOTE: this mixes the constants of the two enums typedef'd as CXIdxEntityKind and 16 | -- CXIdxEntityCXXTemplateKind. 17 | IdxEntity = ffi.new[[struct{ 18 | @@ -C -w EnumConstantDecl -p ^CXIdxEntity_ -s ^CXIdxEntity_ -x ObjC 19 | }]], 20 | -- NOTE [ANONYMOUS_ENUM_WITH_TYPEDEF]: the enum type is anonymous here, but we are lucky 21 | -- because the prefix of the enum constant names is unique to this particular enum type. 22 | -- TODO: teach extractdecls to filter by the name of an immediate typedef. 23 | IdxEntityLang = ffi.new[[struct{ 24 | @@ -C -w EnumConstantDecl -p ^CXIdxEntityLang_ -s ^CXIdxEntityLang_ 25 | }]], 26 | IndexOpt = ffi.new[[struct{ 27 | @@ -C -w EnumConstantDecl -p ^CXIndexOpt_ -s ^CXIndexOpt_ 28 | }]], 29 | RefQualifierKind = ffi.new[[struct{ 30 | @@ -C -e ^CXRefQualifierKind$ -s ^CXRefQualifier_ 31 | }]], 32 | -- NOTE ANONYMOUS_ENUM_WITH_TYPEDEF: 33 | SymbolRole = ffi.new[[struct{ 34 | @@ -C -w EnumConstantDecl -p ^CXSymbolRole_ -s ^CXSymbolRole_ 35 | }]], 36 | CursorKindName = { 37 | @@ -w EnumConstantDecl -Q -R -e CXCursorKind -p ^CXCursor_ -s ^CXCursor_ \ 38 | -x _First -x _Last -x _GCCAsmStmt -x _MacroInstantiation 39 | }, 40 | } 41 | -------------------------------------------------------------------------------- /dev/ljclang_linux_decls.lua.in: -------------------------------------------------------------------------------- 1 | local ffi=require"ffi" 2 | ffi.cdef[[ 3 | @@ -w FunctionDecl -p ^inotify_ ./dev/sys.h 4 | ]] 5 | return { 6 | IN = ffi.new[[struct { 7 | @@ -w MacroDefinition -C -p ^IN_ -s ^IN_ ./dev/sys.h 8 | }]], 9 | MAP = ffi.new[[struct { 10 | @@ -w MacroDefinition -C -p ^MAP_ANON[YMOUS]*$ -s ^MAP_ ./dev/sys.h 11 | }]], 12 | MREMAP = ffi.new[[struct { 13 | @@ -w MacroDefinition -C -p ^MREMAP_FIXED$ -p ^MREMAP_MAYMOVE$ -s ^MREMAP_ -A -D_GNU_SOURCE=1 ./dev/sys.h 14 | }]], 15 | } 16 | -------------------------------------------------------------------------------- /dev/posix.h: -------------------------------------------------------------------------------- 1 | /* This file is used for extracting entities out of system headers. */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | -------------------------------------------------------------------------------- /dev/posix_decls.lua.in: -------------------------------------------------------------------------------- 1 | local ffi=require"ffi" 2 | 3 | local assert = assert 4 | local tonumber = tonumber 5 | 6 | ---------- 7 | 8 | local SIG_DFL = [[ 9 | @@ -m dev.exdecl_macro_def -p ^SIG_DFL$ /usr/include/signal.h 10 | ]] 11 | -- Check that SIG_DFL is #defined as a null pointer cast. 12 | assert(SIG_DFL:match([[^%(%(.*%)0%) 13 | $]])) 14 | 15 | ffi.cdef[[ 16 | struct dirent64; 17 | ]] 18 | 19 | local d_name_offsets = { 20 | @@ -m dev.exdecl_struct_property -A -D_LARGEFILE64_SOURCE=1 -p ^dirent6?4?$ -a offset:name=value -a d_name ./dev/posix.h 21 | } 22 | local dirent64_name_offset 23 | do 24 | local offset = d_name_offsets["struct dirent"] 25 | local offset64 = d_name_offsets["struct dirent64"] 26 | 27 | -- glibc: We avoid using 'struct dirent' since it is defined differently depending on 28 | -- '#ifndef __USE_FILE_OFFSET64'. 29 | local isGlibc = ((offset ~= nil and offset64 ~= nil) and offset <= offset64) 30 | -- musl: off_t is always 64-bit (see FAQ), definition of 'struct dirent' is always the 31 | -- same, but there is no 'dirent64' exposed as actual type, only as #define which our 32 | -- extractdecls path does not see through. It *is* possible to use 'readdir64' from 33 | -- LuaJIT though -- the symbol is present and just redirects to 'readdir', presumably. 34 | local isMusl = (offset64 == nil and offset ~= nil) 35 | assert(isGlibc or isMusl) 36 | 37 | dirent64_name_offset = isGlibc and offset64 or offset 38 | -- Ensure cross-build compatibility wrt this particular binary interface aspect. 39 | -- All the dance above just for that... 40 | assert(dirent64_name_offset == 19) 41 | end 42 | 43 | local uint8_ptr_t = ffi.typeof("const uint8_t *") 44 | local dirent64_ptr_t = ffi.typeof("struct dirent64 *") 45 | 46 | return { 47 | 48 | getDirent64Name = function(dirent) 49 | assert(ffi.istype(dirent64_ptr_t, dirent)) 50 | assert(dirent ~= nil) 51 | local ptr = ffi.cast(uint8_ptr_t, dirent) + dirent64_name_offset 52 | return ffi.string(ptr) 53 | end, 54 | 55 | POLL = ffi.new[[struct { 56 | @@ -w MacroDefinition -C -p ^POLLIN$ -s ^POLL ./dev/sys.h 57 | }]], 58 | -- NOTE: PF -> AF 59 | AF = ffi.new[[struct { 60 | @@ -w MacroDefinition -C -p ^PF_INET$ -s ^PF_ ./dev/sys.h 61 | }]], 62 | CLOCK = ffi.new[[struct { 63 | @@ -w MacroDefinition -C -p ^CLOCK_MONOTONIC$ -s ^CLOCK_ /usr/include/time.h 64 | }]], 65 | E = ffi.new[[struct { 66 | @@ -w MacroDefinition -C -p ^EAGAIN$ -p ^EPIPE$ -p ^ENODEV$ -s ^E /usr/include/errno.h 67 | }]], 68 | F = ffi.new[[struct { 69 | @@ -w MacroDefinition -C -p ^F_GETFL$ -p ^F_SETFL$ -s ^F_ /usr/include/fcntl.h 70 | }]], 71 | MAP = ffi.new[[struct { 72 | @@ -w MacroDefinition -C -p ^MAP_SHARED$ -p ^MAP_PRIVATE$ -p ^MAP_FIXED$ -s ^MAP_ ./dev/posix.h 73 | }]], 74 | O = ffi.new[[struct { 75 | @@ -w MacroDefinition -C -p ^O_RDONLY$ -p ^O_WRONLY$ -p ^O_RDWR$ -p ^O_NONBLOCK$ -s ^O_ /usr/include/fcntl.h 76 | }]], 77 | PROT = ffi.new[[struct { 78 | @@ -w MacroDefinition -C -p ^PROT_READ$ -p ^PROT_WRITE$ -p ^PROT_NONE$ -s ^PROT_ ./dev/posix.h 79 | }]], 80 | _SC = ffi.new[[struct { 81 | @@ -w E+M -C -p ^_SC_PAGESIZE$ -s ^_SC_ /usr/include/unistd.h 82 | }]], 83 | SHUT = ffi.new[[struct { 84 | @@ -w E+M -C -p ^SHUT_RDWR$ -s ^SHUT_ ./dev/sys.h 85 | }]], 86 | SIG = ffi.new[[struct { 87 | @@ -w MacroDefinition -C -p ^SIGINT$ -p ^SIGPIPE$ -s ^SIG /usr/include/signal.h 88 | @@ -w MacroDefinition -C -p ^SIG_BLOCK$ -s ^SIG_ /usr/include/signal.h 89 | }]], 90 | SOCK = ffi.new[[struct { 91 | @@ -w E+M -C -p ^SOCK_STREAM$ -s ^SOCK_ ./dev/sys.h 92 | }]], 93 | 94 | } 95 | -------------------------------------------------------------------------------- /dev/posix_types.lua.in: -------------------------------------------------------------------------------- 1 | local ffi=require"ffi" 2 | 3 | local assert = assert 4 | 5 | ---------- 6 | 7 | ffi.cdef[[ 8 | @@ -w TypedefDecl ./dev/posix.h \ 9 | -p ^_*clockid_t$ -p ^_*time_t$ -p ^_*suseconds_t$ \ 10 | -p ^_*pid_t$ -p ^_*uid_t$ -p ^_*nfds_t$ -p ^_*off_t$ -p ^_*ssize_t$ \ 11 | -p ^_*sa_family_t$ -p ^_*socklen_t$ 12 | 13 | typedef 14 | @@ -m dev.exdecl_surrogate_struct -p ^fd_set$ ./dev/posix.h 15 | fd_set; 16 | 17 | typedef 18 | @@ -m dev.exdecl_surrogate_struct -p ^sigset_t$ ./dev/posix.h 19 | sigset_t; 20 | ]] 21 | 22 | -- Unused, but kept for generation. See POSIX sys/types.h 23 | --[[ 24 | @@ -w TypedefDecl ./dev/posix.h \ 25 | -p ^_*clock_t$ \ 26 | -p ^_*blkcnt_t$ -p ^_*blksize_t$ -p ^_*fsblkcnt_t$ -p ^_*fsfilcnt_t$ -p ^_*ino_t$ \ 27 | -p ^_*id_t$ -p ^_*gid_t$ \ 28 | -p ^_*mode_t$ -p ^_*nlink_t$ 29 | ]] 30 | 31 | -- NOTE: members of time structs have the 'tv_' prefix stripped. 32 | ffi.cdef[[ 33 | struct timeval { 34 | time_t sec; 35 | suseconds_t usec; 36 | }; 37 | 38 | struct timespec { 39 | time_t sec; 40 | long nsec; 41 | }; 42 | 43 | struct pollfd { 44 | int fd; 45 | short events; 46 | short revents; 47 | }; 48 | ]] 49 | 50 | -- TODO: also check glibc vs musl? 51 | local buildFingerprint = ([[ 52 | @@ -m dev.exdecl_system_fingerprint ./dev/sys.h 53 | ]]):sub(1, -2) 54 | -- KEEPINSYNC exdecl_system_fingerprint.lua: 55 | local runFingerprint = ffi.os.."-"..ffi.arch 56 | if (buildFingerprint ~= runFingerprint) then 57 | assert(false, [[ 58 | Platform mismatch: 59 | build=]]..buildFingerprint..[[ but 60 | run=]]..runFingerprint) 61 | end 62 | 63 | -- Check that on our system, the structs we want to expose include *only* the members 64 | -- specified by POSIX. 65 | assert(ffi.sizeof("struct timeval") == 66 | @@ -m dev.exdecl_struct_property -a size -p ^timeval$ ./dev/posix.h 67 | ) 68 | assert(ffi.sizeof("struct timespec") == 69 | @@ -m dev.exdecl_struct_property -a size -p ^timespec$ ./dev/posix.h 70 | ) 71 | assert(ffi.sizeof("struct pollfd") == 72 | @@ -m dev.exdecl_struct_property -a size -p ^pollfd$ ./dev/posix.h 73 | ) 74 | 75 | return {} 76 | -------------------------------------------------------------------------------- /dev/sys.h: -------------------------------------------------------------------------------- 1 | /* This file is used for extracting entities out of system headers. */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | -------------------------------------------------------------------------------- /diagnostics_util.lua: -------------------------------------------------------------------------------- 1 | 2 | local string = require("string") 3 | local table = require("table") 4 | 5 | local format = string.format 6 | 7 | local assert = assert 8 | local ipairs = ipairs 9 | local type = type 10 | local unpack = unpack 11 | 12 | local class = require("class").class 13 | local error_util = require("error_util") 14 | 15 | local check = error_util.check 16 | local checktype = error_util.checktype 17 | 18 | local Col = require("terminal_colors") 19 | local encode_color = Col.encode 20 | 21 | ---------- 22 | 23 | local api = {} 24 | 25 | local function GetColorizeTripleFunc(severityTextColor, colorizeMainText) 26 | return function(pre, tag, post) 27 | return 28 | encode_color(pre, Col.Bold..Col.White).. 29 | encode_color(tag, Col.Bold..severityTextColor).. 30 | (colorizeMainText and encode_color(post, Col.Bold..Col.White) or post) 31 | end 32 | end 33 | 34 | local ColorizeErrorFunc = GetColorizeTripleFunc(Col.Red, true) 35 | local ColorizeWarningFunc = GetColorizeTripleFunc(Col.Purple, true) 36 | local ColorizeNoteFunc = GetColorizeTripleFunc(Col.Black, false) 37 | 38 | local ColorSubstitutions = { 39 | { "(.*)(fatal error: )(.*)", ColorizeErrorFunc }, 40 | { "(.*)(error: )(.*)", ColorizeErrorFunc }, 41 | { "(.*)(warning: )(.*)", ColorizeWarningFunc }, 42 | { "(.*)(note: )(.*)", ColorizeNoteFunc }, 43 | } 44 | 45 | -----===== Diagnostic formatting =====----- 46 | 47 | local function getIndented(indentation, str) 48 | return format("%s%s", string.rep(" ", indentation), str) 49 | end 50 | 51 | local FormattedDiag = class 52 | { 53 | function(useColors, severity) 54 | checktype(useColors, 1, "boolean", 2) 55 | checktype(severity, 2, "string", 2) 56 | 57 | -- self: sequence table of lines constituting the diagnostic 58 | return { 59 | usingColors = useColors, 60 | severity = severity, 61 | } 62 | end, 63 | 64 | addIndentedLine = function(self, indentation, str) 65 | self[#self + 1] = getIndented(indentation, str) 66 | end, 67 | 68 | getSeverity = function(self) 69 | return self.severity 70 | end, 71 | 72 | getString = function(self, keepColorsIfPresent) 73 | checktype(keepColorsIfPresent, 1, "boolean", 2) 74 | 75 | local str = table.concat(self, '\n') 76 | 77 | return 78 | (not self.usingColors) and str or 79 | keepColorsIfPresent and Col.colorize(str) or 80 | Col.strip(str) 81 | end, 82 | } 83 | 84 | -- Special separation characters that are disjoint with our encoding of color codes. 85 | -- (See terminal_colors.lua) 86 | local Sep = { 87 | Diag = { 88 | Value = '\0', 89 | Pattern = '%z', 90 | }, 91 | -- Octet values that cannot be part of a well-formed UTF-8-encoded string. 92 | -- (See ISO/IEC 10646:2017 section 9.2) 93 | Line = '\xFE', 94 | EmptyInfo = '\xFD', 95 | } 96 | 97 | local SpecialCharsPattern = "[%z\xFE\xFD]" 98 | local DiagInfoSeverity = "ignored" 99 | 100 | local function patternFor(sep) 101 | return "([^"..sep.."]+)"..sep 102 | end 103 | 104 | local function FormattedDiagSet_Serialize(self) 105 | local tab = {} 106 | 107 | for _, diag in ipairs(self.diags) do 108 | local innerTab = { diag.severity } 109 | 110 | for _, line in ipairs(diag) do 111 | assert(not line:find(SpecialCharsPattern)) 112 | innerTab[#innerTab + 1] = line 113 | end 114 | 115 | tab[#tab + 1] = table.concat(innerTab, Sep.Line)..Sep.Line 116 | end 117 | 118 | local L = Sep.Line 119 | local info = self.info ~= nil and self.info[1] or Sep.EmptyInfo 120 | 121 | if (self.info ~= nil) then 122 | assert(#self.info == 1) 123 | assert(not self.info[1]:find(SpecialCharsPattern)) 124 | end 125 | 126 | tab[#tab + 1] = DiagInfoSeverity..Sep.Line..info..Sep.Line 127 | 128 | return table.concat(tab, Sep.Diag.Value)..Sep.Diag.Value 129 | end 130 | 131 | local FormattedDiagSet -- "forward-declare" 132 | local InvalidStringMsg = "passed string that is not a formatted diagnostic serialization" 133 | 134 | function api.FormattedDiagSet_Deserialize(diagsStr, useColors) 135 | checktype(diagsStr, 1, "string", 2) 136 | checktype(useColors, 2, "boolean", 2) 137 | 138 | local fDiagSet = FormattedDiagSet(useColors) 139 | 140 | for diagStr in diagsStr:gmatch(patternFor(Sep.Diag.Pattern)) do 141 | local fDiag 142 | for line in diagStr:gmatch(patternFor(Sep.Line)) do 143 | if (fDiag == nil) then 144 | fDiag = FormattedDiag(useColors, line) 145 | else 146 | fDiag[#fDiag + 1] = line 147 | end 148 | end 149 | assert(fDiag ~= nil) 150 | fDiagSet.diags[#fDiagSet.diags + 1] = fDiag 151 | end 152 | 153 | local lastDiag = fDiagSet.diags[#fDiagSet.diags] 154 | fDiagSet.info = (lastDiag[1] ~= Sep.EmptyInfo) and lastDiag or nil 155 | fDiagSet.diags[#fDiagSet.diags] = nil 156 | 157 | if (fDiagSet.info ~= nil) then 158 | local good = (#fDiagSet.info == 1 and not fDiagSet.info[1]:find(Sep.EmptyInfo)) 159 | check(good, InvalidStringMsg..", or INTERNAL ERROR", 2) 160 | else 161 | -- TODO: also have a check? 162 | end 163 | 164 | return fDiagSet 165 | end 166 | 167 | FormattedDiagSet = class 168 | { 169 | function(useColors) 170 | return { 171 | diags = {}, -- list of FormattedDiag objects 172 | info = nil, -- nil or a FormattedDiag with one line 173 | 174 | usingColors = useColors, 175 | } 176 | end, 177 | 178 | isEmpty = function(self) 179 | return (#self:getDiags() == 0 and self:getInfo() == nil) 180 | end, 181 | 182 | getDiags = function(self) 183 | return self.diags 184 | end, 185 | 186 | getInfo = function(self) 187 | return self.info 188 | end, 189 | 190 | newDiag = function(self, severity) 191 | return FormattedDiag(self.usingColors, severity) 192 | end, 193 | 194 | appendDiag = function(self, fDiag) 195 | self.diags[#self.diags + 1] = fDiag 196 | end, 197 | 198 | setInfo = function(self, info) 199 | checktype(info, 1, "string", 2) 200 | 201 | self.info = self:newDiag(DiagInfoSeverity) 202 | self.info:addIndentedLine(0, info) 203 | end, 204 | 205 | getString = function(self, keepColorsIfPresent) 206 | checktype(keepColorsIfPresent, 1, "boolean", 2) 207 | 208 | local fDiags = {} 209 | 210 | for _, fDiag in ipairs(self.diags) do 211 | fDiags[#fDiags + 1] = fDiag:getString(keepColorsIfPresent) 212 | end 213 | 214 | return table.concat(fDiags, '\n\n') .. 215 | (self.info ~= nil and '\n'..self.info:getString(keepColorsIfPresent) or "") 216 | end, 217 | 218 | serialize = FormattedDiagSet_Serialize, 219 | } 220 | 221 | local function FormatDiagnostic(diag, useColors, indentation, 222 | --[[out--]] fDiag) 223 | local text = diag:format() 224 | local printCategory = (indentation == 0) 225 | 226 | if (useColors) then 227 | for i = 1, #ColorSubstitutions do 228 | local matchCount 229 | local subst = ColorSubstitutions[i] 230 | text, matchCount = text:gsub(subst[1], subst[2]) 231 | 232 | if (matchCount > 0) then 233 | break 234 | end 235 | end 236 | end 237 | 238 | local category = diag:category() 239 | 240 | local textWithMaybeCategory = 241 | text .. ((printCategory and #category > 0) and " ["..category.."]" or "") 242 | fDiag:addIndentedLine(indentation, textWithMaybeCategory) 243 | end 244 | 245 | ----- 246 | 247 | local function PrintPrefixDiagnostics(diags, indentation, 248 | --[[out--]] fDiag) 249 | for i = 1, #diags do 250 | local text = diags[i]:spelling() 251 | 252 | if (text:match("^in file included from ")) then 253 | fDiag:addIndentedLine(indentation, "In"..text:sub(3)) 254 | else 255 | return i 256 | end 257 | end 258 | 259 | return #diags + 1 260 | end 261 | 262 | local function PrintDiagsImpl(diags, useColors, allDiags, 263 | startIndex, indentation, currentFDiag) 264 | if (startIndex == nil) then 265 | startIndex = 1 266 | end 267 | if (indentation == nil) then 268 | indentation = 0 269 | end 270 | 271 | local formattedDiags = FormattedDiagSet(useColors) 272 | 273 | for i = startIndex, #diags do 274 | local diag = diags[i] 275 | local fDiag = (currentFDiag ~= nil) and 276 | currentFDiag or 277 | formattedDiags:newDiag(diag:severity()) 278 | 279 | local childDiags = diag:childDiagnostics() 280 | 281 | local innerStartIndex = PrintPrefixDiagnostics(childDiags, indentation, fDiag) 282 | FormatDiagnostic(diag, useColors, indentation, fDiag) 283 | 284 | -- Recurse. We expect only at most two levels in total (but do not check for that). 285 | PrintDiagsImpl(diag:childDiagnostics(), useColors, allDiags, 286 | innerStartIndex, indentation + 2, fDiag) 287 | 288 | local isFatal = (diag:severity() == "fatal") 289 | local isError = isFatal or diag:severity() == "error" 290 | local omitFollowing = not allDiags and (isFatal or (isError and diag:category() == "Parse Issue")) 291 | 292 | if (omitFollowing) then 293 | assert(indentation == 0) 294 | 295 | if (i < #diags) then 296 | local info = format("%s: omitting %d following diagnostics.", 297 | useColors and encode_color("NOTE", Col.Bold..Col.Blue) or "NOTE", 298 | #diags - i) 299 | formattedDiags:setInfo(info) 300 | end 301 | end 302 | 303 | formattedDiags:appendDiag(fDiag) 304 | 305 | if (omitFollowing) then 306 | break 307 | end 308 | end 309 | 310 | return formattedDiags 311 | end 312 | 313 | api.FormattedDiagSet = FormattedDiagSet 314 | 315 | function api.GetDiags(diags, useColors, allDiags) 316 | checktype(diags, 1, "table", 2) 317 | checktype(useColors, 2, "boolean", 2) 318 | checktype(allDiags, 3, "boolean", 2) 319 | 320 | return PrintDiagsImpl(diags, useColors, allDiags) 321 | end 322 | 323 | -- Done! 324 | return api 325 | -------------------------------------------------------------------------------- /docker/Dockerfile.in: -------------------------------------------------------------------------------- 1 | FROM @ARCH@/@DISTRO_IMAGE@ 2 | 3 | LABEL maintainer="Philipp Kutin " 4 | 5 | #@if-foreign:COPY /qemu-@qemu-suffix@ /usr/local/bin 6 | 7 | @DO_update_packages@ 8 | 9 | # Common: 10 | RUN @install@ git make mg 11 | # For building LuaJIT: 12 | RUN @install@ gcc $pkg_libc_dev 13 | 14 | RUN @adduser@ user 15 | 16 | USER user 17 | WORKDIR /home/user 18 | 19 | ########## Check out, build and install LuaJIT 2.1 ########## 20 | 21 | # Make sure that a fast-forward re-clones the repo: 22 | RUN echo @LUAJIT_GIT_HASH@ 23 | RUN git clone https://github.com/LuaJIT/LuaJIT.git \ 24 | --single-branch --branch=v2.1 --shallow-since=2020-08-01 \ 25 | ./luajit-2.1 26 | RUN (cd luajit-2.1 && git checkout @LUAJIT_GIT_HASH@) 27 | 28 | WORKDIR /home/user/luajit-2.1 29 | RUN make -j4 30 | RUN sha256sum src/luajit | grep -q @LUAJIT_SHA256@ 31 | USER root 32 | RUN make install 33 | RUN ln -s /usr/local/bin/luajit-2.1.0-beta3 /usr/local/bin/luajit 34 | USER user 35 | 36 | WORKDIR /home/user 37 | 38 | ########## LJClang cloned from the build context ########## 39 | 40 | # Install prerequisites 41 | USER root 42 | RUN @install@ bash 43 | RUN @install@ $pkg_libclang_dev 44 | 45 | # For LJClang tests: 46 | RUN @install@ $pkg_luarocks 47 | # NOTE: CFLAGS=-I/usr/local/include/luajit-2.1 before 'luarocks-5.1' does not work: 48 | RUN @install@ $pkg_liblua51_dev 49 | # 50 | USER user 51 | RUN @luarocks@ --local install busted 52 | 53 | COPY /ljclang.git/ ./ljclang.git/ 54 | # Create a non-bare repository: 55 | RUN git clone ./ljclang.git ./ljclang 56 | WORKDIR ljclang 57 | 58 | # Do not install llvm-dev, do not rely on llvm-config. (See 'sed -i' invocations below.) 59 | # On Alpine Linux, clang-c/Index.h is in /usr/include and libclang.so is in /usr/lib. 60 | # Using 'llvm-config' of package 'llvm-dev' would wrongly point us to /usr/lib/llvm9. 61 | # This has not been re-checked for Alpine 3.12 (which ships with LLVM 10) but in any case, 62 | # it is nice to avoid the big package if it's not really necessary. 63 | # 64 | # TODO in ljclang: 65 | # - remove altogether in favor of an alternative detection scheme that addresses all 66 | # now supported distributions (Ubuntu, Raspberry Pi OS 32-bit, Alpine Linux)? 67 | ENV LLVM_CONFIG=true 68 | 69 | WORKDIR /home/user/ljclang 70 | # NOTE: The binary directory is relevant only for the install targets. 71 | RUN sed -i 's|llvm_version :=.*|llvm_version := @llvm_version@|' ./Makefile 72 | RUN sed -i 's|bindir :=.*|bindir := /does-not-exist-and-not-relevant-here|' ./Makefile 73 | RUN sed -i 's|incdir :=.*|incdir := @llvm_incdir@|' ./Makefile 74 | RUN sed -i 's|libdir :=.*|libdir := @llvm_libdir@|' ./Makefile 75 | 76 | RUN make apps 77 | 78 | # Run the LJClang tests. 79 | WORKDIR /home/user/ljclang 80 | RUN LJCLANG_TESTS_NO_CXX_STDLIB=1 TEST_LOOP_COUNT=20 make test-loop 81 | 82 | WORKDIR /home/user 83 | 84 | ########## 85 | 86 | ENTRYPOINT ["/bin/@SHELL@"] 87 | -------------------------------------------------------------------------------- /docker/Makefile: -------------------------------------------------------------------------------- 1 | 2 | # From the 'GNU make' manual: 3 | # "recipes will be invoked as if the shell had been passed the '-e' flag: 4 | # the first failing command in a recipe will cause the recipe to fail 5 | # immediately." 6 | .POSIX: 7 | 8 | ########## VARIABLES ########## 9 | 10 | ## User 11 | 12 | # For target 'run', to be used interactively, e.g. 13 | # $ make run ljc --(completion)--> 14 | # $ make run ljclang-dev-arm32v7/alpine --(edit)--> 15 | # $ make run IMAGE=ljclang-dev-arm32v7/alpine <--(invoke this) 16 | IMAGE ?= 17 | 18 | # Use '--no-cache'? 19 | NOCACHE ?= 20 | 21 | # If non-empty, will build images up to and including the LuaJIT build step, tagged 22 | # 'LJ_SHA' instead of the Git commit hash of this repository. Running a container 23 | # with an appropriate '--entrypoint', the SHA-256 hashes of all LuaJIT builds can 24 | # be obtained. (This is especially useful in combination with building 25 | # foreign-architecture images, see below.) 26 | LJ_SHA_ONLY ?= 27 | 28 | ## Protected: for derivatives only 29 | 30 | # Prefix for files named '*..arg', each required to contain lines 31 | # completely matching BUILD_ARGS_LINE_EREGEX below, after having comments 32 | # (whitespace followed by '#' until the end of a line) stripped. 33 | BUILD_ARGS_FILE_PREFIX ?= 34 | SUFFIX_DOCKERFILE_IN ?= 35 | OVERRIDE_IMAGE_NAME ?= 36 | OVERRIDE_IMAGE_TAG ?= 37 | ADDITIONAL_CONTEXT_REPO ?= 38 | 39 | ifneq ($(SUFFIX_DOCKERFILE_IN),) 40 | ifneq ($(LJ_SHA_ONLY),) 41 | $(error When passing SUFFIX_DOCKERFILE_IN, must not pass LJ_SHA_ONLY) 42 | endif 43 | 44 | ifeq ($(OVERRIDE_IMAGE_NAME),) 45 | $(error When passing SUFFIX_DOCKERFILE_IN, must pass OVERRIDE_IMAGE_NAME) 46 | endif 47 | ifeq ($(OVERRIDE_IMAGE_TAG),) 48 | $(error When passing SUFFIX_DOCKERFILE_IN, must pass OVERRIDE_IMAGE_TAG) 49 | endif 50 | endif 51 | 52 | ## Private 53 | 54 | # NOTE: '\x23' is '#'. GNU Make 4.2.1 of Raspberry Pi OS has issues with the latter. 55 | BUILD_ARGS_LINE_SEDCMD := 's/[[:space:]]*\x23.*//g' 56 | BUILD_ARGS_LINE_EREGEX := '--build-arg [A-Za-z_-][A-Za-z_0-9-]*=[A-Za-z_0-9-]*' 57 | 58 | # For '(eval $(call ...))'. 59 | # Argument: 60 | # Effects: if $(BUILD_ARGS_FILE_PREFIX) is non-empty, 61 | # 1. Sets variable 'build_args_file_'. 62 | # 2. Reads contents of the so named file into variable 'build_args_', 63 | # sanitizing its contents so that they are suitable as args to 'docker build'. 64 | # (Otherwise, the two variables are set to the empty string.) 65 | define do_build_args = 66 | build_args_file_$(1) := $$(if $$(BUILD_ARGS_FILE_PREFIX),$$(BUILD_ARGS_FILE_PREFIX).$(1).arg) 67 | build_args_$(1) := $$(if $$(BUILD_ARGS_FILE_PREFIX),$$(shell \ 68 | sed $(BUILD_ARGS_LINE_SEDCMD) < $$(build_args_file_$(1)) | \ 69 | grep -E --line-regexp -- $(BUILD_ARGS_LINE_EREGEX))) 70 | endef 71 | 72 | $(eval $(call do_build_args,alpine)) 73 | $(eval $(call do_build_args,debian)) 74 | $(eval $(call do_build_args,ubuntu)) 75 | $(eval $(call do_build_args,ubuntu-bionic)) 76 | 77 | commit := $(shell git rev-parse --short=12 HEAD) 78 | tmproot := /dev/shm 79 | 80 | native_machine := $(shell uname -m) 81 | # The architecture part of Docker image names: 82 | native_arch = null 83 | 84 | ifeq ($(native_machine),armv7l) 85 | native_arch := arm32v7 86 | endif 87 | ifeq ($(native_machine),aarch64) 88 | native_arch := arm64v8 89 | endif 90 | ifeq ($(native_machine),x86_64) 91 | native_arch := amd64 92 | endif 93 | 94 | equals = $(findstring ~$(1)~,~$(2)~) 95 | qemuSuffixFromArch = $(strip \ 96 | $(if $(call equals,arm32v7,$(1)),arm,\ 97 | $(if $(call equals,arm64v8,$(1)),aarch64,\ 98 | $(if $(call equals,amd64,$(1)),x86_64)))) 99 | 100 | # To build the foreign-architecture images, static builds of qemu- are required 101 | # on the host system. Exception: Armv7 can be built and run on an Armv8 host directly. 102 | # 103 | # Example arguments to qemu's 'configure': 104 | # --target-list=aarch64-linux-user,arm-linux-user --disable-system --static 105 | # 106 | # These binaries need to be registered with binfmt_misc (again, on the host). Example: 107 | # 108 | # $ /scripts/qemu-binfmt-conf.sh --debian --exportdir 109 | # $ (Remove all but the necessary templates...) 110 | # $ sudo mkdir /usr/local/share/binfmts && sudo cp /qemu-* /usr/local/share/binfmts 111 | # For each available architecture : 112 | # $ sudo update-binfmts --importdir /usr/local/share/binfmts --import qemu- 113 | # (See '/scripts/qemu-binfmt-conf.sh --help'.) 114 | # (See '/proc/sys/fs/binfmt_misc/qemu-*' which are then created.) 115 | qemu_prefix := /usr/local/bin/qemu- 116 | haveQemu = $(realpath $(qemu_prefix)$(1)) 117 | # Return: empty (false) or non-empty (true) 118 | canRunLowerVersion_ = $(and $(call equals,arm64v8,$(native_arch)),$(call equals,arm32v7,$(1))) 119 | canRunNatively_ = $(or $(call equals,$(1),$(native_arch)),$(call canRunLowerVersion_,$(1))) 120 | canRunEmulated_ = $(and $(call qemuSuffixFromArch,$(1)),$(call haveQemu,$(call qemuSuffixFromArch,$(1)))) 121 | # Return: empty (false), 1 (can run natively) or 2 (can run emulated) 122 | canRunNatively = $(if $(call canRunNatively_,$(1)),1) 123 | canRunEmulated = $(if $(call canRunEmulated_,$(1)),2) 124 | canRun = $(or $(call canRunNatively,$(1)),$(call canRunEmulated,$(1))) 125 | 126 | # For target '_get-tmpdir' invoked recursively from this Makefile. 127 | ARCH ?= null 128 | DISTRO ?= null 129 | # Check out a specific LuaJIT commit: 130 | # "Merge branch 'master' into v2.1" after 131 | # "Prevent CSE of a REF_BASE operand across IR_RETF." 132 | LJ_GIT_HASH := a91d0d9d3bba1a936669cfac3244509a0f2ac0e3 133 | LJ_SHA256 ?= null 134 | # 135 | qemu_suffix := 136 | ifeq ($(call canRun,$(ARCH)),2) 137 | qemu_suffix := $(call qemuSuffixFromArch,$(ARCH)) 138 | endif 139 | 140 | ########## RULES ########## 141 | 142 | .SILENT: _get-exists _get-tmpdir 143 | .PHONY: _get-exists _get-tmpdir all run clean-all-temp 144 | 145 | image_name_prefix := $(or $(OVERRIDE_IMAGE_NAME),ljclang-dev) 146 | image_tag := $(or $(OVERRIDE_IMAGE_TAG),$(if $(LJ_SHA_ONLY),LJ_SHA,$(commit))) 147 | 148 | all: 149 | @echo 'Usage:' 150 | @echo ' $$ make ljclang-dev-/' 151 | @echo ' Build a Docker image. It has the given name and is tagged' 152 | @echo ' with the short-commit-SHA of *this* Git repository (ljclang).' 153 | @echo ' $$ make run IMAGE=ljclang-dev-/' 154 | @echo ' Run the specified Docker image.' 155 | @echo '' 156 | @echo " Passing NOCACHE=1 will add '--no-cache' to the 'docker build' invocation(s)." 157 | @echo '' 158 | @echo ' $$ LJ_SHA_ONLY=1 make ljclang-dev-/' 159 | @echo " Build Docker images tagged 'LJ_SHA' only up to LuaJIT build." 160 | @echo ' $$ make run-lj-sha-only' 161 | @echo " Run all LJ_SHA-tagged images, printing SHA256 of each 'luajit'." 162 | 163 | _get-exists: 164 | docker image inspect "$(image_name_prefix)-$(ARCH)/$(DISTRO):$(image_tag)" > /dev/null 2>&1 && \ 165 | echo "yes" || echo "no" 166 | 167 | ifneq ($(qemu_suffix),) 168 | _get-tmpdir-in1 = echo "INFO: Building image using '$(qemu_prefix)$(qemu_suffix)'." 1>&2 169 | _get-tmpdir-in2 = cp '$(qemu_prefix)$(qemu_suffix)' "$$tmpdir/context" 170 | _get-tmpdir-in4 = sedcmds="$$sedcmds; s/^\#@if-foreign://g; s/@qemu-suffix@/$(qemu_suffix)/g" 171 | else 172 | _get-tmpdir-in1 = true 173 | _get-tmpdir-in2 = true 174 | _get-tmpdir-in4 = true 175 | endif 176 | 177 | ifeq ($(ARCH)~$(DISTRO),arm32v7~debian) 178 | _get-tmpdir-in5 = sedcmds="$$sedcmds; s/^\#@if-ljrM://g" 179 | else 180 | _get-tmpdir-in5 = true 181 | endif 182 | 183 | Dockerfile_in := Dockerfile.in 184 | 185 | ifneq ($(LJ_SHA_ONLY),) 186 | _get-tmpdir-in3 = true 187 | _tmp-lnum := $(shell grep -m1 -n '^RUN make -j4' $(Dockerfile_in) | sed 's/:.*//g') 188 | ifeq ($(_tmp-lnum),) 189 | $(error INTERNAL: Failed obtaining line number of LuaJIT build invocation) 190 | endif 191 | _cat_Dockerfile_in := head -q -n "$(_tmp-lnum)" $(Dockerfile_in) 192 | else 193 | _get-tmpdir-in3 = git clone --bare ../ "$$tmpdir/context/ljclang.git" > /dev/null 2>&1 194 | _cat_Dockerfile_in := cat $(Dockerfile_in) $(SUFFIX_DOCKERFILE_IN) 195 | endif 196 | 197 | ifneq ($(ADDITIONAL_CONTEXT_REPO),) 198 | _clone_additional_repo := \ 199 | git clone --bare '$(ADDITIONAL_CONTEXT_REPO)' "$$tmpdir/context/additional.git" > /dev/null 2>&1 200 | else 201 | _clone_additional_repo := true 202 | endif 203 | 204 | _get-tmpdir: 205 | test -d $(tmproot) 206 | test -f vars.$(DISTRO).sed 207 | test `echo $(tmproot)/ljclang-tmp-* | wc -w` -lt 10 || \ 208 | echo "INFO: Consider running 'make clean-all-temp' after the current build." 1>&2 209 | $(_get-tmpdir-in1) 210 | tmpdir=`mktemp -d $(tmproot)/ljclang-tmp-XXXXXXXX` && \ 211 | mkdir "$$tmpdir/context" && \ 212 | $(_get-tmpdir-in2) && \ 213 | $(_get-tmpdir-in3) && \ 214 | $(_clone_additional_repo) && \ 215 | test `git rev-parse --short=12 HEAD` = "$(commit)" && \ 216 | test -n "$(ARCH)" && \ 217 | sedcmds='s/@ARCH@/$(ARCH)/; s/@LUAJIT_GIT_HASH@/$(LJ_GIT_HASH)/; s/@LUAJIT_SHA256@/$(LJ_SHA256)/' && \ 218 | $(_get-tmpdir-in4) && \ 219 | $(_get-tmpdir-in5) && \ 220 | $(_cat_Dockerfile_in) | sed -e "$$sedcmds" -f vars.$(DISTRO).sed > "$$tmpdir/Dockerfile" && \ 221 | echo "$$tmpdir" 222 | 223 | replace_NATIVE = $(subst NATIVE,$(native_arch),$(1)) 224 | image_to_run := $(call replace_NATIVE,$(IMAGE)) 225 | 226 | define get_sha_only_images := 227 | docker images -q --filter=reference='ljclang-dev-*/*:LJ_SHA' 228 | endef 229 | 230 | define get_sha_only_images_repo-tag := 231 | $(get_sha_only_images) --format "{{.Repository}}:{{.Tag}}" 232 | endef 233 | 234 | define clean_sha_only_images := 235 | images=`$(get_sha_only_images)` && (test -z "$$images" || docker rmi $$images) 236 | endef 237 | 238 | run: 239 | @test -n "$(image_to_run)" 240 | docker run -it --rm "$(image_to_run):$(image_tag)" 241 | 242 | run-lj-sha-only: 243 | @images=`$(get_sha_only_images_repo-tag)` && \ 244 | (test -n "$$images" || (echo "ERROR: no LJ_SHA-tagged images." 1>&2 && false)) && \ 245 | for i in $$images; do echo "$$i" && docker run --rm --entrypoint sha256sum "$$i" src/luajit 2>&1 | \ 246 | grep -v "WARNING: The requested image's platform "; done 247 | 248 | # For manual invocation when no other build is running: 249 | clean-all-temp: 250 | rm -rf $(tmproot)/ljclang-tmp-* 251 | $(clean_sha_only_images) 252 | 253 | ## Rules for the actual Docker images 254 | 255 | canRunArm32 := $(call canRun,arm32v7) 256 | canRunArm64 := $(call canRun,arm64v8) 257 | canRunAmd64 := $(call canRun,amd64) 258 | 259 | ifeq ($(canRunArm32)$(canRunArm64)$(canRunAmd64),) 260 | $(error Cannot build any Docker image: unsupported architecture) 261 | else 262 | .PHONY: ljclang-dev-ALL/ALL 263 | endif 264 | 265 | ifneq ($(canRunArm32),) 266 | .PHONY: ljclang-dev-arm32v7/alpine ljclang-dev-arm32v7/debian ljclang-dev-arm32v7/ubuntu 267 | .PHONY: ljclang-dev-arm32v7/ubuntu-bionic 268 | .PHONY: ljclang-dev-arm32v7/ALL 269 | endif 270 | ifneq ($(canRunArm64),) 271 | .PHONY: ljclang-dev-arm64v8/alpine ljclang-dev-arm64v8/debian ljclang-dev-arm64v8/ubuntu 272 | .PHONY: ljclang-dev-arm64v8/ALL 273 | endif 274 | ifneq ($(canRunAmd64),) 275 | .PHONY: ljclang-dev-amd64/alpine ljclang-dev-amd64/debian ljclang-dev-amd64/ubuntu 276 | .PHONY: ljclang-dev-amd64/ALL 277 | endif 278 | 279 | .PHONY: ljclang-dev-NATIVE/alpine ljclang-dev-NATIVE/debian ljclang-dev-NATIVE/ubuntu 280 | ljclang-dev-NATIVE/alpine: ljclang-dev-$(native_arch)/alpine 281 | ljclang-dev-NATIVE/debian: ljclang-dev-$(native_arch)/debian 282 | ljclang-dev-NATIVE/ubuntu: ljclang-dev-$(native_arch)/ubuntu 283 | 284 | .PHONY: ljclang-dev-NATIVE/ALL 285 | ljclang-dev-NATIVE/ALL: ljclang-dev-NATIVE/alpine ljclang-dev-NATIVE/debian ljclang-dev-NATIVE/ubuntu 286 | 287 | _image_name = $(subst ljclang-dev,$(image_name_prefix),$@) 288 | _opts := $(if $(NOCACHE),--no-cache) 289 | # 290 | define _docker_build = 291 | DOCKER_BUILDKIT=1 docker build $(_opts) $(call build_args_$(1)) --tag $(_image_name):$(image_tag) \ 292 | -f "$$tmpdir/Dockerfile" "$$tmpdir/context" 293 | endef 294 | 295 | # We want to detect whether we are invoked with potentially-parallel execution 296 | # (-j/--jobs) and if so, redirect output from 'docker build' to a file. 297 | # 298 | # Unfortunately, this logic cannot be pulled into Makefile code: the thing to keep 299 | # in mind is that the special MAKEFLAGS variable evaluates to a (normalized) 300 | # representation of passed command line arguments *only in recipe context*. 301 | # 302 | # NOTE: this handles both the long form '--jobs' (because GNU Make maps that to the 303 | # short form for the internal variable) as well as a plain option without argument. 304 | define invoke_docker_build 305 | if expr match '$(MAKEFLAGS)' '[[:space:]]-j[0-9]*\>' > /dev/null; then \ 306 | $(call _docker_build,$(1)) > $$tmpdir/docker-build.log 2>&1; else $(call _docker_build,$(1)); \ 307 | fi 308 | endef 309 | 310 | define check_or_make_image = 311 | test x`$(MAKE) --silent _get-exists ARCH=$(1) DISTRO=$(2)` = x'yes' || ( \ 312 | tmpdir=`$(MAKE) --silent _get-tmpdir ARCH=$(1) DISTRO=$(2) LJ_SHA256=$(3)` && \ 313 | $(call invoke_docker_build,$(2))) 314 | endef 315 | 316 | ifneq ($(canRunArm32),) 317 | ljclang-dev-arm32v7/ALL: ljclang-dev-arm32v7/alpine ljclang-dev-arm32v7/debian ljclang-dev-arm32v7/ubuntu ljclang-dev-arm32v7/ubuntu-bionic 318 | ljclang-dev-ALL/ALL: ljclang-dev-arm32v7/ALL 319 | 320 | ljclang-dev-arm32v7/alpine: 321 | +$(call check_or_make_image,arm32v7,alpine,1bdc6620c81ced7e45b9e9bc6ee7d3d0dbe57a7daa08006318b1442116b5aa94) 322 | 323 | ljclang-dev-arm32v7/debian: 324 | +$(call check_or_make_image,arm32v7,debian,5e8f6a8dc1e75478b796ef1e8378a168cd8c5935e93f4af4d59af3d6c373c7b2) 325 | 326 | # NOTE: when building the Docker on an RPi OS (32-bit) host, can get 327 | # "GPG error (...) invalid signature" / "repository (...) not signed". 328 | # See: 329 | # https://askubuntu.com/questions/1263284/apt-update-throws-signature-error-in-ubuntu-20-04-container-on-arm 330 | # https://github.com/moby/moby/issues/40734 331 | ljclang-dev-arm32v7/ubuntu: 332 | +$(call check_or_make_image,arm32v7,ubuntu,986a80caf79e2839a78c693d5c6f3db52e120f3398b9d50cecfb8890560c055c) 333 | # 334 | # Docker image of Ubuntu 18.04 as a workaround for the above issue. 335 | ljclang-dev-arm32v7/ubuntu-bionic: 336 | +$(call check_or_make_image,arm32v7,ubuntu-bionic,7b9faea3204fc08b7896f730dcf20a26ddb2d254e2a15d1ba47a0e8d370836e0) 337 | endif 338 | 339 | ifneq ($(canRunArm64),) 340 | ljclang-dev-arm64v8/ALL: ljclang-dev-arm64v8/alpine ljclang-dev-arm64v8/debian ljclang-dev-arm64v8/ubuntu 341 | ljclang-dev-ALL/ALL: ljclang-dev-arm64v8/ALL 342 | 343 | ljclang-dev-arm64v8/alpine: 344 | +$(call check_or_make_image,arm64v8,alpine,2338dbcf2decd457ebc6a68d6b7ebcf636c864c3e5f0f65039afda062815de7a) 345 | 346 | ljclang-dev-arm64v8/debian: 347 | +$(call check_or_make_image,arm64v8,debian,725ebe683e5041a75a7a540f7772fc1b0ecf62f70229d27eefa4d8df8a3ea2df) 348 | 349 | ljclang-dev-arm64v8/ubuntu: 350 | +$(call check_or_make_image,arm64v8,ubuntu,f9e17e8a94ed863fe02a2eaf6908ece89d0ff7271b7cf5d11bc16b726c1f2190) 351 | endif 352 | 353 | ifneq ($(canRunAmd64),) 354 | ljclang-dev-amd64/ALL: ljclang-dev-amd64/alpine ljclang-dev-amd64/debian ljclang-dev-amd64/ubuntu 355 | ljclang-dev-ALL/ALL: ljclang-dev-amd64/ALL 356 | 357 | ljclang-dev-amd64/alpine: 358 | +$(call check_or_make_image,amd64,alpine,e497fdca4101def3ca64136d8d080423a871563da2a0179aaf6985275059d26a) 359 | 360 | ljclang-dev-amd64/debian: 361 | +$(call check_or_make_image,amd64,debian,e3dd74c4961e8127e8fc4f73ba0660087db9f498b2fe8fd232e5cf1ebdf0c847) 362 | 363 | ljclang-dev-amd64/ubuntu: 364 | +$(call check_or_make_image,amd64,ubuntu,7a1ac895515c4bc3a0362b9ad88befede8d9079b3eb56cdc78984b2ff1b8cd44) 365 | endif 366 | -------------------------------------------------------------------------------- /docker/vars.alpine.sed: -------------------------------------------------------------------------------- 1 | s/@DISTRO_IMAGE@/alpine:3.15.0/g 2 | s/@DO_update_packages@//g 3 | s/@adduser@/adduser -D/g 4 | s/@install@/apk add/g 5 | s/@luarocks@/luarocks-5.1/g 6 | s/$pkg_libc_dev/libc-dev/g 7 | s/$pkg_libclang_dev/clang-dev/g 8 | s/$pkg_luarocks/luarocks5.1/g 9 | s/$pkg_liblua51_dev/lua5.1-dev/g 10 | s/@llvm_version@/12.0.1/g 11 | s|@llvm_incdir@|/usr/include|g 12 | s|@llvm_libdir@|/does-not-exist-and-not-relevant-here|g 13 | s/@SHELL@/sh/g 14 | -------------------------------------------------------------------------------- /docker/vars.debian.sed: -------------------------------------------------------------------------------- 1 | s/@DISTRO_IMAGE@/debian:bullseye-20211220/g 2 | s!@DO_update_packages@!RUN apt update > /tmp/apt-update.log \&\& grep -v -q '^\(E:\|Err:\|W:\)' /tmp/apt-update.log!g 3 | s/@adduser@/adduser --disabled-password/g 4 | s/@install@/DEBIAN_FRONTEND=noninteractive apt install -y/g 5 | s/@luarocks@/luarocks/g 6 | s/$pkg_libc_dev/libc6-dev/g 7 | s/$pkg_libclang_dev/libclang-11-dev/g 8 | s/$pkg_luarocks/luarocks/g 9 | s/$pkg_liblua51_dev/liblua5.1-0-dev/g 10 | s/@llvm_version@/11.0.1/g 11 | s|@llvm_incdir@|/usr/lib/llvm-11/include|g 12 | s|@llvm_libdir@|/usr/lib/llvm-11/lib|g 13 | s/@SHELL@/bash/g 14 | -------------------------------------------------------------------------------- /docker/vars.ubuntu-bionic.sed: -------------------------------------------------------------------------------- 1 | s/@DISTRO_IMAGE@/ubuntu:bionic-20210930/g 2 | s!@DO_update_packages@!RUN apt update > /tmp/apt-update.log \&\& grep -v -q '^\(E:\|Err:\|W:\)' /tmp/apt-update.log!g 3 | s/@adduser@/adduser --disabled-password/g 4 | s/@install@/DEBIAN_FRONTEND=noninteractive apt install -y/g 5 | s/@luarocks@/luarocks/g 6 | s/$pkg_libc_dev/libc6-dev/g 7 | s/$pkg_libclang_dev/libclang-10-dev/g 8 | s/$pkg_linux_headers//g; # for ljremarkable; /usr/include/linux/fb.h comes with 'linux-libc-dev' 9 | s/$pkg_luarocks/luarocks/g 10 | s/$pkg_liblua51_dev/liblua5.1-0-dev/g 11 | s/@llvm_version@/10.0.0/g 12 | s|@llvm_incdir@|/usr/lib/llvm-10/include|g 13 | s|@llvm_libdir@|/usr/lib/llvm-10/lib|g 14 | s/@SHELL@/bash/g 15 | -------------------------------------------------------------------------------- /docker/vars.ubuntu.sed: -------------------------------------------------------------------------------- 1 | s/@DISTRO_IMAGE@/ubuntu:focal-20211006/g 2 | s!@DO_update_packages@!RUN apt update > /tmp/apt-update.log \&\& grep -v -q '^\(E:\|Err:\|W:\)' /tmp/apt-update.log!g 3 | s/@adduser@/adduser --disabled-password/g 4 | s/@install@/DEBIAN_FRONTEND=noninteractive apt install -y/g 5 | s/@luarocks@/luarocks/g 6 | s/$pkg_libc_dev/libc6-dev/g 7 | s/$pkg_libclang_dev/libclang-10-dev/g 8 | s/$pkg_luarocks/luarocks/g 9 | s/$pkg_liblua51_dev/liblua5.1-0-dev/g 10 | s/@llvm_version@/10.0.0/g 11 | s|@llvm_incdir@|/usr/lib/llvm-10/include|g 12 | s|@llvm_libdir@|/usr/lib/llvm-10/lib|g 13 | s/@SHELL@/bash/g 14 | -------------------------------------------------------------------------------- /error_util.lua: -------------------------------------------------------------------------------- 1 | local assert = assert 2 | local error = error 3 | local type = type 4 | 5 | ---------- 6 | 7 | local api = {} 8 | 9 | -- TODO: default 'level' to 2 for the following two functions? 10 | 11 | -- Wrap 'error' in assert-like call to write type checks in one line instead of three. 12 | function api.check(pred, msg, level) 13 | if (not pred) then 14 | error(msg, level+1) 15 | end 16 | end 17 | 18 | function api.checktype(object, argIdx, typename, level) 19 | -- NOTE: type(nil) returns nil. We disallow passing nil for `typename` however: 20 | -- the resulting check would be "is 's type anything other than nil" rather than 21 | -- the more likely intended "is 's type nil (in other words, is it nil?)". 22 | assert(type(argIdx) == "number") 23 | assert(type(typename) == "string") 24 | 25 | if (type(object) ~= typename) then 26 | local msg = "argument #"..argIdx.." must be a "..typename.." (got "..type(object)..")" 27 | error(msg, level+1) 28 | end 29 | end 30 | 31 | -- Done! 32 | return api 33 | -------------------------------------------------------------------------------- /extractdecls.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env luajit 2 | 3 | local arg = arg 4 | 5 | local require = require 6 | 7 | local ffi = require("ffi") 8 | 9 | local io = require("io") 10 | local os = require("os") 11 | local math = require("math") 12 | local string = require("string") 13 | local table = require("table") 14 | 15 | local hacks = require("hacks") 16 | local util = require("util") 17 | 18 | local assert = assert 19 | local getfenv = getfenv 20 | local ipairs = ipairs 21 | local loadstring = loadstring 22 | local pcall = pcall 23 | local print = print 24 | local setfenv = setfenv 25 | local tonumber = tonumber 26 | local type = type 27 | 28 | local format = string.format 29 | 30 | ---------- 31 | 32 | local function printf(fmt, ...) 33 | print(format(fmt, ...)) 34 | end 35 | 36 | local function errprintf(fmt, ...) 37 | io.stderr:write(format(fmt, ...).."\n") 38 | end 39 | 40 | -- Are we using mkapp.lua? 41 | -- KEEPINSYNC with 'quiet' short option letter. 42 | local IsMakingApp = (arg[1] == "-Q" and arg[2] == nil) 43 | 44 | local function usage(hline) 45 | if (hline) then 46 | errprintf("ERROR: %s", hline) 47 | end 48 | print([==[ 49 | Usage: 50 | extractdecls.lua [our options...] [-- [Clang command line args ...]] 51 | 52 | (Our options may also come after the file name.) 53 | ]==]) 54 | 55 | if (not hline) then 56 | print[[ 57 | Exits with a non-zero code if there were errors or no match, or if filter 58 | patterns (-p) were provided and not all of them produced matches. 59 | 60 | Options: 61 | -e (enums only) 62 | -p [-p ] ... (logically OR'd) 63 | -x [-x ] ... (logically OR'd) 64 | -s 65 | -1 66 | -2 67 | -A (same as if specified as positional arg) 68 | -C: print lines like 69 | static const int membname = 123; (enums/macros only) 70 | -R: reverse mapping, only if one-to-one. Print lines like 71 | [123] = \"membname\"; (enums/macros only) 72 | -m : name of a Lua module to 'require()' which should return a 73 | function taking the LJClang cursor as a first argument and a table of strings collected 74 | from the -a option instances as the second argument. In the context of the call to 75 | 'require()' and the module function, the functions 'check' and 'printf' are available. 76 | The function 'printf' must not be called at module load time. 77 | Incompatible with -1, -2, -C, -R, -f and -w. 78 | -a [-a ] ...: arguments passed to the 79 | as a table. 80 | Can only be used with -m. 81 | -f : user-provided body for formatting function (enums/macros only) 82 | Arguments to that function are named 83 | * 'k' (enum constant / macro name) 84 | * 'v' (its numeric value) 85 | * 'enumName' (the name in 'enum ', or the empty string) 86 | * 'enumIntTypeName' (the name of the underlying integer type of an enum) 87 | * 'enumPrefixLength' (the length of the common prefix of all names; enums only) 88 | Also, the following is provided: 89 | * 'f' as a shorthand for 'string.format' 90 | Must return a formatted line. 91 | Example: 92 | "return f('%s = %s%s,', k, k:find('KEY_') and '65536+' or '', v)" 93 | Incompatible with -C, -R or -f. 94 | -Q: be quiet 95 | -w: extract what? Can be 96 | E+M, EnumConstantDecl (default), MacroDefinition, TypedefDecl, FunctionDecl 97 | ]] 98 | -- ^ KEEPINSYNC with IsValidWhat: 99 | -- TODO: allow more general list in '-w'? 100 | end 101 | 102 | if (not IsMakingApp) then 103 | os.exit(1) 104 | end 105 | 106 | -- Continue to allow mkapp.lua collect the remaining 'require's. 107 | end 108 | 109 | local EnumOrMacro = "E+M" 110 | -- KEEPINSYNC with usage text above: 111 | local IsValidWhat = { 112 | [EnumOrMacro] = true, 113 | EnumConstantDecl = true, 114 | MacroDefinition = true, 115 | TypedefDecl = true, 116 | FunctionDecl = true, 117 | } 118 | 119 | local parsecmdline = require("parsecmdline_pk") 120 | 121 | -- Meta-information about options, see parsecmdline_pk. 122 | local opt_meta = { e=true, p=1, A=1, x=1, s=true, C=false, R=false, Q=false, 123 | ['1']=true, ['2']=true, w=true, f=true, m=true, a=1 } 124 | 125 | local opts, args = parsecmdline.getopts(opt_meta, arg, usage) 126 | 127 | local additionalArgs = opts.A 128 | local enumNameFilterPattern = opts.e 129 | local filterPatterns = opts.p 130 | local excludePatterns = opts.x 131 | local stripPattern = opts.s 132 | local printConstInt = opts.C 133 | local reverse = opts.R 134 | local quiet = opts.Q 135 | local haveWhat = (opts.w ~= nil) 136 | local what = opts.w or "EnumConstantDecl" 137 | local fmtfuncCode = opts.f 138 | local moduleName = opts.m 139 | local moduleArgs = opts.a 140 | 141 | local extractEnum = (moduleName == nil and what == "EnumConstantDecl" or what == EnumOrMacro) 142 | local extractMacro = (what:find("^Macro") or what == EnumOrMacro) 143 | 144 | local prefixString = opts['1'] 145 | local suffixString = opts['2'] 146 | 147 | if (#args == 0) then 148 | usage() 149 | elseif (not IsValidWhat[what]) then 150 | usage("Invalid argument to '-w'.") 151 | end 152 | 153 | -- Late load to allow printing the help text with a plain invocation. 154 | local cl = require("ljclang") 155 | 156 | if (IsMakingApp) then 157 | -- KEEPINSYNC: make sure that there are no require() calls below us! 158 | os.exit(0) 159 | end 160 | 161 | if (not extractEnum and enumNameFilterPattern ~= nil) then 162 | usage("Option -e only available for enum extraction.") 163 | end 164 | 165 | if (not (extractEnum or extractMacro) and (printConstInt or reverse)) then 166 | usage("Options -C and -R only available for enum or macro extraction.") 167 | end 168 | 169 | local fmtfunc 170 | local moduleFunc 171 | 172 | local matchCount = 0 173 | local filterPatternMatchCount = {} 174 | local currentFilterPatternIdx 175 | 176 | local function printfMatch(fmt, ...) 177 | printf(fmt, ...) 178 | matchCount = matchCount + 1 179 | 180 | local fpi = currentFilterPatternIdx 181 | -- NOTE: this assumes that modules will not print at load time: 182 | assert((fpi ~= nil) == (#filterPatterns > 0)) 183 | 184 | if (fpi ~= nil) then 185 | assert(fpi <= #filterPatterns) 186 | local fmc = filterPatternMatchCount[fpi] 187 | filterPatternMatchCount[fpi] = (fmc and fmc or 0) + 1 188 | end 189 | end 190 | 191 | if (fmtfuncCode and moduleName) then 192 | usage("Options -f and -m are mutually exclusive.") 193 | elseif (fmtfuncCode) then 194 | if (not (extractEnum or extractMacro)) then 195 | usage("Option -f only available for enum or macro extraction.") 196 | end 197 | 198 | if (printConstInt or reverse) then 199 | usage("Option -f is incompatible with -C or -R.") 200 | end 201 | 202 | local func, errmsg = loadstring([[ 203 | local f=string.format 204 | return function(k, v, enumName, enumIntTypeName, enumPrefixLength) 205 | ]]..fmtfuncCode..[[ 206 | end 207 | ]]) 208 | if (func == nil) then 209 | io.stderr:write("Error loading '-f' string: "..errmsg.."\n") 210 | os.exit(1) 211 | end 212 | 213 | fmtfunc = func() 214 | elseif (moduleName) then 215 | -- KEEPINSYNC with usage help: "Incompatible with -1, -2, -C, -R, -f and -w." 216 | if (prefixString or suffixString or printConstInt or reverse or fmtfuncCode or haveWhat) then 217 | usage("Passed option incompatible with -m.") 218 | end 219 | 220 | -- Restrict the environment because we are taking arguments from the command line. 221 | -- 222 | -- Notes: 223 | -- - It would be preferable to set the environment on a temporary function, but if 224 | -- we attempt to do that, 'printf' is already nil at require() time. 225 | -- - This way, we also disallow calling 'printf()' at require() time, but unintendedly: 226 | -- it appears that *from within the C function implementing Lua print()*, there is an 227 | -- access to the global environment which then is not fulfilled. 228 | local env = getfenv(0) 229 | setfenv(0, { 230 | check=assert, concat=table.concat, printf=printfMatch, type=type, 231 | ffi = { os = ffi.os, arch = ffi.arch, abi = ffi.abi }, 232 | }) 233 | moduleFunc = require(moduleName) 234 | setfenv(0, env) 235 | 236 | if (type(moduleFunc) ~= "function") then 237 | io.stderr:write("ERROR: module specified with -m must return a function\n") 238 | os.exit(1) 239 | end 240 | 241 | setfenv(moduleFunc, {}) 242 | elseif (#moduleArgs > 0) then 243 | usage("Module arguments passed with -a are only allowed with -m.") 244 | end 245 | 246 | local tuOptions = (extractMacro or moduleFunc) and {"DetailedPreprocessingRecord"} or nil 247 | 248 | local filename = args[1] 249 | do 250 | local f, msg = io.open(filename) 251 | if (f == nil) then 252 | errprintf("ERROR: Failed opening %s", msg) 253 | os.exit(1) 254 | end 255 | f:close() 256 | end 257 | 258 | for _, additionalArg in ipairs(additionalArgs) do 259 | args[#args + 1] = additionalArg 260 | end 261 | 262 | hacks.addSystemInclude(args, "c") 263 | 264 | local index = cl.createIndex(true, false) 265 | local tu, errorCode = index:parse("", args, tuOptions) 266 | 267 | if (tu == nil) then 268 | errprintf("ERROR: Failed parsing %s (%s)", filename, errorCode) 269 | os.exit(1) 270 | end 271 | 272 | local haveErrors = false 273 | 274 | if (not quiet) then 275 | local diags = tu:diagnosticSet() 276 | 277 | for i=1,#diags do 278 | local d = diags[i] 279 | local severity = d:severity() 280 | haveErrors = haveErrors or (severity == "error" or severity == "fatal") 281 | 282 | io.stderr:write(d:format().."\n") 283 | end 284 | end 285 | 286 | -- Mapping of enum value to its name for -R. 287 | local enumname = {} 288 | -- Mapping of running index to enum value for -R. 289 | local enumseq = {} 290 | 291 | local function checkexclude(name) 292 | for i=1,#excludePatterns do 293 | if (name:find(excludePatterns[i])) then 294 | return true 295 | end 296 | end 297 | end 298 | 299 | -- Get definition string of #define macro definition cursor. 300 | local function getDefStr(cur) 301 | local tokens = cur:_tokens() 302 | return table.concat(tokens, " ", 2, #tokens) 303 | end 304 | 305 | local function getCommonPrefixLengthOfEnums(enumDeclCur) 306 | local commonPrefix = util.getCommonPrefix( 307 | function(_, cur) return cur:displayName() end, 308 | nil, ipairs(enumDeclCur:children())) 309 | 310 | if (commonPrefix ~= nil) then 311 | return #commonPrefix 312 | end 313 | end 314 | 315 | -- The in 'enum ' if available, or the empty string: 316 | local currentEnumName 317 | 318 | local currentEnumIntTypeName 319 | local currentEnumPrefixLength 320 | 321 | -- NOTE: cl.ChildVisitResult is not available when run from 'make bootstrap', so 322 | -- use string enum constant names. 323 | 324 | local function wantedCursorKind(cur) 325 | if (what == EnumOrMacro) then 326 | return 327 | (cur:haskind("EnumConstantDecl") and "EnumConstantDecl") or 328 | (cur:haskind("MacroDefinition") and "MacroDefinition") or 329 | nil 330 | end 331 | 332 | return (cur:haskind(what)) and what or nil 333 | end 334 | 335 | local function matchesFilterPattern(name) 336 | currentFilterPatternIdx = nil 337 | 338 | for i, filterPattern in ipairs(filterPatterns) do 339 | if (name:find(filterPattern)) then 340 | currentFilterPatternIdx = i 341 | return true 342 | end 343 | end 344 | 345 | return false 346 | end 347 | 348 | local visitor = cl.regCursorVisitor( 349 | function(cur, parent) 350 | if (extractEnum) then 351 | if (cur:haskind("EnumDecl")) then 352 | if (enumNameFilterPattern ~= nil and not cur:name():find(enumNameFilterPattern)) then 353 | return 'CXChildVisit_Continue' 354 | else 355 | currentEnumName = cur:name() 356 | currentEnumIntTypeName = cur:enumIntegerType():name() 357 | currentEnumPrefixLength = getCommonPrefixLengthOfEnums(cur) 358 | return 'CXChildVisit_Recurse' 359 | end 360 | end 361 | end 362 | 363 | local isUserDefined = (moduleFunc ~= nil) 364 | local kind = isUserDefined and "*anything*" or wantedCursorKind(cur) 365 | 366 | if (kind ~= nil) then 367 | local name = cur:displayName() 368 | 369 | if (#filterPatterns == 0 or matchesFilterPattern(name)) then 370 | if (not checkexclude(name)) then 371 | local ourname = stripPattern and name:gsub(stripPattern, "") or name 372 | 373 | if (isUserDefined) then 374 | moduleFunc(cur, moduleArgs) 375 | elseif (extractEnum or extractMacro) then 376 | local isEnum = (kind == "EnumConstantDecl") 377 | local val = isEnum and cur:enumval() or getDefStr(cur) 378 | 379 | -- Notes: 380 | -- - tonumber(val) == nil can only happen with #defines that are not 381 | -- like a literal number. 382 | -- - We only use tonumber() for the check here. To convert, we output 383 | -- the token as it appears in the source code. This way, numbers 384 | -- written in octal are correctly preserved. 385 | if (isEnum or tonumber(val) ~= nil) then 386 | if (fmtfunc) then 387 | local str = fmtfunc(ourname, val, 388 | currentEnumName, 389 | currentEnumIntTypeName, 390 | currentEnumPrefixLength) 391 | printfMatch("%s", str) 392 | elseif (reverse) then 393 | if (enumname[val]) then 394 | printf("Error: enumeration value %d not unique: %s and %s", 395 | val, enumname[val], ourname) 396 | os.exit(2) 397 | end 398 | enumname[val] = ourname 399 | enumseq[#enumseq+1] = val 400 | elseif (printConstInt) then 401 | printfMatch("static const int %s = %s;", ourname, val) 402 | else 403 | printfMatch("%s = %s,", ourname, val) 404 | end 405 | end 406 | elseif (what=="FunctionDecl") then 407 | -- Function declaration 408 | local rettype = cur:resultType() 409 | if (not checkexclude(rettype:name())) then 410 | printfMatch("%s %s;", rettype, ourname) 411 | end 412 | elseif (what=="TypedefDecl") then 413 | -- Type definition 414 | local utype = cur:typedefType() 415 | if (not checkexclude(utype:name())) then 416 | printfMatch("typedef %s %s;", utype, ourname) 417 | end 418 | else 419 | -- Anything else 420 | printfMatch("%s", ourname) 421 | end 422 | end 423 | end 424 | end 425 | 426 | return isUserDefined and 'CXChildVisit_Recurse' or 'CXChildVisit_Continue' 427 | end) 428 | 429 | if (prefixString) then 430 | print(prefixString) 431 | end 432 | 433 | tu:cursor():children(visitor) 434 | 435 | if (reverse) then 436 | for i=1,#enumseq do 437 | local val = enumseq[i] 438 | local name = enumname[val] 439 | printfMatch("[%d] = %q;", val, name) 440 | end 441 | end 442 | 443 | if (suffixString) then 444 | print(suffixString) 445 | end 446 | 447 | if (haveErrors or matchCount == 0) then 448 | -- There were errors or not matches. 449 | os.exit(1) 450 | elseif (#filterPatterns > 0) then 451 | -- Not every filter pattern had a match. 452 | -- NOTE: cannot use #filterPatternMatchCount as the table may be sparse 453 | -- (which would invoke nondeterminism of the '#' operator). 454 | for i = 1, #filterPatterns do 455 | local fmc = filterPatternMatchCount[i] 456 | assert(fmc == nil or fmc > 0) 457 | if (fmc == nil) then 458 | os.exit(2) 459 | end 460 | end 461 | end 462 | -------------------------------------------------------------------------------- /extractrange.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env luajit 2 | 3 | local io = require("io") 4 | local os = require("os") 5 | 6 | local fileName = arg[1] 7 | local startPattern = arg[2] 8 | local stopPattern = arg[3] 9 | 10 | if (fileName == nil or startPattern == nil or stopPattern == nil) then 11 | print("Usage: "..arg[0].." ") 12 | print("") 13 | print("Extracts all lines from the first one matching ") 14 | print("up to and including the first one matching .") 15 | print("") 16 | print("Exit codes:") 17 | print(" 0: at least one line was extracted") 18 | print(" 1: no lines were extracted") 19 | print(" 2: failed opening file") 20 | os.exit(1) 21 | end 22 | 23 | local function main() 24 | local f, errMsg = io.open(fileName) 25 | if (f == nil) then 26 | print("Error opening file: "..errMsg) 27 | os.exit(1) 28 | end 29 | 30 | local extracting = false 31 | 32 | while (true) do 33 | local line = f:read("*l") 34 | if (line == nil) then 35 | break 36 | end 37 | 38 | if (not extracting and line:match(startPattern)) then 39 | extracting = true 40 | end 41 | 42 | if (extracting) then 43 | print(line) 44 | 45 | if (line:match(stopPattern)) then 46 | return 0 47 | end 48 | end 49 | end 50 | 51 | return 1 52 | end 53 | 54 | return main() 55 | -------------------------------------------------------------------------------- /hacks.lua: -------------------------------------------------------------------------------- 1 | 2 | local error_util = require("error_util") 3 | local llvm_libdir_include = require("llvm_libdir_include")[1] 4 | 5 | local checktype = error_util.checktype 6 | local check = error_util.check 7 | 8 | -- 9 | 10 | local api = {} 11 | 12 | function api.addSystemInclude(compilerArgs, language) 13 | checktype(compilerArgs, 1, "table", 2) 14 | checktype(language, 2, "string", 2) 15 | 16 | check(language == "c", "argument #2 must be 'c'", 2) 17 | 18 | compilerArgs[#compilerArgs + 1] = "-isystem" 19 | -- Fixes extractdecls.lua on : 20 | compilerArgs[#compilerArgs + 1] = llvm_libdir_include 21 | end 22 | 23 | -- Done! 24 | return api 25 | -------------------------------------------------------------------------------- /inclusion_graph.lua: -------------------------------------------------------------------------------- 1 | 2 | local error_util = require("error_util") 3 | local class = require("class").class 4 | local math = require("math") 5 | local string = require("string") 6 | local table = require("table") 7 | 8 | local check = error_util.check 9 | local checktype = error_util.checktype 10 | 11 | local assert = assert 12 | local ipairs = ipairs 13 | 14 | ---------- 15 | 16 | local api = {} 17 | 18 | -- Helper function to manipulate "Set+Vector" tables: tables which have 19 | -- { = , ... } to be used like sets and 20 | -- { [1] = , ... } (uniq'd) to be used like vectors. 21 | -- 22 | -- Note: this cannot be a 'class' since the keys are strings. (So, a member function name 23 | -- may conflict with a key.) 24 | local function SVTableAddOrGet(tab, key, value) 25 | checktype(tab, 1, "table", 2) 26 | checktype(key, 1, "string", 2) 27 | check(value ~= nil, "argument #3 must be non-nil", 2) 28 | 29 | if (tab[key] == nil) then 30 | tab[#tab + 1] = key 31 | tab[key] = value 32 | end 33 | 34 | return tab[key] 35 | end 36 | 37 | -- Merely for marking "Set+Vector tables". 38 | local function SVTable() 39 | return {} 40 | end 41 | 42 | local Node = class 43 | { 44 | function(key) 45 | checktype(key, 1, "string", 2) 46 | 47 | return { 48 | key = key, 49 | edgeTo = SVTable(), 50 | } 51 | end, 52 | 53 | addEdgeTo = function(self, key, value) 54 | SVTableAddOrGet(self.edgeTo, key, value) 55 | end, 56 | 57 | getKey = function(self) 58 | return self.key 59 | end, 60 | 61 | getEdgeCount = function(self) 62 | return #self.edgeTo 63 | end, 64 | 65 | iEdges = function(self) 66 | return ipairs(self.edgeTo) 67 | end 68 | } 69 | 70 | -- Private InclusionGraph functions 71 | 72 | local function addOrGetNode(self, filename) 73 | return SVTableAddOrGet(self._nodes, filename, Node(filename)) 74 | end 75 | 76 | local function dot_quote(str) 77 | checktype(str, 1, "string", 2) 78 | -- Graphviz docs ("The DOT language") say: 79 | -- In quoted strings in DOT, the only escaped character is double-quote ("). 80 | -- (...) 81 | -- As another aid for readability, dot allows double-quoted strings to span multiple physical lines using the standard C 82 | -- convention of a backslash immediately preceding a newline character^2. 83 | return str:gsub('"', '\\"'):gsub('\n', '\\\n') 84 | end 85 | 86 | local function quote(str) 87 | return '"'..dot_quote(str)..'"' 88 | end 89 | 90 | -- Public API 91 | 92 | local function SerializeGraph(self) 93 | local tab = {} 94 | 95 | for _, filename in self:iFileNames() do 96 | for _, otherFileName in self:getNode(filename):iEdges() do 97 | -- TODO: could store 'filename' only once and have a count of edges. 98 | 99 | assert(not (filename:find('%z') or otherFileName:find('%z'))) 100 | tab[#tab + 1] = filename..'\0'..otherFileName 101 | end 102 | end 103 | 104 | return table.concat(tab, '\0\0')..'\0\0' 105 | end 106 | 107 | local InclusionGraph -- "forward-declare" 108 | local InvalidStringMsg = "passed string that is not a graph serialization" 109 | 110 | api.Deserialize = function(graphStr) 111 | checktype(graphStr, 1, "string", 2) 112 | check(#graphStr >= 2, "argument #1 must have length of at least two") 113 | check(graphStr:sub(-2) == "\0\0", InvalidStringMsg, 2) 114 | 115 | local graph = InclusionGraph() 116 | 117 | for filename, otherFileName in graphStr:gmatch("([^%z]+)%z([^%z]+)") do 118 | graph:addInclusion(filename, otherFileName) 119 | end 120 | 121 | local totalEdgeCount = 0 122 | local doubleZeroCount = 0 123 | 124 | for _, filename in graph:iFileNames() do 125 | totalEdgeCount = totalEdgeCount + graph:getNode(filename):getEdgeCount() 126 | end 127 | 128 | for _ in graphStr:gmatch("%z%z") do 129 | doubleZeroCount = doubleZeroCount + 1 130 | end 131 | 132 | check(totalEdgeCount == doubleZeroCount - ((totalEdgeCount == 0) and 1 or 0), 133 | InvalidStringMsg..", or INTERNAL ERROR", 2) 134 | 135 | return graph 136 | end 137 | 138 | InclusionGraph = class 139 | { 140 | function() 141 | return { 142 | _nodes = SVTable() 143 | } 144 | end, 145 | 146 | -- Edge in the graph will point from a to b. 147 | -- Interpretation is up to the user. 148 | addInclusion = function(self, aFile, bFile) 149 | checktype(aFile, 1, "string", 2) 150 | checktype(bFile, 2, "string", 2) 151 | 152 | check(not aFile:find('%z'), "argument #1 must not contain NUL bytes", 2) 153 | check(not bFile:find('%z'), "argument #2 must not contain NUL bytes", 2) 154 | 155 | local aNode = addOrGetNode(self, aFile) 156 | local bNode = addOrGetNode(self, bFile) 157 | 158 | aNode:addEdgeTo(bFile, bNode) 159 | end, 160 | 161 | getNodeCount = function(self) 162 | return #self._nodes 163 | end, 164 | 165 | getNode = function(self, filename) 166 | return self._nodes[filename] 167 | end, 168 | 169 | iFileNames = function(self) 170 | return ipairs(self._nodes) 171 | end, 172 | 173 | merge = function(self, other) 174 | for _, filename in other:iFileNames() do 175 | for _, otherFileName in other:getNode(filename):iEdges() do 176 | self:addInclusion(filename, otherFileName) 177 | end 178 | end 179 | end, 180 | 181 | serialize = SerializeGraph, 182 | 183 | printAsGraphvizDot = function(self, title, reverse, commonPrefix, edgeCountLimit, printf) 184 | checktype(title, 1, "string", 2) 185 | reverse = (reverse ~= nil) and reverse or false 186 | checktype(reverse, 2, "boolean", 2) 187 | checktype(commonPrefix, 3, "string") 188 | edgeCountLimit = (edgeCountLimit ~= nil) and edgeCountLimit or math.huge 189 | checktype(edgeCountLimit, 4, "number", 2) 190 | checktype(printf, 5, "function", 2) 191 | 192 | local strip = function(fn) 193 | return (fn:sub(1, #commonPrefix) == commonPrefix) and 194 | fn:sub(#commonPrefix + 1) or 195 | fn 196 | end 197 | 198 | printf("strict digraph %s {", quote(title)) 199 | printf("rankdir=LR") 200 | 201 | local qs = function(fn) 202 | return quote(strip(fn)) 203 | end 204 | 205 | -- Nodes. 206 | for i, filename in self:iFileNames() do 207 | printf('%s [shape=box];', qs(filename)) 208 | end 209 | 210 | printf('') 211 | 212 | -- Edges. 213 | for _, filename in self:iFileNames() do 214 | local node = self:getNode(filename) 215 | local edgeCount = node:getEdgeCount() 216 | 217 | if (edgeCount > edgeCountLimit) then 218 | assert(not reverse) 219 | local placeholderNodeName = string.format( 220 | '"(%d edges from %s)"', edgeCount, dot_quote(strip(filename))) 221 | printf('%s;', placeholderNodeName) 222 | printf('%s -> %s;', qs(filename), placeholderNodeName) 223 | else 224 | for _, filename2 in node:iEdges() do 225 | local left = (not reverse) and filename or filename2 226 | local right = (not reverse) and filename2 or filename 227 | printf('%s -> %s;', qs(left), qs(right)) 228 | end 229 | end 230 | end 231 | 232 | printf("}") 233 | end 234 | } 235 | 236 | api.InclusionGraph = InclusionGraph 237 | 238 | -- Done! 239 | return api 240 | -------------------------------------------------------------------------------- /inotify.lua: -------------------------------------------------------------------------------- 1 | local ffi = require("ffi") 2 | local C = ffi.C 3 | 4 | local IN = require("ljclang_linux_decls").IN 5 | 6 | local class = require("class").class 7 | local check = require("error_util").check 8 | local posix = require("posix") 9 | 10 | local assert = assert 11 | local error = error 12 | local tonumber = tonumber 13 | local tostring = tostring 14 | local type = type 15 | 16 | ---------- 17 | 18 | ffi.cdef[[ 19 | struct inotify_event { 20 | int wd; 21 | uint32_t mask; 22 | uint32_t cookie; 23 | 24 | uint32_t len; 25 | // char name[]; 26 | }; 27 | ]] 28 | 29 | local api = { IN=IN } 30 | 31 | local getErrnoString = posix.getErrnoString 32 | 33 | local inotify_event_t = ffi.typeof("struct inotify_event") 34 | local sizeof_inotify_event_t = tonumber(ffi.sizeof(inotify_event_t)) 35 | local MaxEventsInBatch = 128 36 | local EventBatch = ffi.typeof("$ [$]", inotify_event_t, MaxEventsInBatch) 37 | 38 | api.init = class 39 | { 40 | function(flags) 41 | check(flags == nil or type(flags) == "number", 42 | " must be nil or a number", 2) 43 | 44 | local fd = (flags == nil) and C.inotify_init() or C.inotify_init1(flags) 45 | 46 | if (fd == -1) then 47 | local funcname = (flags == nil) and "inotify_init" or "inotify_init1" 48 | error(funcname.."() failed: "..getErrnoString()) 49 | end 50 | 51 | return { 52 | fd = posix.Fd(fd) 53 | } 54 | end, 55 | --[[ 56 | -- TODO: implement 57 | __gc = function(self) 58 | self:close() 59 | end, 60 | --]] 61 | getRawFd = function(self) 62 | check(self.fd.fd ~= -1, "must call before closing", 2) 63 | return self.fd.fd 64 | end, 65 | 66 | close = function(self) 67 | if (self.fd.fd ~= -1) then 68 | self.fd:close() 69 | end 70 | end, 71 | 72 | add_watch = function(self, pathname, mask) 73 | check(type(pathname) == "string", " must be a string", 2) 74 | check(type(mask) == "number", " must be a number", 2) 75 | 76 | local wd = C.inotify_add_watch(self.fd.fd, pathname, mask) 77 | 78 | if (wd == -1) then 79 | error("inotify_add_watch() on '"..pathname.."' failed: "..getErrnoString()) 80 | end 81 | 82 | assert(wd >= 0) 83 | return wd 84 | end, 85 | 86 | waitForEvents = function(self) 87 | local events, bytesRead = self.fd:readInto(EventBatch(), true) 88 | assert(bytesRead % sizeof_inotify_event_t == 0) 89 | local eventCount = tonumber(bytesRead) / sizeof_inotify_event_t 90 | 91 | local tab = {} 92 | 93 | for i = 1, eventCount do 94 | local event = events[i - 1] 95 | assert(event.len == 0) 96 | tab[i] = event 97 | end 98 | 99 | return tab 100 | end 101 | } 102 | 103 | -- Done! 104 | return api 105 | -------------------------------------------------------------------------------- /ljclang_extracted_enums.lua: -------------------------------------------------------------------------------- 1 | local ffi=require"ffi" 2 | return { 3 | ErrorCode = ffi.new[[struct{ 4 | static const int Success = 0; 5 | static const int Failure = 1; 6 | static const int Crashed = 2; 7 | static const int InvalidArguments = 3; 8 | static const int ASTReadError = 4; 9 | }]], 10 | SaveError = ffi.new[[struct{ 11 | static const int None = 0; 12 | static const int Unknown = 1; 13 | static const int TranslationErrors = 2; 14 | static const int InvalidTU = 3; 15 | }]], 16 | DiagnosticSeverity = ffi.new[[struct{ 17 | static const int Ignored = 0; 18 | static const int Note = 1; 19 | static const int Warning = 2; 20 | static const int Error = 3; 21 | static const int Fatal = 4; 22 | }]], 23 | ChildVisitResult = ffi.new[[struct{ 24 | static const int Break = 0; 25 | static const int Continue = 1; 26 | static const int Recurse = 2; 27 | }]], 28 | -- NOTE: this mixes the constants of the two enums typedef'd as CXIdxEntityKind and 29 | -- CXIdxEntityCXXTemplateKind. 30 | IdxEntity = ffi.new[[struct{ 31 | static const int Unexposed = 0; 32 | static const int Typedef = 1; 33 | static const int Function = 2; 34 | static const int Variable = 3; 35 | static const int Field = 4; 36 | static const int EnumConstant = 5; 37 | static const int Enum = 13; 38 | static const int Struct = 14; 39 | static const int Union = 15; 40 | static const int CXXClass = 16; 41 | static const int CXXNamespace = 17; 42 | static const int CXXNamespaceAlias = 18; 43 | static const int CXXStaticVariable = 19; 44 | static const int CXXStaticMethod = 20; 45 | static const int CXXInstanceMethod = 21; 46 | static const int CXXConstructor = 22; 47 | static const int CXXDestructor = 23; 48 | static const int CXXConversionFunction = 24; 49 | static const int CXXTypeAlias = 25; 50 | static const int CXXInterface = 26; 51 | static const int NonTemplate = 0; 52 | static const int Template = 1; 53 | static const int TemplatePartialSpecialization = 2; 54 | static const int TemplateSpecialization = 3; 55 | }]], 56 | -- NOTE [ANONYMOUS_ENUM_WITH_TYPEDEF]: the enum type is anonymous here, but we are lucky 57 | -- because the prefix of the enum constant names is unique to this particular enum type. 58 | -- TODO: teach extractdecls to filter by the name of an immediate typedef. 59 | IdxEntityLang = ffi.new[[struct{ 60 | static const int None = 0; 61 | static const int C = 1; 62 | static const int ObjC = 2; 63 | static const int CXX = 3; 64 | static const int Swift = 4; 65 | }]], 66 | IndexOpt = ffi.new[[struct{ 67 | static const int None = 0; 68 | static const int SuppressRedundantRefs = 1; 69 | static const int IndexFunctionLocalSymbols = 2; 70 | static const int IndexImplicitTemplateInstantiations = 4; 71 | static const int SuppressWarnings = 8; 72 | static const int SkipParsedBodiesInSession = 16; 73 | }]], 74 | RefQualifierKind = ffi.new[[struct{ 75 | static const int None = 0; 76 | static const int LValue = 1; 77 | static const int RValue = 2; 78 | }]], 79 | -- NOTE ANONYMOUS_ENUM_WITH_TYPEDEF: 80 | SymbolRole = ffi.new[[struct{ 81 | static const int None = 0; 82 | static const int Declaration = 1; 83 | static const int Definition = 2; 84 | static const int Reference = 4; 85 | static const int Read = 8; 86 | static const int Write = 16; 87 | static const int Call = 32; 88 | static const int Dynamic = 64; 89 | static const int AddressOf = 128; 90 | static const int Implicit = 256; 91 | }]], 92 | CursorKindName = { 93 | [1] = "UnexposedDecl"; 94 | [2] = "StructDecl"; 95 | [3] = "UnionDecl"; 96 | [4] = "ClassDecl"; 97 | [5] = "EnumDecl"; 98 | [6] = "FieldDecl"; 99 | [7] = "EnumConstantDecl"; 100 | [8] = "FunctionDecl"; 101 | [9] = "VarDecl"; 102 | [10] = "ParmDecl"; 103 | [11] = "ObjCInterfaceDecl"; 104 | [12] = "ObjCCategoryDecl"; 105 | [13] = "ObjCProtocolDecl"; 106 | [14] = "ObjCPropertyDecl"; 107 | [15] = "ObjCIvarDecl"; 108 | [16] = "ObjCInstanceMethodDecl"; 109 | [17] = "ObjCClassMethodDecl"; 110 | [18] = "ObjCImplementationDecl"; 111 | [19] = "ObjCCategoryImplDecl"; 112 | [20] = "TypedefDecl"; 113 | [21] = "CXXMethod"; 114 | [22] = "Namespace"; 115 | [23] = "LinkageSpec"; 116 | [24] = "Constructor"; 117 | [25] = "Destructor"; 118 | [26] = "ConversionFunction"; 119 | [27] = "TemplateTypeParameter"; 120 | [28] = "NonTypeTemplateParameter"; 121 | [29] = "TemplateTemplateParameter"; 122 | [30] = "FunctionTemplate"; 123 | [31] = "ClassTemplate"; 124 | [32] = "ClassTemplatePartialSpecialization"; 125 | [33] = "NamespaceAlias"; 126 | [34] = "UsingDirective"; 127 | [35] = "UsingDeclaration"; 128 | [36] = "TypeAliasDecl"; 129 | [37] = "ObjCSynthesizeDecl"; 130 | [38] = "ObjCDynamicDecl"; 131 | [39] = "CXXAccessSpecifier"; 132 | [40] = "ObjCSuperClassRef"; 133 | [41] = "ObjCProtocolRef"; 134 | [42] = "ObjCClassRef"; 135 | [43] = "TypeRef"; 136 | [44] = "CXXBaseSpecifier"; 137 | [45] = "TemplateRef"; 138 | [46] = "NamespaceRef"; 139 | [47] = "MemberRef"; 140 | [48] = "LabelRef"; 141 | [49] = "OverloadedDeclRef"; 142 | [50] = "VariableRef"; 143 | [70] = "InvalidFile"; 144 | [71] = "NoDeclFound"; 145 | [72] = "NotImplemented"; 146 | [73] = "InvalidCode"; 147 | [100] = "UnexposedExpr"; 148 | [101] = "DeclRefExpr"; 149 | [102] = "MemberRefExpr"; 150 | [103] = "CallExpr"; 151 | [104] = "ObjCMessageExpr"; 152 | [105] = "BlockExpr"; 153 | [106] = "IntegerLiteral"; 154 | [107] = "FloatingLiteral"; 155 | [108] = "ImaginaryLiteral"; 156 | [109] = "StringLiteral"; 157 | [110] = "CharacterLiteral"; 158 | [111] = "ParenExpr"; 159 | [112] = "UnaryOperator"; 160 | [113] = "ArraySubscriptExpr"; 161 | [114] = "BinaryOperator"; 162 | [115] = "CompoundAssignOperator"; 163 | [116] = "ConditionalOperator"; 164 | [117] = "CStyleCastExpr"; 165 | [118] = "CompoundLiteralExpr"; 166 | [119] = "InitListExpr"; 167 | [120] = "AddrLabelExpr"; 168 | [121] = "StmtExpr"; 169 | [122] = "GenericSelectionExpr"; 170 | [123] = "GNUNullExpr"; 171 | [124] = "CXXStaticCastExpr"; 172 | [125] = "CXXDynamicCastExpr"; 173 | [126] = "CXXReinterpretCastExpr"; 174 | [127] = "CXXConstCastExpr"; 175 | [128] = "CXXFunctionalCastExpr"; 176 | [129] = "CXXTypeidExpr"; 177 | [130] = "CXXBoolLiteralExpr"; 178 | [131] = "CXXNullPtrLiteralExpr"; 179 | [132] = "CXXThisExpr"; 180 | [133] = "CXXThrowExpr"; 181 | [134] = "CXXNewExpr"; 182 | [135] = "CXXDeleteExpr"; 183 | [136] = "UnaryExpr"; 184 | [137] = "ObjCStringLiteral"; 185 | [138] = "ObjCEncodeExpr"; 186 | [139] = "ObjCSelectorExpr"; 187 | [140] = "ObjCProtocolExpr"; 188 | [141] = "ObjCBridgedCastExpr"; 189 | [142] = "PackExpansionExpr"; 190 | [143] = "SizeOfPackExpr"; 191 | [144] = "LambdaExpr"; 192 | [145] = "ObjCBoolLiteralExpr"; 193 | [146] = "ObjCSelfExpr"; 194 | [147] = "OMPArraySectionExpr"; 195 | [148] = "ObjCAvailabilityCheckExpr"; 196 | [149] = "FixedPointLiteral"; 197 | [150] = "OMPArrayShapingExpr"; 198 | [151] = "OMPIteratorExpr"; 199 | [152] = "CXXAddrspaceCastExpr"; 200 | [200] = "UnexposedStmt"; 201 | [201] = "LabelStmt"; 202 | [202] = "CompoundStmt"; 203 | [203] = "CaseStmt"; 204 | [204] = "DefaultStmt"; 205 | [205] = "IfStmt"; 206 | [206] = "SwitchStmt"; 207 | [207] = "WhileStmt"; 208 | [208] = "DoStmt"; 209 | [209] = "ForStmt"; 210 | [210] = "GotoStmt"; 211 | [211] = "IndirectGotoStmt"; 212 | [212] = "ContinueStmt"; 213 | [213] = "BreakStmt"; 214 | [214] = "ReturnStmt"; 215 | [215] = "AsmStmt"; 216 | [216] = "ObjCAtTryStmt"; 217 | [217] = "ObjCAtCatchStmt"; 218 | [218] = "ObjCAtFinallyStmt"; 219 | [219] = "ObjCAtThrowStmt"; 220 | [220] = "ObjCAtSynchronizedStmt"; 221 | [221] = "ObjCAutoreleasePoolStmt"; 222 | [222] = "ObjCForCollectionStmt"; 223 | [223] = "CXXCatchStmt"; 224 | [224] = "CXXTryStmt"; 225 | [225] = "CXXForRangeStmt"; 226 | [226] = "SEHTryStmt"; 227 | [227] = "SEHExceptStmt"; 228 | [228] = "SEHFinallyStmt"; 229 | [229] = "MSAsmStmt"; 230 | [230] = "NullStmt"; 231 | [231] = "DeclStmt"; 232 | [232] = "OMPParallelDirective"; 233 | [233] = "OMPSimdDirective"; 234 | [234] = "OMPForDirective"; 235 | [235] = "OMPSectionsDirective"; 236 | [236] = "OMPSectionDirective"; 237 | [237] = "OMPSingleDirective"; 238 | [238] = "OMPParallelForDirective"; 239 | [239] = "OMPParallelSectionsDirective"; 240 | [240] = "OMPTaskDirective"; 241 | [241] = "OMPMasterDirective"; 242 | [242] = "OMPCriticalDirective"; 243 | [243] = "OMPTaskyieldDirective"; 244 | [244] = "OMPBarrierDirective"; 245 | [245] = "OMPTaskwaitDirective"; 246 | [246] = "OMPFlushDirective"; 247 | [247] = "SEHLeaveStmt"; 248 | [248] = "OMPOrderedDirective"; 249 | [249] = "OMPAtomicDirective"; 250 | [250] = "OMPForSimdDirective"; 251 | [251] = "OMPParallelForSimdDirective"; 252 | [252] = "OMPTargetDirective"; 253 | [253] = "OMPTeamsDirective"; 254 | [254] = "OMPTaskgroupDirective"; 255 | [255] = "OMPCancellationPointDirective"; 256 | [256] = "OMPCancelDirective"; 257 | [257] = "OMPTargetDataDirective"; 258 | [258] = "OMPTaskLoopDirective"; 259 | [259] = "OMPTaskLoopSimdDirective"; 260 | [260] = "OMPDistributeDirective"; 261 | [261] = "OMPTargetEnterDataDirective"; 262 | [262] = "OMPTargetExitDataDirective"; 263 | [263] = "OMPTargetParallelDirective"; 264 | [264] = "OMPTargetParallelForDirective"; 265 | [265] = "OMPTargetUpdateDirective"; 266 | [266] = "OMPDistributeParallelForDirective"; 267 | [267] = "OMPDistributeParallelForSimdDirective"; 268 | [268] = "OMPDistributeSimdDirective"; 269 | [269] = "OMPTargetParallelForSimdDirective"; 270 | [270] = "OMPTargetSimdDirective"; 271 | [271] = "OMPTeamsDistributeDirective"; 272 | [272] = "OMPTeamsDistributeSimdDirective"; 273 | [273] = "OMPTeamsDistributeParallelForSimdDirective"; 274 | [274] = "OMPTeamsDistributeParallelForDirective"; 275 | [275] = "OMPTargetTeamsDirective"; 276 | [276] = "OMPTargetTeamsDistributeDirective"; 277 | [277] = "OMPTargetTeamsDistributeParallelForDirective"; 278 | [278] = "OMPTargetTeamsDistributeParallelForSimdDirective"; 279 | [279] = "OMPTargetTeamsDistributeSimdDirective"; 280 | [280] = "BuiltinBitCastExpr"; 281 | [281] = "OMPMasterTaskLoopDirective"; 282 | [282] = "OMPParallelMasterTaskLoopDirective"; 283 | [283] = "OMPMasterTaskLoopSimdDirective"; 284 | [284] = "OMPParallelMasterTaskLoopSimdDirective"; 285 | [285] = "OMPParallelMasterDirective"; 286 | [286] = "OMPDepobjDirective"; 287 | [287] = "OMPScanDirective"; 288 | [300] = "TranslationUnit"; 289 | [400] = "UnexposedAttr"; 290 | [401] = "IBActionAttr"; 291 | [402] = "IBOutletAttr"; 292 | [403] = "IBOutletCollectionAttr"; 293 | [404] = "CXXFinalAttr"; 294 | [405] = "CXXOverrideAttr"; 295 | [406] = "AnnotateAttr"; 296 | [407] = "AsmLabelAttr"; 297 | [408] = "PackedAttr"; 298 | [409] = "PureAttr"; 299 | [410] = "ConstAttr"; 300 | [411] = "NoDuplicateAttr"; 301 | [412] = "CUDAConstantAttr"; 302 | [413] = "CUDADeviceAttr"; 303 | [414] = "CUDAGlobalAttr"; 304 | [415] = "CUDAHostAttr"; 305 | [416] = "CUDASharedAttr"; 306 | [417] = "VisibilityAttr"; 307 | [418] = "DLLExport"; 308 | [419] = "DLLImport"; 309 | [420] = "NSReturnsRetained"; 310 | [421] = "NSReturnsNotRetained"; 311 | [422] = "NSReturnsAutoreleased"; 312 | [423] = "NSConsumesSelf"; 313 | [424] = "NSConsumed"; 314 | [425] = "ObjCException"; 315 | [426] = "ObjCNSObject"; 316 | [427] = "ObjCIndependentClass"; 317 | [428] = "ObjCPreciseLifetime"; 318 | [429] = "ObjCReturnsInnerPointer"; 319 | [430] = "ObjCRequiresSuper"; 320 | [431] = "ObjCRootClass"; 321 | [432] = "ObjCSubclassingRestricted"; 322 | [433] = "ObjCExplicitProtocolImpl"; 323 | [434] = "ObjCDesignatedInitializer"; 324 | [435] = "ObjCRuntimeVisible"; 325 | [436] = "ObjCBoxable"; 326 | [437] = "FlagEnum"; 327 | [438] = "ConvergentAttr"; 328 | [439] = "WarnUnusedAttr"; 329 | [440] = "WarnUnusedResultAttr"; 330 | [441] = "AlignedAttr"; 331 | [500] = "PreprocessingDirective"; 332 | [501] = "MacroDefinition"; 333 | [502] = "MacroExpansion"; 334 | [503] = "InclusionDirective"; 335 | [600] = "ModuleImportDecl"; 336 | [601] = "TypeAliasTemplateDecl"; 337 | [602] = "StaticAssert"; 338 | [603] = "FriendDecl"; 339 | [700] = "OverloadCandidate"; 340 | }, 341 | } 342 | -------------------------------------------------------------------------------- /ljclang_support.c: -------------------------------------------------------------------------------- 1 | // Support library for LJClang. 2 | // Copyright (C) 2013-2020 Philipp Kutin 3 | // See LICENSE for license information. 4 | 5 | #include 6 | 7 | // Returns the LLVM version obtained with " --version" when 8 | // building us. 9 | const char *ljclang_getLLVMVersion() 10 | { 11 | return LJCLANG_LLVM_VERSION; 12 | } 13 | 14 | /* Our cursor visitor takes the CXCursor objects by pointer. */ 15 | typedef enum CXChildVisitResult (*LJCX_CursorVisitor)( 16 | CXCursor *cursor, CXCursor *parent, CXClientData client_data); 17 | 18 | static enum CXChildVisitResult 19 | ourCursorVisitor(CXCursor cursor, CXCursor parent, CXClientData client_data) 20 | { 21 | LJCX_CursorVisitor *visitor = (LJCX_CursorVisitor *)(client_data); 22 | return (*visitor)(&cursor, &parent, NULL); 23 | } 24 | 25 | int ljclang_visitChildrenWith(CXCursor parent, LJCX_CursorVisitor visitor) 26 | { 27 | const unsigned wasBroken = clang_visitChildren(parent, ourCursorVisitor, &visitor); 28 | return (wasBroken ? 1 : 0); 29 | } 30 | -------------------------------------------------------------------------------- /make_docs.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env luajit 2 | 3 | local io = io 4 | local os = os 5 | local ipairs = ipairs 6 | 7 | if (arg[1] == nil) then 8 | print("Usage: "..arg[0].." []") 9 | os.exit(1) 10 | end 11 | 12 | local docLines = {} 13 | local srcLines = {} 14 | 15 | local function readFileIntoTable(fileName, table) 16 | local f = io.open(fileName) 17 | assert(f ~= nil) 18 | repeat 19 | local s = f:read() 20 | table[#table + 1] = s 21 | until (s == nil) 22 | end 23 | 24 | readFileIntoTable(arg[1], docLines) 25 | 26 | for i = 2,#arg do 27 | readFileIntoTable(arg[i], srcLines) 28 | end 29 | 30 | local LineBegPattern = "^ *%-%- " 31 | -- For continued doc lines, it is valid to not have a space after the comment marker. 32 | local LineContPattern = "^ *%-%- ?" 33 | 34 | local function findText(table, searchText) 35 | local lineNum 36 | 37 | for i, line in ipairs(table) do 38 | if (line:match(LineBegPattern) and line:gsub(LineBegPattern, ""):find(searchText, 1, true)) then 39 | if (lineNum ~= nil) then 40 | error("Search text '" .. searchText .. "' is present multiple times.") 41 | end 42 | lineNum = i 43 | end 44 | end 45 | 46 | return lineNum 47 | end 48 | 49 | local sections = {} 50 | 51 | for i = 1, #docLines do 52 | local docLine = docLines[i] 53 | local nextLine = docLines[i + 1] or "" 54 | if (docLine:match("^[A-Za-z ]+$") and 55 | nextLine == string.rep('-', #docLine)) then 56 | sections[#sections + 1] = docLine 57 | end 58 | end 59 | 60 | local homeDir = os.getenv("HOME") 61 | 62 | for _, docLine in ipairs(docLines) do 63 | local searchText = docLine:match("^@@(.*)") 64 | 65 | if (searchText == nil) then 66 | io.write(docLine, '\n') 67 | elseif (searchText == "[toc]") then 68 | -- Link to auto-generated anchor tags on GitHub. 69 | for i, section in ipairs(sections) do 70 | local str = string.format("**[%s](#%s)**%s", 71 | section, section:lower():gsub(' ', '-'), 72 | i < #sections and '\\' or "") 73 | io.write(str, '\n') 74 | end 75 | elseif (searchText:sub(1,5) == "[run]") then 76 | 77 | io.write("~~~~~~~~~~\n") 78 | 79 | local command = searchText:sub(6) 80 | local helpText = io.popen(command):read("*a") 81 | if (homeDir ~= nil) then 82 | -- FIXME: 'homeDir' may (but hopefully does not) contain magic characters. 83 | helpText = helpText:gsub(homeDir, "$HOME") 84 | end 85 | io.write(helpText) 86 | 87 | io.write("~~~~~~~~~~\n") 88 | else 89 | local lineNum = findText(srcLines, searchText) 90 | 91 | if (lineNum == nil) then 92 | io.write(docLine, '\n') 93 | else 94 | for i = lineNum, #srcLines do 95 | local line = srcLines[i] 96 | 97 | if (not line:match(LineContPattern)) then 98 | break 99 | end 100 | 101 | io.write(line:gsub(LineContPattern, ""), "\n") 102 | end 103 | end 104 | end 105 | end 106 | -------------------------------------------------------------------------------- /mgrep.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env luajit 2 | -- mgrep.lua -- Search for named member accesses. 3 | 4 | local require = require 5 | 6 | local io = require("io") 7 | local os = require("os") 8 | local math = require("math") 9 | local string = require("string") 10 | local table = require("table") 11 | 12 | local jit = require("jit") 13 | local ffi = require("ffi") 14 | local C = ffi.C 15 | 16 | local cl = require("ljclang") 17 | local class = require("class").class 18 | 19 | local compile_commands_util = require("compile_commands_util") 20 | local diagnostics_util = require("diagnostics_util") 21 | 22 | local abs = math.abs 23 | local format = string.format 24 | 25 | local arg = arg 26 | local assert = assert 27 | local ipairs = ipairs 28 | local print = print 29 | local type = type 30 | 31 | ffi.cdef[[ 32 | char *getcwd(char *buf, size_t size); 33 | void free(void *ptr); 34 | ]] 35 | 36 | local function getcwd() 37 | if (jit.os ~= "Linux") then 38 | return nil 39 | end 40 | 41 | local cwd = C.getcwd(nil, 0) 42 | if (cwd == nil) then 43 | return nil 44 | end 45 | 46 | local str = ffi.string(cwd) 47 | C.free(cwd) 48 | return str 49 | end 50 | 51 | ---------- 52 | 53 | local function printf(fmt, ...) 54 | print(format(fmt, ...)) 55 | end 56 | 57 | local function errprint(str) 58 | io.stderr:write(str.."\n") 59 | end 60 | 61 | local function errprintf(fmt, ...) 62 | errprint(format(fmt, ...)) 63 | end 64 | 65 | local function abort(str) 66 | errprint(str.."\n") 67 | os.exit(1) 68 | end 69 | 70 | local function usage(hline) 71 | if (hline) then 72 | errprint("ERROR: "..hline.."\n") 73 | end 74 | local progname = arg[0]:match("([^/]+)$") 75 | errprint("Usage:\n "..progname.." :: [options...] [filenames...]\n") 76 | errprint 77 | [[ 78 | Options: 79 | -d /path/to/compile_commands.json: use compilation database 80 | -O '': pass options to Clang, split at whitespace 81 | --no-color: Turn off match and diagnostic highlighting 82 | -n: only parse and potentially print diagnostics 83 | -q: be quiet (don't print diagnostics) 84 | 85 | For the compilation DB invocation, -O can be used for e.g. 86 | -I./clang-include (-> /usr/local/lib/clang/3.7.0/include) 87 | (Attempt to use either -isystem or -I/usr/local/lib/clang/3.7.0/include 88 | to prevent messages like "fatal error: 'stddef.h' file not found".) 89 | This issue seems to be fixed with LLVM 4.0 though. 90 | ]] 91 | os.exit(1) 92 | end 93 | 94 | if (arg[1] == nil) then 95 | usage() 96 | end 97 | 98 | local parsecmdline = require("parsecmdline_pk") 99 | local opt_meta = { 100 | [0] = -1, 101 | d=true, O=true, q=false, n=false, 102 | ["-no-color"]=false 103 | } 104 | 105 | local opts, files = parsecmdline.getopts(opt_meta, arg, usage) 106 | 107 | local compDbName = opts.d 108 | local clangOpts = opts.O 109 | local quiet = opts.q 110 | local dryrun = opts.n 111 | local useColors = not opts["-no-color"] 112 | 113 | local queryStr = files[0] 114 | files[0] = nil 115 | 116 | if (queryStr == nil) then 117 | usage("Must provide :: to search for as first argument") 118 | end 119 | 120 | local function parseQueryString(qstr) 121 | local pos = qstr:find("::") 122 | if (pos == nil) then 123 | abort("ERROR: member to search for must be specified as ::") 124 | end 125 | 126 | return qstr:sub(1,pos-1), qstr:sub(pos+2) 127 | end 128 | 129 | local typeName, memberName = parseQueryString(queryStr) 130 | 131 | -- The ClassDecl or StructDecl of the type we're searching for: 132 | local g_structDecl 133 | local g_cursorKind 134 | 135 | local V = cl.ChildVisitResult 136 | -- Visitor for finding the named structure declaration. 137 | local GetTypeVisitor = cl.regCursorVisitor( 138 | function(cur, parent) 139 | local curKind = cur:kind() 140 | 141 | if (curKind == "ClassDecl" or curKind == "StructDecl") then 142 | if (cur:name() == typeName) then 143 | g_structDecl = cl.Cursor(cur) 144 | g_cursorKind = curKind 145 | return V.Break 146 | end 147 | elseif (curKind == "TypedefDecl") then 148 | local typ = cur:typedefType() 149 | local structDecl = typ:declaration() 150 | if (structDecl:haskind("StructDecl")) then 151 | -- printf("typedef struct %s %s", structDecl:name(), cur:name()) 152 | if (cur:name() == typeName) then 153 | g_structDecl = structDecl 154 | g_cursorKind = "StructDecl" 155 | return V.Break 156 | end 157 | end 158 | end 159 | 160 | -- The structure declaration might not be found on the top level for C++ 161 | -- code, 'Recurse' instead of 'Continue'. 162 | -- 163 | -- NOTE: Of course, mgrep does not have any notable C++ naming support, but 164 | -- one might use it to search code originally written as C which then had 165 | -- C++ "added in". 166 | return V.Recurse 167 | end) 168 | 169 | -------------------- 170 | 171 | local Col = require("terminal_colors") 172 | 173 | local SourceFile = class 174 | { 175 | function(fn) 176 | local fh, msg = io.open(fn) 177 | if (fh == nil) then 178 | errprintf("Could not open %s", msg) 179 | os.exit(1) 180 | end 181 | 182 | return { fh=fh, line=0 } 183 | end, 184 | 185 | getLine = function(f, line) 186 | assert(f.line < line) 187 | 188 | local str 189 | while (f.line < line) do 190 | f.line = f.line+1 191 | str = f.fh:read("*l") 192 | end 193 | 194 | return str 195 | end, 196 | } 197 | 198 | local g_fileIdx = {} -- [fileName] = fileIdx 199 | local g_fileName = {} -- [fileIdx] = fileName 200 | local g_fileLines = {} -- [fileIdx] = { linenum1, linenum2, ... }, negated if spans >1 line 201 | local g_fileColumnPairs = {} -- [fileIdx] = { {colBeg1, colEnd1, colBeg2, colEnd2}, ... } 202 | 203 | local function clearResults() 204 | g_fileIdx = {} 205 | g_fileName = {} 206 | g_fileLines = {} 207 | g_fileColumnPairs = {} 208 | end 209 | 210 | -- Visitor for looking for the wanted member accesses. 211 | local SearchVisitor = cl.regCursorVisitor( 212 | function(cur, parent) 213 | if (cur:haskind("MemberRefExpr")) then 214 | local membname = cur:name() 215 | if (membname == memberName) then 216 | local def = cur:definition():parent() 217 | if (def:haskind(g_cursorKind) and def == g_structDecl) then 218 | local fn, line, col, lineEnd, colEnd = cur:location() 219 | local oneline = (line == lineEnd) 220 | 221 | local idx = g_fileIdx[fn] or #g_fileLines+1 222 | if (g_fileLines[idx] == nil) then 223 | -- encountering file name for the first time 224 | g_fileIdx[fn] = idx 225 | g_fileName[idx] = fn 226 | g_fileLines[idx] = {} 227 | g_fileColumnPairs[idx] = {} 228 | end 229 | 230 | local lines = g_fileLines[idx] 231 | local haveLine = 232 | lines[#lines] ~= nil 233 | and (abs(lines[#lines]) == line) 234 | 235 | local lidx = haveLine and #lines or #lines+1 236 | 237 | if (not haveLine) then 238 | lines[lidx] = oneline and line or -line 239 | end 240 | 241 | local colNumPairs = g_fileColumnPairs[idx] 242 | if (colNumPairs[lidx] == nil) then 243 | colNumPairs[lidx] = {} 244 | end 245 | 246 | local pairs = colNumPairs[lidx] 247 | if (oneline) then 248 | -- The following 'if' check is to prevent adding the same 249 | -- column pair twice, e.g. when a macro contains multiple 250 | -- references to the searched-for member. 251 | if (pairs[#pairs-1] ~= col) then 252 | pairs[#pairs+1] = col 253 | pairs[#pairs+1] = colEnd 254 | end 255 | end 256 | end 257 | end 258 | end 259 | 260 | return V.Recurse 261 | end) 262 | 263 | local function colorizeResult(str, colBegEnds) 264 | local a=1 265 | local strtab = {} 266 | 267 | for i=1,#colBegEnds,2 do 268 | local b = colBegEnds[i] 269 | local e = colBegEnds[i+1] 270 | 271 | strtab[#strtab+1] = str:sub(a,b-1) 272 | local encoded_string = Col.encode_color(str:sub(b,e-1), Col.Bold..Col.Red) 273 | strtab[#strtab+1] = Col.colorize(encoded_string) 274 | a = e 275 | end 276 | strtab[#strtab+1] = str:sub(a) 277 | 278 | return table.concat(strtab) 279 | end 280 | 281 | local curDir = getcwd() 282 | 283 | local function printResults() 284 | for fi = 1,#g_fileName do 285 | local fn = g_fileName[fi] 286 | 287 | if (curDir ~= nil and fn:sub(1,#curDir)==curDir) then 288 | fn = "./"..fn:sub(#curDir+2) 289 | end 290 | 291 | local lines = g_fileLines[fi] 292 | local pairs = g_fileColumnPairs[fi] 293 | 294 | local f = SourceFile(fn) 295 | 296 | for li=1,#lines do 297 | local line = abs(lines[li]) 298 | local str = f:getLine(line) 299 | if (useColors) then 300 | str = colorizeResult(str, pairs[li]) 301 | end 302 | printf("%s:%d: %s", fn, line, str) 303 | end 304 | end 305 | end 306 | 307 | -- Use a compilation database? 308 | local useCompDb = (compDbName ~= nil) 309 | local compArgs = {} -- if using compDB, will have #compArgs == #compDbEntries, each a table 310 | 311 | if (not useCompDb and #files == 0) then 312 | os.exit(0) 313 | end 314 | 315 | if (useCompDb) then 316 | if (#files > 0) then 317 | usage("When using compilation database, must pass no file names") 318 | end 319 | 320 | local compDbPos = compDbName:find("[\\/]compile_commands.json$") 321 | if (compDbPos == nil) then 322 | usage("File name of compilation database must be compile_commands.json") 323 | end 324 | 325 | local compDbDir = compDbName:sub(1, compDbPos) 326 | -- TODO: port to compile_commands_reader 327 | local db = cl.CompilationDatabase(compDbDir) 328 | 329 | if (db == nil) then 330 | abort("Fatal: Could not load compilation database") 331 | end 332 | 333 | local cmds = db:getAllCompileCommands() 334 | 335 | if (#cmds == 0) then 336 | -- NOTE: We get a CompilationDatabase even if 337 | -- clang_CompilationDatabase_fromDirectory() failed (as evidenced by 338 | -- error output from "LIBCLANG TOOLING"). 339 | abort("Fatal: Compilation database contains no entries, or an error occurred") 340 | end 341 | 342 | for ci, cmd in ipairs(cmds) do 343 | local args = compile_commands_util.sanitize_args(cmd:getArgs(), cmd:getDirectory()) 344 | 345 | if (clangOpts ~= nil) then 346 | local suffixArgs = cl.splitAtWhitespace(clangOpts) 347 | for ai=1,#suffixArgs do 348 | args[#args+1] = suffixArgs[ai] 349 | end 350 | end 351 | compArgs[ci] = args 352 | end 353 | 354 | for i=1,#cmds do 355 | -- Fake presence of files for compilation database mode for the later loop. 356 | files[i] = "/dev/null" -- XXX: Windows 357 | end 358 | end 359 | 360 | local foundStruct = false 361 | 362 | for fi=1,#files do 363 | local fn = files[fi] 364 | 365 | local index = cl.createIndex(true, false) 366 | local opts = useCompDb and compArgs[fi] or clangOpts or {} 367 | 368 | do 369 | local f, msg = io.open(fn) 370 | if (f == nil) then 371 | errprintf("ERROR: Failed opening %s", msg) 372 | goto nextfile 373 | end 374 | f:close() 375 | end 376 | 377 | local tu, errorCode = index:parse(useCompDb and "" or fn, opts, {"KeepGoing"}) 378 | 379 | if (tu == nil) then 380 | errprintf("ERROR: Failed parsing %s: %s", fn, errorCode) 381 | goto nextfile 382 | end 383 | 384 | if (not quiet) then 385 | -- TODO 386 | assert(false) 387 | diagnostics_util.GetDiags(tu:diagnosticSet(), useColors, false) 388 | end 389 | 390 | if (not dryrun) then 391 | local tuCursor = tu:cursor() 392 | tuCursor:children(GetTypeVisitor) 393 | 394 | if (g_structDecl ~= nil) then 395 | tuCursor:children(SearchVisitor) 396 | printResults() 397 | clearResults() 398 | g_structDecl = nil 399 | g_cursorKind = nil 400 | foundStruct = true 401 | end 402 | end 403 | 404 | ::nextfile:: 405 | end 406 | 407 | if (not foundStruct) then 408 | if (not quiet) then 409 | errprintf("Did not find declaration for '%s'.", typeName) 410 | end 411 | os.exit(1) 412 | end 413 | -------------------------------------------------------------------------------- /mkapp.lua: -------------------------------------------------------------------------------- 1 | #!/bin/false 2 | -- Usage: luajit -l mkapp .lua [] 3 | 4 | local assert = assert 5 | local print = print 6 | local require = require 7 | local type = type 8 | 9 | local io = io 10 | local os = os 11 | local package = package 12 | local string = string 13 | local table = table 14 | 15 | local format = string.format 16 | local open = io.open 17 | 18 | local arg = arg 19 | local _G = _G 20 | 21 | ---------- 22 | 23 | local function printf(fmt, ...) 24 | print(format("mkapp: "..fmt, ...)) 25 | end 26 | 27 | local function errprint(str) 28 | io.stderr:write("mkapp: "..str.."\n") 29 | end 30 | 31 | local function errprintf(fmt, ...) 32 | errprint(format(fmt, ...)) 33 | end 34 | 35 | -- TODO: if no file modules encountered, just output the main file under the new name. 36 | 37 | -- Among other things, used for cases where we cannot proceed with processing 38 | -- (e.g. file I/O encountered) 39 | -- NOTE: do not merely error() so that code wrapped cannot pcall() it. 40 | -- TODO: think about other places where we would like to terminate on presence of a thrown 41 | -- error instead of allowing it to propagate? 42 | local function errprintfAndExit(fmt, ...) 43 | errprintf("ERROR: "..fmt, ...) 44 | os.exit(150) 45 | end 46 | 47 | ---------- 48 | 49 | -- TODO: treat ffi.load() analogously. If the program has ffi.load() calls, 50 | -- generate target identifier (something like the "triple") from 51 | -- jit.os jit.arch ffi.abi(). 52 | -- Do not *handle* shared library loads though. This would entail platform-specific work. 53 | 54 | local level = 0 55 | 56 | local progName = arg[0] 57 | local outFileName = progName:match("%.lua$") and 58 | progName:gsub("%.lua$", ".app.lua") or 59 | progName..".app.lua" 60 | 61 | ---------- 62 | 63 | -- The generated .app.lua should be as stand-alone as possible, so disallow all Lua 64 | -- functions taking a file name at app generation time. 65 | local function abortDisallowedFunction(...) 66 | -- TODO: find out and print the name of the function? 67 | errprintfAndExit("Attempt to call disallowed function at app generation time") 68 | end 69 | 70 | -- List obtained by searching for 'filename' in the Lua 5.1 reference manual opened in w3m. 71 | -- TODO: are there other functions by which any amount of information may be obtained about 72 | -- any host file system state? These may be reasonable candidates for disallowing, too. 73 | _G.dofile = abortDisallowedFunction 74 | _G.loadfile = abortDisallowedFunction 75 | _G.io.lines = abortDisallowedFunction 76 | _G.io.open = abortDisallowedFunction 77 | _G.os.remove = abortDisallowedFunction 78 | 79 | local io_input = io.input 80 | _G.io.input = function(file) 81 | return (file ~= nil) and abortDisallowedFunction() or io_input(file) 82 | end 83 | 84 | local io_output = io.output 85 | _G.io.output = function(file) 86 | return (file ~= nil) and abortDisallowedFunction() or io_output(file) 87 | end 88 | 89 | -- Be strict and disallow a few other functions. 90 | _G.io.popen = abortDisallowedFunction 91 | _G.os.execute = abortDisallowedFunction 92 | 93 | -- Also disallow loading C modules. 94 | package.loaders[3] = nil 95 | package.loaders[4] = nil 96 | 97 | ---------- 98 | 99 | -- Will contain generated Lua code at sequential indexes. 100 | -- (Generated by us, mixed with contents of the Lua files that are 'require'd.) 101 | local code = { 102 | "", -- place for the shebang 103 | "\n", 104 | "-- Application unity file generated by mkapp.lua of LJClang, see\n", 105 | "-- https://github.com/helixhorned/ljclang\n", 106 | "-- NOTE: this does not say anything about the authorship or any\n", 107 | "-- other property of the constituent files of this amalgamation!\n", 108 | "\n", 109 | "local __LJClang_MkApp_Modules = {require}\n" 110 | } 111 | 112 | code[#code + 1] = [==[ 113 | -- mkapp.lua: override require() ======================================================================== 114 | local require 115 | do 116 | local error=error 117 | local orig_require=__LJClang_MkApp_Modules[1] 118 | __LJClang_MkApp_Modules[1]=nil 119 | 120 | require = function(m) 121 | local t=__LJClang_MkApp_Modules[m] 122 | if t==nil then error("module '"..m.."' not included in .app.lua, please contact app and/or mkapp.lua maintainer") end 123 | return (t==true) and orig_require(m) or t 124 | end 125 | end 126 | ]==] 127 | 128 | local function addFileContents(f) 129 | code[#code + 1] = f:read("*a") 130 | f:close() 131 | end 132 | 133 | local function addCodeForFile(fileName) 134 | local f = open(fileName) 135 | if (f == nil) then 136 | errprintfAndExit("Failed opening '%s'", fileName) 137 | end 138 | 139 | code[#code + 1] = "(function()\n" 140 | addFileContents(f) 141 | -- NOTE: in the generated code, immediately execute the module function. 142 | code[#code + 1] = "end)()\n" 143 | end 144 | 145 | local haveBuiltinModule = {} 146 | local lastModuleWasBuiltin = false 147 | 148 | -- Roughly corresponds to the file searching part of the second loader described in Lua 5.1 149 | -- Reference Manual's `package.loaders` [1] and also the undocumented LuaJIT function 150 | -- package.searchpath() implemented in 'lj_cf_package_searchpath()' in 'src/lib_package.c'. 151 | -- 152 | -- [1] https://www.lua.org/manual/5.1/manual.html#pdf-package.loaders 153 | local function searchpath(module, searchPath) 154 | assert(type(module) == "string") 155 | assert(type(searchPath) == "string") 156 | 157 | module = module:gsub('%.', '/') 158 | 159 | for template in searchPath:gmatch("[^;]+") do 160 | local fileName = template:gsub("%?", module) 161 | local f = open(fileName) 162 | if (f ~= nil) then 163 | f:close() 164 | return fileName 165 | end 166 | end 167 | end 168 | 169 | _G.require = function(module) 170 | assert(type(module) == "string") 171 | 172 | level = level + 1 173 | 174 | local fileName = searchpath(module, package.path) 175 | local wasLoaded = (package.loaded[module] ~= nil) 176 | 177 | -- TODO: error/warn/have-option if not in current directory or beneath? 178 | printf("[%d] REQUIRE %s -> %s%s", 179 | level, module, (fileName or "(built-in)"), 180 | wasLoaded and "" or " [first time]") 181 | 182 | local tab = require(module) 183 | 184 | if (type(tab) ~= "table") then 185 | errprintfAndExit("require() must return table") 186 | end 187 | 188 | assert(package.loaded[module] == tab) 189 | 190 | if (fileName == nil) then 191 | -- Built-in module. We cannot rely on 'package.loaded': some (like 'bit') are 192 | -- pre-loaded, others (like 'ffi') are not. 193 | if (not haveBuiltinModule[module]) then 194 | if (not lastModuleWasBuiltin) then 195 | code[#code + 1] = format("-- mkapp.lua: built-in modules %s\n", ("="):rep(94 - 21)) 196 | lastModuleWasBuiltin = true 197 | end 198 | code[#code + 1] = format("__LJClang_MkApp_Modules[%q]=true\n", module) 199 | haveBuiltinModule[module] = true 200 | end 201 | elseif (not wasLoaded) then 202 | -- Module loaded first time from file. 203 | printf("GENCODE %s -> %s", module, fileName) 204 | 205 | code[#code + 1] = format("-- mkapp.lua: file module %s\n", ("="):rep(99 - 21)) 206 | code[#code + 1] = format("__LJClang_MkApp_Modules[%q]=", module) 207 | lastModuleWasBuiltin = false 208 | 209 | addCodeForFile(fileName) 210 | end 211 | 212 | level = level - 1 213 | 214 | return tab 215 | end 216 | 217 | local function writeCode(fileName) 218 | local f, msg = open(fileName, "w") 219 | if (f == nil) then 220 | errprintfAndExit("%s", msg) 221 | end 222 | 223 | f:write(table.concat(code)) 224 | end 225 | 226 | local os_exit = os.exit 227 | _G.os.exit = function(errorCode) 228 | print() 229 | 230 | if (errorCode == 0) then 231 | local f = open(progName) 232 | if (f == nil) then 233 | errprintfAndExit("Failed opening '%s'", progName) 234 | end 235 | 236 | local shebang = f:read("*l") 237 | if (not shebang:match("^#!/")) then 238 | errprintfAndExit("Main file %s must start with a shebang ('#!/')") 239 | end 240 | code[1] = shebang..'\n' 241 | code[#code + 1] = [[ 242 | -- mkapp.lua: main ===================================================================================== 243 | ]] 244 | addFileContents(f) 245 | 246 | printf("Writing %s", outFileName) 247 | writeCode(outFileName) 248 | else 249 | -- TODO: what if the application does not call os.exit() at all? 250 | printf("Not writing anything: application exits with non-zero code.") 251 | end 252 | 253 | os_exit(errorCode) 254 | end 255 | -------------------------------------------------------------------------------- /mkdecls.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # NOTE: this program expects an environment suitable for running: 6 | extractdecls=${LJCLANG_EXTRACTDECLS:-./extractdecls.lua} 7 | test -x "$extractdecls" || \ 8 | (echo "ERROR: '$extractdecls' not found or not executable." >&2 && exit 1) 9 | 10 | inFile="$1" 11 | 12 | function usageAndExit() { 13 | echo 14 | echo "Usage: $0 [arguments-to-extractdecls...]" 15 | echo " Reads the template file line by line, copying each one to stdout" 16 | echo " except those starting with '@@', who are taken as arguments to $extractdecls" 17 | echo " which is then run and on success, its output is substituted for the '@@' line." 18 | echo " The trailing arguments from the command line are likewise passed to $extractdecls," 19 | echo " after the ones from the template file." 20 | echo " On the first error from $extractdecls, exits with the same exit code as it." 21 | echo 22 | exit 1 23 | } 24 | 25 | if [ -z "$inFile" ]; then 26 | usageAndExit 27 | fi 28 | 29 | shift 30 | 31 | if grep '\\.' "$inFile"; then 32 | # We allow line continuations, but disallow any other use of the backslash because 33 | # 'read' without '-r' would not retain them. 34 | echo "ERROR: in $inFile, found backslash not at the end of a line." >&2 35 | exit 2 36 | fi 37 | 38 | exec {resultFd}< "$inFile" 39 | 40 | while IFS='' read -u $resultFd line; do 41 | if [ x"${line:0:2}" == x'@@' ]; then 42 | args="${line:2}" 43 | "$extractdecls" $args "$@" 44 | else 45 | echo -E "$line" 46 | fi 47 | done 48 | -------------------------------------------------------------------------------- /parsecmdline_pk.lua: -------------------------------------------------------------------------------- 1 | 2 | local pairs = pairs 3 | 4 | -- Get options and positional arguments from command line. A '--' stops option 5 | -- processing and collects the following arguments into 'args' (positional 6 | -- arguments), irrespective of whether they start with a dash. 7 | -- 8 | -- opts, args = getopts(opt_meta, arg, usage_func) 9 | -- 10 | -- : table { [optletter]=true/false/string/tab } 11 | -- : string sequence 12 | -- 13 | -- : Meta-information about options, table of [optletter]=, 14 | -- false: doesn't have argument (i.e. is switch) 15 | -- true: has argument, collect once 16 | -- 1: has argument, collect all 17 | -- opt_meta[0] is an offset for the indices of the returned table. 18 | -- For example, if it's -1, the args[0] will be the first positional argument. 19 | -- Defaults to 0. 20 | -- 21 | -- : The arguments provided to the program 22 | -- : Function to print usage and terminate. Should accept optional 23 | -- prefix line. 24 | local function getopts(opt_meta, arg, usage) 25 | local opts = {} 26 | for k,v in pairs(opt_meta) do 27 | -- Init tables for collect-multi options. 28 | if (v and v~=true) then 29 | opts[k] = {} 30 | end 31 | end 32 | 33 | -- The extracted positional arguments: 34 | local args = {} 35 | local apos = 1 + (opt_meta[0] or 0) 36 | 37 | local skipnext = false 38 | local processOpts = true 39 | 40 | for i=1,#arg do 41 | if (skipnext) then 42 | skipnext = false 43 | goto next 44 | end 45 | 46 | if (arg[i] == "--") then 47 | processOpts = false 48 | goto next 49 | end 50 | 51 | if (processOpts and arg[i]:sub(1,1)=="-") then 52 | local opt = arg[i]:sub(2) 53 | skipnext = opt_meta[opt] 54 | 55 | if (skipnext == nil) then 56 | usage("Unrecognized option "..arg[i]) 57 | elseif (skipnext) then 58 | if (arg[i+1] == nil) then 59 | usage() 60 | end 61 | 62 | if (skipnext~=true) then 63 | opts[opt][#opts[opt]+1] = arg[i+1] 64 | else 65 | if (opts[opt] ~= nil) then 66 | usage("Duplicate option "..arg[i]) 67 | end 68 | opts[opt] = arg[i+1] 69 | end 70 | else 71 | opts[opt] = true 72 | end 73 | else 74 | -- Uncommenting this makes option processing stop at the first 75 | -- non-option argument. When it is commented, options and 76 | -- positional arguments can be in any order, and only '--' stops 77 | -- option processing. 78 | -- processOpts = false 79 | args[apos] = arg[i] 80 | apos = apos+1 81 | end 82 | ::next:: 83 | end 84 | 85 | return opts, args 86 | end 87 | 88 | return { 89 | getopts = getopts 90 | } 91 | -------------------------------------------------------------------------------- /run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Obtained with: 4 | # $ luarocks path | sed -r "s|;(/[^h]\|[^/])[^;']*||g" 5 | # This assumes that the 'busted' Lua unit test framework has been installed like this: 6 | # $ luarocks --local install busted 7 | LUA_PATH="$HOME/.luarocks/share/lua/5.1/?.lua;$HOME/.luarocks/share/lua/5.1/?/init.lua" 8 | LUA_CPATH="$HOME/.luarocks/lib/lua/5.1/?.so" 9 | 10 | d=`pwd` 11 | LUA_PATH=";;$d/?.lua;$LUA_PATH" 12 | LD_LIBRARY_PATH="$LLVM_LIBDIR:$d" 13 | 14 | if [ -z "$LLVM_LIBDIR" ]; then 15 | echo "ERROR: Must pass 'LLVM_LIBDIR'" 16 | exit 1 17 | fi 18 | 19 | export LUA_PATH LUA_CPATH LD_LIBRARY_PATH 20 | 21 | luajit "$d/tests.lua" "$@" 22 | -------------------------------------------------------------------------------- /symbol_index.lua: -------------------------------------------------------------------------------- 1 | 2 | local ffi = require("ffi") 3 | 4 | local class = require("class").class 5 | local posix = require("posix") 6 | local linux_decls = require("ljclang_linux_decls") 7 | 8 | local error_util = require("error_util") 9 | local checktype = error_util.checktype 10 | local check = error_util.check 11 | 12 | local assert = assert 13 | local tonumber = tonumber 14 | 15 | ---------- 16 | 17 | local api = { 18 | EntriesPerPage = nil -- set below 19 | } 20 | 21 | local SymbolInfo = ffi.typeof[[struct { 22 | uint64_t intFlags; // intrinsic flags (identifying a particular symbol) 23 | uint64_t extFlags; // extrinsic flags (describing a particular symbol use) 24 | }]] 25 | 26 | local SymbolInfoPage = (function() 27 | local pageSize = posix.getPageSize() 28 | assert(pageSize % ffi.sizeof(SymbolInfo) == 0) 29 | api.EntriesPerPage = tonumber(pageSize / ffi.sizeof(SymbolInfo)) 30 | return ffi.typeof("$ [$]", SymbolInfo, api.EntriesPerPage) 31 | end)() 32 | 33 | local SymbolInfoPagePtr = ffi.typeof("$ *", SymbolInfoPage) 34 | 35 | local MaxSymPages = { 36 | Local = (ffi.abi("64bit") and 1*2^30 or 128*2^20) / ffi.sizeof(SymbolInfoPage), 37 | Global = (ffi.abi("64bit") and 4*2^30 or 512*2^20) / ffi.sizeof(SymbolInfoPage), 38 | } 39 | 40 | api.SymbolIndex = class 41 | { 42 | function(localPageArrayCount) 43 | checktype(localPageArrayCount, 1, "number", 2) 44 | 45 | local PROT, MAP, LMAP = posix.PROT, posix.MAP, linux_decls.MAP 46 | 47 | local requestSymPages = function(count, flags, ptrTab) 48 | local voidPtr = posix.mmap(nil, count * ffi.sizeof(SymbolInfoPage), 49 | PROT.READ + PROT.WRITE, flags, -1, 0) 50 | -- Need to retain the pointer as its GC triggers the munmap(). 51 | ptrTab[#ptrTab + 1] = voidPtr 52 | 53 | return ffi.cast(SymbolInfoPagePtr, voidPtr) 54 | end 55 | 56 | local localPageArrays, voidPtrs = {}, {} 57 | 58 | for i = 1, localPageArrayCount do 59 | localPageArrays[i] = requestSymPages( 60 | MaxSymPages.Local, MAP.SHARED + LMAP.ANONYMOUS, voidPtrs) 61 | end 62 | 63 | return { 64 | globalPageArray = requestSymPages( 65 | MaxSymPages.Global, MAP.PRIVATE + LMAP.ANONYMOUS, voidPtrs), 66 | localPageArrays = localPageArrays, 67 | voidPtrs_ = voidPtrs, 68 | } 69 | end, 70 | 71 | remapLocalToGlobalPage = function(self, localPageArrayIdx, srcPageIdx, globalPageIdx) 72 | checktype(localPageArrayIdx, 1, "number", 2) 73 | check(localPageArrayIdx >= 1 and localPageArrayIdx <= #self.localPageArrays, 74 | "argument #1 must be a valid local page array index" ,2) 75 | 76 | checktype(srcPageIdx, 2, "number", 2) 77 | check(srcPageIdx >= 0 and srcPageIdx < MaxSymPages.Local, 78 | "argument #2 must be a valid local page index", 2) 79 | checktype(globalPageIdx, 3, "number", 2) 80 | check(globalPageIdx >= 0 and globalPageIdx < MaxSymPages.Global, 81 | "argument #3 must be a valid global page index", 2) 82 | 83 | posix.memRemapSinglePage( 84 | self:getLocalPageArrayVoidPtr(localPageArrayIdx), srcPageIdx, 85 | self:getGlobalPageArrayVoidPtr(), globalPageIdx) 86 | end, 87 | 88 | -- private, KEEPINSYNC with how self.voidPtrs_ is set up: 89 | getLocalPageArrayVoidPtr = function(self, localPageArrayIdx) 90 | return self.voidPtrs_[localPageArrayIdx] 91 | end, 92 | 93 | getGlobalPageArrayVoidPtr = function(self) 94 | return self.voidPtrs_[#self.voidPtrs_] 95 | end, 96 | } 97 | 98 | -- Done! 99 | return api 100 | -------------------------------------------------------------------------------- /terminal_colors.lua: -------------------------------------------------------------------------------- 1 | local assert = assert 2 | local pairs = pairs 3 | 4 | local string = require("string") 5 | local table = require("table") 6 | 7 | local error_util = require("error_util") 8 | local checktype = error_util.checktype 9 | 10 | ---------- 11 | 12 | local InternalCode = { 13 | Normal = string.char(1), 14 | Bold = string.char(2), 15 | Uline = string.char(3), 16 | 17 | Black = string.char(4), 18 | Red = string.char(5), 19 | Green = string.char(6), 20 | Yellow = string.char(7), 21 | Blue = string.char(8), 22 | Purple = string.char(11), 23 | Cyan = string.char(12), 24 | White = string.char(14), 25 | 26 | Begin = string.char(15), 27 | End = string.char(16), 28 | } 29 | 30 | local InternalCodeSeq = {} 31 | for _, v in pairs(InternalCode) do 32 | InternalCodeSeq[#InternalCodeSeq + 1] = v 33 | end 34 | 35 | local InternalCodePattern = "[" .. table.concat(InternalCodeSeq) .. "]" 36 | 37 | local api = { 38 | -- We pass outward our internal color codes. (Which are just lower control codes. 39 | -- Hopefully they are not encountered in diagnostic strings from Clang.) 40 | Normal = InternalCode.Normal, 41 | Bold = InternalCode.Bold, 42 | Uline = InternalCode.Uline, 43 | 44 | Black = InternalCode.Black, 45 | Red = InternalCode.Red, 46 | Green = InternalCode.Green, 47 | Yellow = InternalCode.Yellow, 48 | Blue = InternalCode.Blue, 49 | Purple = InternalCode.Purple, 50 | Cyan = InternalCode.Cyan, 51 | White = InternalCode.White, 52 | } 53 | 54 | -- For reference: 55 | -- https://wiki.archlinux.org/index.php/Color_Bash_Prompt#List_of_colors_for_prompt_and_Bash 56 | local ToTerminalCode = { 57 | [InternalCode.Normal] = "0;", 58 | [InternalCode.Bold] = "1;", 59 | [InternalCode.Uline] = "4;", 60 | 61 | [InternalCode.Black] = "30m", 62 | [InternalCode.Red] = "31m", 63 | [InternalCode.Green] = "32m", 64 | [InternalCode.Yellow] = "33m", 65 | [InternalCode.Blue] = "34m", 66 | [InternalCode.Purple] = "35m", 67 | [InternalCode.Cyan] = "36m", 68 | [InternalCode.White] = "37m", 69 | 70 | [InternalCode.Begin] = "\027[", 71 | [InternalCode.End] = "\027[m", 72 | } 73 | 74 | api.encode = function(str, modcolor) 75 | checktype(str, 1, "string", 2) 76 | checktype(modcolor, 2, "string", 2) 77 | 78 | assert(not str:match(InternalCodePattern), 79 | "String to color-code contains lower control chars") 80 | 81 | return InternalCode.Begin .. modcolor..str.. InternalCode.End 82 | end 83 | 84 | api.colorize = function(coded_str) 85 | checktype(coded_str, 1, "string", 2) 86 | return coded_str:gsub(InternalCodePattern, ToTerminalCode) 87 | end 88 | 89 | api.strip = function(coded_str) 90 | checktype(coded_str, 1, "string", 2) 91 | return coded_str:gsub(InternalCodePattern, "") 92 | end 93 | 94 | -- Done! 95 | return api 96 | -------------------------------------------------------------------------------- /test_data/defines.hpp: -------------------------------------------------------------------------------- 1 | #define LITTLE (Billion/1000000000) 2 | #define MAKE_MUCH(x) ((x) * Trillion) 3 | #define MAKE_VAR(Type, Name, Value) Type Name = Value 4 | -------------------------------------------------------------------------------- /test_data/enums.hpp: -------------------------------------------------------------------------------- 1 | 2 | enum Fruits 3 | { 4 | Apple, 5 | Pear = -4, 6 | Orange, 7 | }; 8 | 9 | enum BigNumbers : unsigned long long 10 | { 11 | Billion = 1000000000, 12 | Trillion = 1'000'000'000'000, 13 | }; 14 | 15 | enum /* anonymous */ : short 16 | { 17 | Red, 18 | Green, 19 | Blue 20 | }; 21 | -------------------------------------------------------------------------------- /test_data/indexopt.cpp: -------------------------------------------------------------------------------- 1 | static const int C = 314159; 2 | int var = 0; 3 | 4 | long func1() { 5 | const char funcLocal = 'a'; 6 | return var + funcLocal; 7 | } 8 | 9 | long func2() { 10 | return var + 'b'; 11 | } 12 | 13 | extern const int D = C + 1; 14 | extern const int E = C + 2; 15 | 16 | template 17 | T Add(T a, T b) { 18 | return var + a + b; 19 | } 20 | 21 | template 22 | struct StructTemplate { 23 | StructTemplate(); 24 | T member; 25 | }; 26 | 27 | // NOTE: does not give an 'unused variable' warning. 28 | static const int F = Add(4, 5); 29 | 30 | StructTemplate g; 31 | -------------------------------------------------------------------------------- /test_data/simple.hpp: -------------------------------------------------------------------------------- 1 | 2 | struct First 3 | { 4 | int a = 1; 5 | long b = 2; 6 | 7 | // ref-qualifiers 8 | void refQualNone(); 9 | void refQualLValue() &; 10 | void refQualRValue() &&; 11 | }; 12 | 13 | inline int func() 14 | { 15 | First f; 16 | return f.a; 17 | } 18 | 19 | enum Fruits 20 | { 21 | Apple, 22 | Pear, 23 | }; 24 | 25 | inline int badFunc(const char *, ...) 26 | { 27 | int i; 28 | return i; // uninitialized use 29 | } 30 | 31 | // Declaration of the enum from enums.hpp to test USRs. 32 | enum BigNumbers : unsigned long long; 33 | 34 | class Incomplete; 35 | -------------------------------------------------------------------------------- /test_data/virtual.hpp: -------------------------------------------------------------------------------- 1 | 2 | class Interface 3 | { 4 | public: 5 | virtual int getIt() = 0; 6 | virtual void setIt(int) = 0; 7 | }; 8 | 9 | class Base 10 | { 11 | int getIt(bool); // not virtual 12 | virtual int getIt(void *); // virtual, but not overridden by this file 13 | virtual int getIt(); 14 | }; 15 | 16 | class Derived : public Base, virtual public Interface 17 | { 18 | int getIt() override; // overrides two functions (one in each base class) 19 | }; 20 | 21 | class Final : public Derived 22 | { 23 | int getIt() final { return 0; } 24 | void setIt(int) final {} 25 | }; 26 | 27 | // Mis-declaration of the enum from enums.hpp to test USRs. (Wrong underlying type.) 28 | enum BigNumbers : int; 29 | 30 | namespace LJClangTest { 31 | inline int GetIt(Interface &interface) { 32 | return interface.getIt(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /text_editor_bindings/ljclang-wcc-flycheck.el: -------------------------------------------------------------------------------- 1 | 2 | ;; Copyright (C) 2019-2020 Philipp Kutin 3 | 4 | ;; This program is free software: you can redistribute it and/or modify 5 | ;; it under the terms of the GNU General Public License as published by 6 | ;; the Free Software Foundation, either version 3 of the License, or 7 | ;; (at your option) any later version. 8 | 9 | ;; This program is distributed in the hope that it will be useful, 10 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | ;; GNU General Public License for more details. 13 | 14 | ;; You should have received a copy of the GNU General Public License 15 | ;; along with this program. If not, see . 16 | 17 | (require 'flycheck) 18 | 19 | ;; Usage (for now): 20 | ;; (load "/path/to/.../ljclang-wcc-flycheck.el") 21 | 22 | ;; TODO: 23 | ;; make usable with 'require'. 24 | 25 | (defun ljclang-wcc--invoke-client (destination &rest args) 26 | (let 27 | ((res 28 | ;; Execute 'wcc-client -c' to check it being properly set up. 29 | ;; ignore-errors is used so that we catch the case when the executable is not found. 30 | ;; TODO: find out: redundant because flycheck checks this before? 31 | (ignore-errors (apply 'call-process "wcc-client" nil destination nil 32 | args)))) 33 | (cond 34 | ((null res) "failed to execute wcc-client") 35 | ((stringp res) (concat "wcc-client exited with signal: " res)) 36 | ((numberp res) 37 | (case res 38 | ;; KEEPINSYNC with wcc-client's exit codes. 39 | (0 "success") 40 | (1 "failed creating FIFO") 41 | (2 "UNEXPECTED ERROR") ; malformed command/args, shouldn't happen with '-c'. 42 | (3 "server not running") 43 | ;; May indicate that: 44 | ;; - it is an exit code >=100 (malformed/unexpected reply from server), or 45 | ;; - an update here is necessary. 46 | (t "UNEXPECTED EXIT CODE") 47 | ))))) 48 | 49 | (defun ljclang-wcc-check-diags-request (_) 50 | (let ((message (ljclang-wcc--invoke-client nil "-C"))) 51 | (list 52 | (flycheck-verification-result-new 53 | :label "wcc-client check" 54 | :message message 55 | :face (if (equal message "success") 'success '(bold error)) 56 | )))) 57 | 58 | (defun ljclang-wcc--tweak-file-info-string (str) 59 | (if (string-equal str "1!") 60 | ;; The file is a source file with a count of including TUs of one. Do not 61 | ;; show the count as it contains little information. (Most source files 62 | ;; only participate in one translation unit. We are interested in headers, 63 | ;; and source files which participate in zero or more than one TUs.) 64 | "" 65 | ;; NOTE: it is deliberate that we pass through a count of zero. Assuming 66 | ;; that wcc-server is finished, this means that a file (even a source!) is 67 | ;; not reachable by any of the compile commands the it was instructed with. 68 | (concat "⇝" str))) 69 | 70 | (defvar-local ljclang-wcc--buffer-file-info-string nil) 71 | 72 | (defun ljclang-wcc--parse-with-patterns (output checker buffer) 73 | "Parse OUTPUT from CHECKER with error patterns. 74 | 75 | Wraps `flycheck-parse-with-patterns' to additionally set a buffer-local 76 | suffix for the 'FlyC' status text. 77 | " 78 | ;; Match the first line of the wcc-client invocation, which we expect is the output of 79 | ;; the "fileinfo including-tu-count" command. 80 | (let* ((firstLine 81 | ;; NOTE [STRING_VALIDATION]: 82 | (progn (if (string-match "^\\(\\(0\\|[1-9]+\\)[\+\?]?!?\\)\n" output) 83 | (match-string 1 output)))) 84 | ;; High voltage sign: first line of output has unexpected form. 85 | ;; This can happen because wcc-client is not running or mismatched. 86 | ;; TODO: this can also happen temporarily, when requesting diags 87 | ;; for a TU which need some time to be computed. Address. 88 | (infoStr (or firstLine "⚡"))) 89 | ;; Set the mode line suffix. 90 | (setq ljclang-wcc--buffer-file-info-string infoStr)) 91 | (flycheck-parse-with-patterns output checker buffer) 92 | ) 93 | 94 | (defun ljclang-wcc--do-list-including-tu-files () 95 | (let* ((fileName (buffer-file-name)) 96 | (baseName (file-name-base fileName)) 97 | (ext (file-name-extension fileName t)) 98 | (newBufferName 99 | ;; NOTE: "source" here is meant in a very specific way, namely as shorthand for 100 | ;; "entry file of compile command considered in the active wcc-server session". 101 | (concat "*source files including " (concat baseName ext) "*"))) 102 | (with-current-buffer-window 103 | newBufferName nil nil 104 | (let* ((msg (ljclang-wcc--invoke-client 105 | t "fileinfo" "including-tu-files" fileName))) 106 | ;; TODO: handle errors: 107 | ;; - Non-zero exit status -> 108 | ;; * our short exit-status-message to the minibuffer 109 | ;; * output still into the new buffer? 110 | ) 111 | (goto-char (point-min)) 112 | ;; Make all file names highlighted and navigatable. 113 | (while (re-search-forward "[^\n]+" nil t) 114 | (replace-match "\\&:1:" t)) 115 | (grep-mode)))) 116 | 117 | (defun ljclang-wcc-list-including-tu-files () 118 | "List the names of the source files affected by the current file. 119 | 120 | Here, 'source' means a file that is an entry into a compile command 121 | that the active wcc-server session is aware of. 122 | " 123 | (interactive) 124 | (let ((infoStr ljclang-wcc--buffer-file-info-string)) 125 | (cond 126 | ((not (eq (flycheck-get-checker-for-buffer) 'c/c++-wcc)) 127 | (message "The current buffer is not flychecked by c/c++-wcc.")) 128 | ((equal infoStr "⚡") 129 | (message "wcc-server not running?")) 130 | ((string-match "^0" infoStr) 131 | (let* ((lastChar (string-to-char (substring infoStr -1))) 132 | (suffix 133 | (case lastChar 134 | (?+ " (yet)") 135 | (?? " (yet)") 136 | (?0 "") 137 | (t (assert nil)) ;; See STRING_VALIDATION for why. 138 | ))) 139 | (message 140 | "The file of the current buffer does not affect any compile commands%s." suffix))) 141 | ((equal infoStr "1!") 142 | ;; NOTE: if a file affects more than one compile command, we will open the list, even 143 | ;; if the unique'd listing only contains the file of the current buffer itself. 144 | ;; (This means that there are multiple compile commands take the source file.) 145 | ;; At this point, we do not know the length of the ultimate result list. 146 | (message "The file of the current buffer only affects itself in one compile command.")) 147 | (t 148 | (ljclang-wcc--do-list-including-tu-files))))) 149 | 150 | (flycheck-define-checker c/c++-wcc 151 | "A C/C++ syntax checker using watch_compile_commands. 152 | 153 | See URL `https://github.com/helixhorned/ljclang/tree/rpi'." 154 | ;; TODO: remove 'rpi' once it is merged to master. 155 | :command ("wcc-client" "-b" 156 | "fileinfo" "including-tu-count" source-original 157 | "@@" 158 | "diags" source-original) 159 | 160 | :error-parser ljclang-wcc--parse-with-patterns 161 | :error-patterns 162 | ( 163 | ;;; Same as for 'c/c++-clang' checker, but with '' removed. 164 | 165 | ;; TODO: if possible, make known to Flycheck: 166 | ;; - the error group, e.g. [Semantic Issue] or [Parse Issue] 167 | ;; - the warning ID, e.g. [-W#warnings] 168 | (info line-start (file-name) ":" line ":" column 169 | ": note: " (optional (message)) line-end) 170 | (warning line-start (file-name) ":" line ":" column 171 | ": warning: " (optional (message)) line-end) 172 | (error line-start (file-name) ":" line ":" column 173 | ": " (or "fatal error" "error") ": " (optional (message)) line-end) 174 | 175 | ;;; Specific to ljclang-wcc-flycheck.el 176 | 177 | ;; NOTE: Flycheck flags an invocation of a checker as "suspicious" (and outputs a lengthy 178 | ;; user-facing message asking to update Flycheck and/or file a Flycheck bug report) if 179 | ;; its exit code is non-zero but no errors were found (using the matching rules defined 180 | ;; in the checker). So, make the client error states known to Flycheck. 181 | ;; 182 | ;; TODO: however, why does Flycheck not enter the 'errored' ("FlyC!") state when 183 | ;; wcc-client exits with a non-zero code? We do not want to miss any kind of errors! 184 | ;; Need to research Flycheck error handling further. 185 | 186 | ;; Error occurring in the client. 187 | (error "ERROR: " (message) line-end) 188 | ;; Error reported back by the server. 189 | (error "remote: ERROR: " (message) line-end) 190 | ) 191 | 192 | :modes (c-mode c++-mode) 193 | :predicate flycheck-buffer-saved-p 194 | 195 | ;; NOTE: this is called only on a manual flycheck-verify-setup invocation. 196 | :verify ljclang-wcc-check-diags-request 197 | ) 198 | 199 | (defun ljclang-wcc-mode-line-status-text (&optional status) 200 | "Get a text describing STATUS for use in the mode line. 201 | 202 | Can be used instead of `flycheck-mode-line-status-text' in the 203 | value for `flycheck-mode-line'. 204 | " 205 | ;; TODO: what if someone has their status line (suffix) modified? 206 | ;; Propose a way to do this per-checker in Flycheck? 207 | (concat 208 | (flycheck-mode-line-status-text status) 209 | (let ((checker (flycheck-get-checker-for-buffer))) 210 | (if (eq checker 'c/c++-wcc) 211 | (ljclang-wcc--tweak-file-info-string 212 | ljclang-wcc--buffer-file-info-string) 213 | "")))) 214 | -------------------------------------------------------------------------------- /util.lua: -------------------------------------------------------------------------------- 1 | local ffi = require("ffi") 2 | 3 | local bit = require("bit") 4 | local math = require("math") 5 | 6 | local error_util = require("error_util") 7 | local check = error_util.check 8 | local checktype = error_util.checktype 9 | local class = require("class").class 10 | 11 | local assert = assert 12 | local error = error 13 | local ipairs = ipairs 14 | local type = type 15 | local unpack = unpack 16 | 17 | ---------- 18 | 19 | local api = {} 20 | 21 | -- argstab = splitAtWhitespace(args) 22 | function api.splitAtWhitespace(args) 23 | check(type(args) == "string", " must be a string", 2) 24 | 25 | local argstab = {} 26 | -- Split delimited by whitespace. 27 | for str in args:gmatch("[^%s]+") do 28 | argstab[#argstab+1] = str 29 | end 30 | 31 | return argstab 32 | end 33 | 34 | -- Is a sequence of strings? 35 | local function iscellstr(tab) 36 | for i=1,#tab do 37 | if (type(tab[i]) ~= "string") then 38 | return false 39 | end 40 | end 41 | 42 | -- We require this because in ARGS_FROM_TAB below, an index 0 would be 43 | -- interpreted as the starting index. 44 | return (tab[0] == nil) 45 | end 46 | 47 | function api.check_iftab_iscellstr(tab, name, level) 48 | if (type(tab)=="table") then 49 | if (not iscellstr(tab)) then 50 | error(name.." must be a string sequence when a table, with no element at [0]", level+1) 51 | end 52 | end 53 | end 54 | 55 | function api.checkOptionsArgAndGetDefault(opts, defaultValue) 56 | if (opts == nil) then 57 | opts = defaultValue; 58 | else 59 | check(type(opts)=="number" or type(opts)=="table", 60 | "argument #1 must be a number or a table", 4) 61 | api.check_iftab_iscellstr(opts, "", 4) 62 | end 63 | 64 | return opts 65 | end 66 | 67 | function api.handleTableOfOptionStrings(lib, prefix, opts) 68 | assert(type(prefix) == "string") 69 | 70 | if (type(opts)=="table") then 71 | local optflags = {} 72 | for i=1,#opts do 73 | optflags[i] = lib[prefix..opts[i]] -- look up the enum 74 | end 75 | opts = (#opts > 0) and bit.bor(unpack(optflags)) or 0 76 | end 77 | 78 | return opts 79 | end 80 | 81 | function api.getCommonPrefix(getString, commonPrefix, ...) 82 | checktype(getString, 1, "function", 2) 83 | check(commonPrefix == nil or type(commonPrefix) == "string", 84 | "argument #2 must be nil or a string", 2) 85 | 86 | for key, value in ... do 87 | local str = getString(key, value) 88 | check(type(str) == "string", "getString(k, v) for iterated k, v should return a string", 2) 89 | 90 | if (commonPrefix == nil) then 91 | commonPrefix = str 92 | else 93 | for i = 1, math.min(#commonPrefix, #str) do 94 | if (commonPrefix:sub(1, i) ~= str:sub(1, i)) then 95 | commonPrefix = commonPrefix:sub(1, i-1) 96 | end 97 | end 98 | end 99 | end 100 | 101 | return commonPrefix 102 | end 103 | 104 | function api.copySequence(tab) 105 | local newTab = {} 106 | 107 | for i = 1,#tab do 108 | newTab[i] = tab[i] 109 | end 110 | 111 | return newTab 112 | end 113 | 114 | ---------- Bimap ---------- 115 | 116 | local BimapTags = { 117 | FIRST_TYPE = {}, 118 | SECOND_TYPE = {}, 119 | COUNT = {}, 120 | } 121 | 122 | api.Bimap = class 123 | { 124 | function(firstType, secondType) 125 | checktype(firstType, 1, "string", 2) 126 | checktype(secondType, 2, "string", 2) 127 | check(firstType ~= secondType, "arguments #1 and #2 must be distinct", 2) 128 | 129 | return { 130 | [BimapTags.FIRST_TYPE] = firstType, 131 | [BimapTags.SECOND_TYPE] = secondType, 132 | [BimapTags.COUNT] = 0, 133 | } 134 | end, 135 | 136 | -- NOTE: 'self' itself is used to store the data. 137 | -- Hence, the "member functions" are stand-alone. 138 | } 139 | 140 | function api.BimapAdd(self, first, second) 141 | checktype(first, 1, self[BimapTags.FIRST_TYPE], 2) 142 | checktype(second, 2, self[BimapTags.SECOND_TYPE], 2) 143 | 144 | -- NOTE: No checking of any kind (such as for one-to-oneness). 145 | self[first] = second 146 | self[second] = first 147 | 148 | self[BimapTags.COUNT] = self[BimapTags.COUNT] + 1 149 | end 150 | 151 | function api.MakeBimap(tab) 152 | checktype(tab, 1, "table", 2) 153 | 154 | local firstItem = tab[1] 155 | local bimap = api.Bimap(type(firstItem[1]), type(firstItem[2])) 156 | 157 | for _, item in ipairs(tab) do 158 | api.BimapAdd(bimap, item[1], item[2]) 159 | end 160 | 161 | return bimap 162 | end 163 | 164 | function api.BimapGetCount(self) 165 | return self[BimapTags.COUNT] 166 | end 167 | 168 | ---------- BoolArray ---------- 169 | 170 | local function CheckIsFiniteInt(number, argIdx) 171 | checktype(number, argIdx, "number", 3) 172 | check(number >= 1 and number < math.huge, "argument must be a finite number", 3) 173 | check(math.floor(number) == number, "argument must be an integral number", 3) 174 | end 175 | 176 | api.BoolArray = function(size, initialValue) 177 | CheckIsFiniteInt(size, 1) 178 | checktype(initialValue, 2, "boolean", 2) 179 | 180 | local array = {} 181 | for i = 1,size do 182 | array[i] = initialValue 183 | end 184 | return array 185 | end 186 | 187 | -- Done! 188 | return api 189 | -------------------------------------------------------------------------------- /wcc-client.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | TEMPDIR=/tmp/ljclang 4 | 5 | pid=$$ 6 | FIFO=$TEMPDIR/wcc-client-${pid}.fifo 7 | 8 | REQUEST_SINK=$TEMPDIR/wcc-request.sink 9 | 10 | ## Usage 11 | 12 | function usage() 13 | { 14 | echo "Usage:" 15 | echo " $0 -c [ [command-options...]] (ignored)" 16 | echo " Validate client invocation (and if present, the well-formedness" 17 | echo " of the command and its arguments)." 18 | echo " $0 -C [ [command-options...]] (ignored)" 19 | echo " In addition to the effects of '-c', actually send the command to" 20 | echo " the server (which treats it as a no-op) and wait for the response." 21 | echo " $0 [-n] [command-options...]" 22 | echo " Send command to be processed by the watch_compile_commands server." 23 | echo " $0 -b [command1-options...] [@@ [command2-options...] ...]" 24 | echo " Send multiple commands. Processing stops at the first failing command." 25 | echo "" 26 | echo "Recognized options in single-command mode" 27 | echo " '-n': exit immediately after sending the command." 28 | exit 1 29 | } 30 | 31 | block=yes 32 | bulk=no 33 | 34 | if [ x"$1" = x"-n" ]; then 35 | block=no 36 | shift 37 | fi 38 | 39 | if [ x"$1" = x"-b" ]; then 40 | if [ $block = no ]; then 41 | # Notes: 42 | # - This error is for when we are invoked with "-n -b". 43 | # Invoking us as "-b -n" will treat the '-n' as the first command. 44 | # - The reason for disallowing it is that the output from different commands would 45 | # appear in undetermined relative order unless special measures are taken. 46 | echo "ERROR: multi-command mode (-b) is not compatible with -n." 47 | exit 2 48 | fi 49 | bulk=yes 50 | shift 51 | fi 52 | 53 | if [ ${#@} -eq 0 ]; then 54 | usage 55 | fi 56 | 57 | # Validate the command 58 | # NOTE: if this part becomes more complex, it may be reasonable to implement it in Lua. 59 | 60 | args=("$@") 61 | numArgs=${#args[@]} 62 | 63 | for ((i=0; i < $numArgs; i++)); do 64 | arg="${args[i]}" 65 | 66 | if [[ "$arg" =~ [[:space:][:cntrl:]] ]]; then 67 | echo "ERROR: command and arguments must not contain space or control characters." 68 | exit 2 69 | fi 70 | 71 | if [[ "$arg" =~ ^-[Een]+$ ]]; then 72 | # -E, -e and -n are the *only* three options interpreted by Bash's builtin 'echo'. 73 | # But, they may be passed "compacted" to be interpreted. (E.g. '-En'.) 74 | # Other combinations are not interpreted: the argument is just printed out as is. 75 | # Examples: 76 | # -Ena (-E and -n are special, but the 'a' makes the whole string non-special) 77 | # -qwe (does not contain special characters at all) 78 | echo "ERROR: command and arguments must not match extended regexp '^-[Een]+$'." 79 | exit 2 80 | fi 81 | 82 | if [[ $bulk == yes && "$arg" =~ ^- ]]; then 83 | # This is very strict and prevents us from passing dash-options server commands, 84 | # but this is done in order to exclude passing options to *us* in our recursive 85 | # invocations, which otherwise may yield unexpected results. 86 | echo "ERROR: in multi-command mode, arguments must not start with '-'." 87 | fi 88 | done 89 | 90 | if [ $bulk == yes ]; then 91 | ## Multi-command mode handling (recursively invoking this script). 92 | curCmdArgs=() 93 | k=0 94 | 95 | for ((i=0; i <= $numArgs; i++)); do 96 | arg="${args[i]}" 97 | 98 | if [[ $i -lt $numArgs && x"$arg" != x"@@" ]]; then 99 | curCmdArgs[$k]="$arg" 100 | k=$((++k)) 101 | else 102 | # Invoke us recursively (with the arguments collected so far). 103 | "$0" "${curCmdArgs[@]}" 104 | exitCode=$? 105 | if [ $exitCode -ne 0 ]; then 106 | exit $exitCode 107 | fi 108 | 109 | curCmdArgs=() 110 | k=0 111 | fi 112 | done 113 | 114 | exit 0 115 | fi 116 | 117 | ## Setup 118 | 119 | function cleanup() 120 | { 121 | rm -f "$FIFO" 122 | } 123 | 124 | function setup() 125 | { 126 | if [ ! -w "$REQUEST_SINK" ]; then 127 | echo "ERROR: $REQUEST_SINK does not exist or is not writable." 128 | echo " Is wcc-server running?" 129 | exit 3 130 | fi 131 | 132 | if [ $block == yes ]; then 133 | mkfifo "$FIFO" || exit 1 134 | trap cleanup EXIT 135 | fi 136 | } 137 | 138 | setup 139 | 140 | if [ x"$1" == x"-c" ]; then 141 | exit 0 142 | fi 143 | 144 | cmdLine="$@" 145 | 146 | ## Handle the command 147 | 148 | function exitWithTimeout() 149 | { 150 | echo "ERROR: timed out waiting for the result." 151 | exit 110 152 | } 153 | 154 | if [ $block == yes ]; then 155 | # The FIFO must *first* be opened for reading: the server opens it with 156 | # O_NONBLOCK | O_WRONLY, thus failing with ENXIO otherwise. 157 | # Also, dummy-open it for writing because otherwise we would hang (chicken & egg). 158 | # 159 | # NOTE: opening a FIFO for reading and writing simultaneously is *undefined* according 160 | # to POSIX (IEEE 1003.1-2008, see open()). Using O_RDWR seems to be Linux-specific, 161 | # see e.g. https://stackoverflow.com/questions/15055065/o-rdwr-on-named-pipes-with-poll 162 | exec {resultFd}<> "$FIFO" 163 | 164 | # Send the command, informing the server of our ID. 165 | echo -E $pid "$cmdLine" >> "$REQUEST_SINK" 166 | 167 | # Read the server's acknowledgement of the request receival, waiting for merely a 168 | # second. (The expectation is that the server sends the acknowledgement immediately.) 169 | read -rs -N 3 -u ${resultFd} -t 1.0 ack 170 | 171 | if [ $? -gt 128 ]; then 172 | echo "ERROR: receival of the request acknowledgement timed out." 173 | exit 100 174 | elif [ x"$ack" != x"ACK" ]; then 175 | echo "ERROR: malformed acknowledgement message." 176 | exit 101 177 | fi 178 | 179 | # Read the success status of the request, waiting for a bit longer. 180 | # 181 | # TODO: elaborate timing expectations/requirements (e.g. it may be OK for the server to 182 | # delay a computation until the respective compile command has been processed). 183 | read -rs -N 3 -u ${resultFd} -t 10.0 res 184 | 185 | if [ $? -gt 128 ]; then 186 | exitWithTimeout 187 | elif [[ x"$res" != x"rOK" && x"$res" != x"rER" ]]; then 188 | echo "ERROR: malformed success status message." 189 | exit 111 190 | fi 191 | 192 | if [ "$res" == rER ]; then 193 | echo -n "remote: ERROR: " 194 | fi 195 | 196 | # The server has sent all data (2 items of header data checked above and the payload) in 197 | # a single write(). Hence, we can read in a loop with timeout 0 (meaning, to check for 198 | # data availability) at the outer level. 199 | # 200 | # The background of doing it this way is that attempts to plainly read it from bash or 201 | # using /bin/cat led to the read *hanging*, even if 'resultFd' was previously closed. 202 | # [In the /bin/cat case, that is; in the 'read' builtin case, the distinction of closing 203 | # "for reading" or "for writing" ('<&-' or '>&-', respectively) seems to be merely 204 | # decorative: in either case, the *single* previously open file descriptor is closed]. 205 | # 206 | # NOTE: There could be data lost if the total message size exceeds 'PIPE_BUF' bytes (in 207 | # which case the OS is allowed to split up the message, see POSIX). 208 | # TODO: detect data loss? 209 | 210 | readArgs=(-rs -u ${resultFd}) 211 | 212 | while read ${readArgs[@]} -t 0 data_available_check_; do 213 | # NOTE: fractional argument seems to be a feature of GNU coreutils 'sleep'. 214 | read ${readArgs[@]} -t 0.1 line 215 | 216 | if [ x"$line" == x"INFO: one or more compile commands not yet processed." ]; then 217 | ## HACK (maybe better than having this logic in watch_compile_commands though): 218 | ## retry a few times after sleeping. 219 | 220 | # First, delete the FIFO (to be recreated with the same name!). 221 | cleanup 222 | 223 | # NOTE: initial argument count should be 2 (for "diags ") or 3 (for 224 | # "diags -k "). 225 | argCount=${#@} 226 | msToWait=$((2 ** argCount)) 227 | secs=$((msToWait / 10)) 228 | ms=$((msToWait % 10)) 229 | /bin/sleep "${secs}.${ms}" 230 | 231 | # NOTE: the EXIT trap ('cleanup') will not execute before an 'exec'. 232 | # It will before an 'exit' though, hence the '-f' argument to 'rm'. 233 | # (We have cleaned up above already.) 234 | if [ $argCount -le 6 ]; then 235 | exec "$0" "$@" . 236 | else 237 | # Tried too many times. 238 | exitWithTimeout 239 | fi 240 | fi 241 | 242 | # NOTE: do not guard against 'line' being '-n', '-e' or '-E' (options to 'echo'). 243 | echo "$line" 244 | done 245 | 246 | if [ "$res" == rER ]; then 247 | exit 200 248 | fi 249 | else 250 | # Send the command as an anonymous request. 251 | echo -E - "$cmdLine" >> "$REQUEST_SINK" 252 | fi 253 | -------------------------------------------------------------------------------- /wcc-server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | TEMPDIR=/tmp/ljclang 4 | 5 | pid=$$ 6 | FIFO=$TEMPDIR/wcc-${pid}-request.sink 7 | 8 | FIFO_LINK_BASENAME=wcc-request.sink 9 | FIFO_LINK=$TEMPDIR/$FIFO_LINK_BASENAME 10 | link_status=1 11 | 12 | function cleanup() 13 | { 14 | echo "Removing $FIFO" 15 | rm "$FIFO" 16 | 17 | if [ $link_status -eq 0 ]; then 18 | echo "Removing $FIFO_LINK" 19 | rm "$FIFO_LINK" 20 | fi 21 | } 22 | 23 | if [ ${#@} -ne 0 ]; then 24 | mkdir -p "$TEMPDIR" || exit 1 25 | 26 | mkfifo "$FIFO" || exit 1 27 | echo "Server: created FIFO $FIFO" 28 | 29 | ln -s -T "$FIFO" "$FIFO_LINK" 30 | link_status=$? 31 | 32 | if [ $link_status -eq 0 ]; then 33 | echo "Server: created symlink $FIFO_LINK" 34 | fi 35 | 36 | trap cleanup EXIT 37 | fi 38 | 39 | watch_compile_commands -m "$FIFO" "$@" 40 | --------------------------------------------------------------------------------