Add libcody

In order to separate compiler from build system, C++ Modules, as
implemented in GCC introduces a communication channel between those
two entities.  This is implemented by libcody.  It is anticipated that
other implementations will also implement this protocol, or use
libcody to provide it.

	* Makefile.def: Add libcody.
	* configure.ac: Add libcody.
	* Makefile.in: Regenerated.
	* configure: Regenerated.
	gcc/
	* Makefile.in (CODYINC, CODYLIB, CODYLIB_H): New. Use them.
	libcody/
	* configure.ac: New.
	* CMakeLists.txt: New.
	* CODING.md: New.
	* CONTRIB.md: New.
	* LICENSE: New.
	* LICENSE.gcc: New.
	* Makefile.in: New.
	* Makesub.in: New.
	* README.md: New.
	* buffer.cc: New.
	* build-aux/config.guess: New.
	* build-aux/config.sub: New.
	* build-aux/install-sh: New.
	* client.cc: New.
	* cmake/libcody-config-ix.cmake
	* cody.hh: New.
	* config.h.in: New.
	* config.m4: New.
	* configure: New.
	* configure.ac: New.
	* dox.cfg.in: New.
	* fatal.cc: New.
	* gdbinit.in: New.
	* internal.hh: New.
	* netclient.cc: New.
	* netserver.cc: New.
	* packet.cc: New.
	* resolver.cc: New.
	* server.cc: New.
	* tests/01-serialize/connect.cc: New.
	* tests/01-serialize/decoder.cc: New.
	* tests/01-serialize/encoder.cc: New.
	* tests/02-comms/client-1.cc: New.
	* tests/02-comms/pivot-1.cc: New.
	* tests/02-comms/server-1.cc: New.
	* tests/Makesub.in: New.
	* tests/jouster: New.
This commit is contained in:
Nathan Sidwell 2020-12-14 08:10:27 -08:00
parent c5271279d6
commit 362303298a
41 changed files with 16301 additions and 4 deletions

View File

@ -81,6 +81,15 @@ host_modules= { module= itcl; };
host_modules= { module= ld; bootstrap=true; };
host_modules= { module= libbacktrace; bootstrap=true; };
host_modules= { module= libcpp; bootstrap=true; };
// As with libiconv, don't install any of libcody
host_modules= { module= libcody; bootstrap=true;
no_install= true;
missing= pdf;
missing= html;
missing= info;
missing= install-pdf;
missing= install-html;
missing= install-info; };
host_modules= { module= libdecnumber; bootstrap=true; };
host_modules= { module= libgui; };
host_modules= { module= libiberty; bootstrap=true;
@ -347,6 +356,7 @@ dependencies = { module=all-gcc; on=all-build-libcpp; };
dependencies = { module=all-gcc; on=all-zlib; };
dependencies = { module=all-gcc; on=all-libbacktrace; hard=true; };
dependencies = { module=all-gcc; on=all-libcpp; hard=true; };
dependencies = { module=all-gcc; on=all-libcody; hard=true; };
dependencies = { module=all-gcc; on=all-libdecnumber; hard=true; };
dependencies = { module=all-gcc; on=all-libiberty; };
dependencies = { module=all-gcc; on=all-fixincludes; };

File diff suppressed because it is too large Load Diff

2
configure vendored
View File

@ -2787,7 +2787,7 @@ build_tools="build-texinfo build-flex build-bison build-m4 build-fixincludes"
# these libraries are used by various programs built for the host environment
#f
host_libs="intl libiberty opcodes bfd readline tcl tk itcl libgui zlib libbacktrace libcpp libdecnumber gmp mpfr mpc isl libelf libiconv libctf"
host_libs="intl libiberty opcodes bfd readline tcl tk itcl libgui zlib libbacktrace libcpp libcody libdecnumber gmp mpfr mpc isl libelf libiconv libctf"
# these tools are built for the host environment
# Note, the powerpc-eabi build depends on sim occurring before gdb in order to

View File

@ -132,7 +132,7 @@ build_tools="build-texinfo build-flex build-bison build-m4 build-fixincludes"
# these libraries are used by various programs built for the host environment
#f
host_libs="intl libiberty opcodes bfd readline tcl tk itcl libgui zlib libbacktrace libcpp libdecnumber gmp mpfr mpc isl libelf libiconv libctf"
host_libs="intl libiberty opcodes bfd readline tcl tk itcl libgui zlib libbacktrace libcpp libcody libdecnumber gmp mpfr mpc isl libelf libiconv libctf"
# these tools are built for the host environment
# Note, the powerpc-eabi build depends on sim occurring before gdb in order to

View File

@ -412,6 +412,9 @@ enable_as_accelerator = @enable_as_accelerator@
CPPLIB = ../libcpp/libcpp.a
CPPINC = -I$(srcdir)/../libcpp/include
CODYLIB = ../libcody/libcody.a
CODYINC = -I$(srcdir)/../libcody
# Where to find decNumber
enable_decimal_float = @enable_decimal_float@
DECNUM = $(srcdir)/../libdecnumber
@ -982,6 +985,7 @@ SYSTEM_H = system.h hwint.h $(srcdir)/../include/libiberty.h \
PREDICT_H = predict.h predict.def
CPPLIB_H = $(srcdir)/../libcpp/include/line-map.h \
$(srcdir)/../libcpp/include/cpplib.h
CODYLIB_H = $(srcdir)/../libcody/cody.hh
INPUT_H = $(srcdir)/../libcpp/include/line-map.h input.h
OPTS_H = $(INPUT_H) $(VEC_H) opts.h $(OBSTACK_H)
SYMTAB_H = $(srcdir)/../libcpp/include/symtab.h $(OBSTACK_H)
@ -1102,7 +1106,7 @@ BUILD_ERRORS = build/errors.o
# libintl.h will be found in ../intl if we are using the included libintl.
INCLUDES = -I. -I$(@D) -I$(srcdir) -I$(srcdir)/$(@D) \
-I$(srcdir)/../include @INCINTL@ \
$(CPPINC) $(GMPINC) $(DECNUMINC) $(BACKTRACEINC) \
$(CPPINC) $(CODYINC) $(GMPINC) $(DECNUMINC) $(BACKTRACEINC) \
$(ISLINC)
COMPILE.base = $(COMPILER) -c $(ALL_COMPILERFLAGS) $(ALL_CPPFLAGS) -o $@
@ -1714,7 +1718,7 @@ endif
ALL_HOST_OBJS = $(ALL_HOST_FRONTEND_OBJS) $(ALL_HOST_BACKEND_OBJS)
BACKEND = libbackend.a main.o libcommon-target.a libcommon.a \
$(CPPLIB) $(LIBDECNUMBER)
$(CPPLIB) $(CODYLIB) $(LIBDECNUMBER)
# This is defined to "yes" if Tree checking is enabled, which roughly means
# front-end checking.

121
libcody/CMakeLists.txt Normal file
View File

@ -0,0 +1,121 @@
# Top Level CMake file for libcody.
cmake_minimum_required(VERSION 3.4.3)
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
message(STATUS "No build type selected, default to MinSizeRel")
set(CMAKE_BUILD_TYPE MinSizeRel)
set(LIBCODY_ENABLE_ASSERTIONS 1)
endif()
string(TOUPPER "${CMAKE_BUILD_TYPE}" uppercase_CMAKE_BUILD_TYPE)
set(cmake_3_2_USES_TERMINAL USES_TERMINAL)
if( CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR AND NOT MSVC_IDE)
message(FATAL_ERROR "In-source builds are not allowed. ")
endif()
# message(STATUS "SRC ${CMAKE_SOURCE_DIR} CSRC : ${CMAKE_CURRENT_SOURCE_DIR} ")
# Add path for custom modules
set(CMAKE_MODULE_PATH
${CMAKE_MODULE_PATH}
"${CMAKE_CURRENT_SOURCE_DIR}/cmake"
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules")
# If we are building stand-alone, set up the names and versions.
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR
OR LIBCODY_STANDALONE_BUILD)
project(libcody CXX)
set(PACKAGE_NAME codylib)
set(LIBCODY_VERSION_MAJOR 0)
set(LIBCODY_VERSION_MINOR 0)
set(LIBCODY_VERSION_PATCH 1)
set(LIBCODY_VERSION_SUFFIX git)
set(LIBCODY_VERSION "${LIBCODY_VERSION_MAJOR}.${LIBCODY_VERSION_MINOR}.${LIBCODY_VERSION_PATCH}")
set(PACKAGE_VERSION "${LIBCODY_VERSION}-${LIBCODY_VERSION_SUFFIX}")
set(PACKAGE_STRING "${PACKAGE_NAME} ${PACKAGE_VERSION}")
set(PACKAGE_URL "https://github.com/urnathan/libcody")
set(PACKAGE_BUGREPORT "https://github.com/urnathan/libcody/issues")
set (GIT_REV "git" "-C" "${CMAKE_CURRENT_SOURCE_DIR}" "rev-parse" "--short=12" "HEAD")
execute_process(
COMMAND ${GIT_REV}
RESULT_VARIABLE HAD_ERROR
OUTPUT_VARIABLE CODY_REVISION
)
if (NOT HAD_ERROR)
string(REGEX REPLACE "\n$" "" CODY_REVISION "${CODY_REVISION}")
set (GIT_CHANGES "git" "-C" "${CMAKE_CURRENT_SOURCE_DIR}" "diff-index" "--quiet" "HEAD" "--")
execute_process(
COMMAND ${GIT_CHANGES}
RESULT_VARIABLE MOD_ERROR
OUTPUT_VARIABLE MOD_OUTPUT
)
if (MOD_ERROR)
set (CODY_REVISION "${CODY_REVISION}-modified")
endif ()
else()
set(CODY_REVISION, "unknown")
endif ()
set(LIBCODY_STANDALONE YES)
else()
set(LIBCODY_STANDALONE NO)
endif()
# We are using C++11
set (CMAKE_CXX_STANDARD 11)
message(STATUS "git revision ${CODY_REVISION} ")
option(CODY_CHECKING "Enable checking" ON)
# Address github issue #10
option(CODY_WITHEXCEPTIONS "Enable exceptions" OFF)
if (LIBCODY_STANDALONE)
include(CTest)
endif()
include(libcody-config-ix)
add_definitions(
-DPACKAGE_URL="${PACKAGE_URL}"
-DBUGURL="${PACKAGE_BUGREPORT}"
-DSRCDIR="${CMAKE_CURRENT_SOURCE_DIR}"
-DPACKAGE_NAME="${PACKAGE_NAME}"
-DPACKAGE_STRING="${PACKAGE_STRING}"
-DPACKAGE_VERSION="${LIBCODY_VERSION}"
-DREVISION="${CODY_REVISION}"
)
if (CODY_CHECKING)
add_definitions(-DNMS_CHECKING=1)
else()
add_definitions(-DNMS_CHECKING=0)
endif()
set(LIBCODY_SOURCES
buffer.cc
client.cc
fatal.cc
netclient.cc
netserver.cc
resolver.cc
packet.cc
server.cc)
if(LIBCODY_STANDALONE)
add_library(cody STATIC ${LIBCODY_SOURCES})
else()
message(STATUS "Configured for in-tree build of libcody as LLVMcody")
add_llvm_component_library(LLVMcody ${LIBCODY_SOURCES})
endif()
if (LIBCODY_STANDALONE)
set_target_properties(cody PROPERTIES PUBLIC_HEADER "cody.hh")
install(TARGETS cody
LIBRARY DESTINATION lib
PUBLIC_HEADER DESTINATION include
)
endif()

115
libcody/CODING.md Normal file
View File

@ -0,0 +1,115 @@
# Coding standard
I guess I should document this, it might not be obvious.
libcody is implemented in C++11. Because it's used in compiler
development, we can't use the latest and greatest.
The formatting is close to GNU, but with a few differences.
## Extensions to C++11
It uses __VA_OPT__ when available, falling back on GNU's variadic
macro `,#` extension. This is in the `Assert` macro, so one can have
multi-argument template instantiations there. Not that libcody does
that, but this is code I used elsewhere.
## GNU
The underlying formatting is GNU style. Here are a few notes about
things that commonly catches programmers unfamiliar with it is:
* Spaces between binary operators. Particularly in a function call,
between the name and the open paren:
```c++
Fn (a + b, ary[4], *ptr);
```
In general GNU style uses a lot more whitespace than Clang-style.
We're not trying to cram as much code as possible onto a page!
* Scope braces are always on a line of their own, indented by 2
spaces, if they're a sub-statement of an `if`, `for` or whatever:
```c++
if (bob)
{
Frob ();
Quux ();
}
```
Conditions and loops containing a single statement should not use `{}`.
FWIW this was my personal indentation scheme, before I even met GNU code!
* The same is true for a function definition body, except the
indentation is zero:
```c++
int Foo ()
noexcept // indented
{
return 0;
}
```
* Initialization bracing is not like scope bracing. There tends to be
more flexibility.
* Break lines at 80 chars, this should be /before/ the operator, not after:
```c++
a = (b
+ c);
ptr
->MemberFn (stuff);
Func
(arg);
```
Thus you can tell what lines are continued from the previous by
looking at their start. Use parens to control indentation.
If you find yourself wanting to break a line at `.`, don't.
Refactor your code to avoid needing that.
* Template instantiations and C++ casts should have no space before the `<`:
```c++
std::vector<int> k;
static_cast<T> (arg); // space before the ( though
```
* Pointer and reference types need a space before the `*` or `&`, if
the preceding token is ascii text (a cpp-identifier):
```
int *ptr;
int **ptr_ptr;
int *&pref = ptr;
```
See below a difference in qualifier placement.
* Code should compile without warnings.
## Not GNU
### Names
Unlike GNU code, variants of Camel Case are used. use `PascalCase`
for function, type and global variable names. Use `dromedaryCase` for
member variables. Block-scope vars can be `dromedaryCase` or
`snake_case`, your choice.
### Type qualifiers
Type qualifiers go after the thing they qualify. You have to do this
for pointers anyway, and read them inside-out, because, C Just being
consistent:
```c++
int const foo = 5; // constant int
int *const pfoo = nullptr; // constant pointer to int
```

10
libcody/CONTRIB.md Normal file
View File

@ -0,0 +1,10 @@
# A probably incomplete list of contributors
Thanks for the interest in this library!
* Iain Sandoe
Darwin testing and porting
* Martin Liska
Code cleanups

201
libcody/LICENSE Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

29
libcody/LICENSE.gcc Normal file
View File

@ -0,0 +1,29 @@
This instance of Libcody is licensed under the GPLv3 as part of
GCC. (See ../COPYING3.)
The documentation of this file itself is separate from libcody.
The GCC steering committee have declined to decide whether libcody may
be included in GCC as an external dependency licensed under the Apache
v2 license.
In order to progress C++20 modules, this instance is relicensed as
part of GCC under the GPLv3 and assigned to the FSF. This version is
derived from libcody upstream b79dbea with contributions from jjravi
removed. Specifically the bulk of the code is authored by Nathan
Sidwell (me), and some portability issues fixed by Iain Sandoe. Iain
has blessed this contribution in a private communication to me. A
trivial install fix from Johel Peña is included here, along with typo
fixes to README.md from Boris Kolpackov (I do not think either of
those contains copyrightable work).
To the extent that Apache V2 prevents it, you may not update to other
upstream versions (past or future) of libcody and relicensing and/or
reassigning copyright of that update without reobtaining the authors'
permission. You may of course update to other versions of libcody
but keep the Apache license and libcody's copyright assignment. [This
is merely pointing out the implications of the Apache V2 license and
libcody's copyright, not adding additional requirements.]
Any patches applied to this instance should be provided to upstream
libcody, for consideration there.

187
libcody/Makefile.in Normal file
View File

@ -0,0 +1,187 @@
# Nathan's generic Makefile -*- mode:Makefile -*-
# Copyright (C) 2019-2020 Nathan Sidwell, nathan@acm.org
# License: Apache v2.0
ifeq (0,$(MAKELEVEL))
ifneq (,@tools@)
$(info Prepending @tools@/bin to PATH)
PATH := @tools@/bin:$(PATH)
export PATH
endif
ifeq (,$(SERIAL))
# Figure out if we should set parallelism
ifeq (,$(filter clean%,$(MAKECMDGOALS)))
PARALLELISM := @NUM_CPUS@
endif
endif
endif
ifeq (00,$(MAKELEVEL)$(if $(PARALLELISM),0,1))
# Although Make 4.3 documentation suggests I can set parallelism just
# by appending to MAKEFLAGS, it doesn't seem to work. It's also not
# possible to figure out the current Make invocation's parallelism,
# the -j option doesn't appear in MAKEFLAGS and is magically inserted
# when that is expanded in a rule. I can't figure how to get a rule
# expansion into a variable to test. Fortunately, Make propagates an
# incoming -j option rather than the one you attempted to append
$(info Parallelizing $(PARALLELISM) ways)
MAKEFLAGS += -j$(PARALLELISM)
ifneq (,$(MAKECMDGOALS))
$(MAKECMDGOALS): recurse
endif
recurse:
$(MAKE) -r$(MAKEFLAGS) $(MAKECMDGOALS)
.PHONY: recurse
else
srcdir := @srcdir@
prefix := @prefix@
exec_prefix := @exec_prefix@
bindir := @bindir@
libdir := @libdir@
includedir := @includedir@
SUBDIRS := @SUBDIRS@
# autoconf doesn't seem to like setting SHELL
SHELL := $(shell which zsh 2>/dev/null >/dev/null && echo zsh \
|| (echo "No ZSH, maybe flakey" >&2 && echo sh))
# We have to place the -I paths last, so that building will see -I paths to us
CXX := $(filter-out -I%,@CXX@)
AR := @AR@
INSTALL := $(srcdir)/build-aux/install-sh
# C++ compiler options
CXXFLAGS := @CXXFLAGS@
CXXINC := $(filter -I%,@CXX@)
CXXOPTS := $(CXXFLAGS)
ifeq ($(notdir $(firstword $(CXX))),g++)
# It's GCC, or pretending to be it -- so it better smell like it!
# Code generation
CXXOPTS += -fno-enforce-eh-specs
CXXOPTS += -fno-stack-protector -fno-threadsafe-statics
ifneq (@EXCEPTIONS@,yes)
CXXOPTS += -fno-exceptions -fno-rtti
endif
ifeq ($(filter -fdebug-prefix-map=%,$(CXXOPTS)),)
CXXOPTS += -fdebug-prefix-map=${srcdir}/=
endif
# Warning options
CXXOPTS += -W -Wall -Woverloaded-virtual -Wshadow
CXXOPTS += -Wno-invalid-offsetof -Wno-unused-variable
CXXOPTS += -Wno-missing-field-initializers
# Diagnostic options, look at controlling terminal so that piping
# through more works
MLEN := $(shell stty size </dev/tty 2>/dev/null | cut -d' ' -f2)
ifneq (,$(MLEN))
CXXOPTS += -fmessage-length=$(MLEN)
endif
CXXOPTS += -fdiagnostics-color=always -fno-diagnostics-show-option
else
ifeq ($(notdir $(firstword $(CXX))),clang++)
CXXOPTS += -fno-stack-protector -fno-threadsafe-statics
ifneq (@EXCEPTIONS@,yes)
CXXOPTS += -fno-exceptions -fno-rtti
endif
# Warning options
CXXOPTS += -W -Wall -Woverloaded-virtual -Wshadow
CXXOPTS += -Wno-invalid-offsetof -Wno-unused-variable
CXXOPTS += -Wno-missing-field-initializers
else
# Add different compiler's options here
endif
endif
# Config
CXXOPTS += $(filter-out -DHAVE_CONFIG_H,@DEFS@) -include config.h
# Linker options
LDFLAGS := -L. @LDFLAGS@
LIBS := @LIBS@
# Per-source & per-directory compile flags (warning: recursive)
SRC_CXXFLAGS = $(CXXFLAGS$(patsubst $(srcdir)%,%,$1)) \
$(if $(filter-out $(srcdir)/,$1),\
$(call $0,$(dir $(patsubst %/,%,$1))))
ifneq ($(MAINTAINER),)
override MAINTAINER += $1
endif
ifeq (@MAINTAINER@,yes)
MAINTAINER = $2
else
MAINTAINER = \# --enable-maintainer-mode to rebuild $1, or make MAINTAINER=touch
endif
vpath %.in $(srcdir)
vpath %.cc $(srcdir)
.SUFFIXES: .o .cc
%.o: %.cc
@mkdir -p $(dir $@)
$(CXX) $(strip $(CXXOPTS) $(call SRC_CXXFLAGS,$<) $(CXXINC)) \
-MMD -MP -MF ${@:.o=.d} -c -o $@ $<
all:: Makefile $(addprefix all.,$(SUBDIRS))
check:: Makefile $(addprefix check.,$(SUBDIRS))
clean:: Makefile $(addprefix clean.,$(SUBDIRS))
revision.stamp: $(addprefix $(srcdir)/,. $(SUBDIRS))
@revision=$$(git -C $(srcdir) rev-parse HEAD 2>/dev/null) ;\
if test -n "$$revision" ;\
then revision=git-$$revision ;\
if git -C $(srcdir) status --porcelain 2>/dev/null | grep -vq '^ ' ;\
then revision+=M ;\
fi ;\
else revision=unknown ;\
fi ;\
echo $$revision > $@
revision: revision.stamp
@cmp -s $< $@ || cp -f $< $@
clean::
rm -f revision.stamp revision
distclean:: clean
rm -f config.log config.status
rm -rf $(SUBDIRS)
$(srcdir)/configure: $(srcdir)/configure.ac \
$(patsubst %,$(srcdir)/%/config.m4,. $(SUBDIRS))
$(call MAINTAINER,$@,cd $(@D) && autoconf -W all,error)
$(srcdir)/config.h.in: $(srcdir)/configure.ac \
$(patsubst %,$(srcdir)/%/config.m4,. $(SUBDIRS))
$(call MAINTAINER,$@,cd $(@D) && autoheader -f -W all,error)
config.h: config.status config.h.in
./$< --header=$@
touch $@
ifeq ($(filter %clean,$(MAKECMDGOALS)),)
@CONFIG_FILES@: %: config.status %.in
./$< --file=$@
touch $@
endif
config.status: $(srcdir)/configure $(srcdir)/config.h.in
if test -x $@; then ./$@ -recheck; else $< @configure_args@; fi
distclean:: clean
rm -f config.h
maintainer-clean:: distclean
rm -f $(srcdir)/config.h.in
clean::
rm -f $(shell find $(srcdir) -name '*~')
.PHONY: all check clean distclean maintainer-clean
-include $(addsuffix /Makesub,. $(SUBDIRS))
-include $(addsuffix /tests/Makesub,. $(SUBDIRS))
endif

48
libcody/Makesub.in Normal file
View File

@ -0,0 +1,48 @@
# CODYlib -*- mode:Makefile -*-
# Copyright (C) 2019-2020 Nathan Sidwell, nathan@acm.org
# License: Apache v2.0
DOXYGEN := @DOXYGEN@
CXXFLAGS/ := -I$(srcdir)
LIBCODY.O := buffer.o client.o fatal.o netclient.o netserver.o \
resolver.o packet.o server.o
all:: .gdbinit
.gdbinit: gdbinit
rm -f $@ ; ln -s $< $@
clean::
rm -f gdbinit .gdbinit
all:: libcody.a
libcody.a: $(LIBCODY.O)
$(AR) -cr $@ $^
clean::
rm -f $(LIBCODY.O) $(LIBCODY.O:.o=.d)
rm -f libcody.a
CXXFLAGS/fatal.cc = -DREVISION='"$(shell cat revision)"' -DSRCDIR='"$(srcdir)"'
fatal.o: Makefile revision
doc:: dox.cfg
ifneq ($(DOXYGEN),:)
cd $(<D); $(DOXYGEN) $(<F) >&dox.log
else
@echo "doxygen not present, documentation not built"
endif
clean::
rm -rf dox dox.log
install::
$(INSTALL) -d $(libdir) $(includedir)
$(INSTALL) libcody.a $(libdir)
$(INSTALL) $(srcdir)/cody.hh $(includedir)
ifeq ($(filter clean%,$(MAKECMDGOALS)),)
-include $(LIBCODY.O:.o=.d)
endif

497
libcody/README.md Normal file
View File

@ -0,0 +1,497 @@
# libCODY: COmpiler DYnamism<sup><a href="#1">1</a></sup>
Copyright (C) 2020 Nathan Sidwell, nathan@acm.org
libCODY is an implementation of a communication protocol between
compilers and build systems.
**WARNING:** This is preliminary software.
In addition to supporting C++modules, this may also support LTO
requirements and could also deal with generated #include files
and feed the compiler with prepruned include paths and whatnot. (The
system calls involved in include searches can be quite expensive on
some build infrastructures.)
* Client and Server objects
* Direct connection for in-process use
* Testing with Joust (that means nothing to you, doesn't it!)
## Problem Being Solved
The origin is in C++20 modules:
```
import foo;
```
At that import, the compiler needs<sup><a href="#2">2</a></sup> to
load up the compiled serialization of module `foo`. Where is that
file? Does it even exist? Unless the build system already knows the
dependency graph, this might be a completely unknown module. Now, the
build system knows how to build things, but it might not have complete
information about the dependencies. The ultimate source of
dependencies is the source code being compiled, and specifying the
same thing in multiple places is a recipe for build skew.
Hence, a protocol by which a compiler can query a build system. This
was originally described in <a
href="https://wg21.link/p1184r1">p1184r1:A Module Mapper</a>. Along
with a proof-of-concept hack in GNUmake, described in <a
href="https://wg21.link/p1602">p1602:Make Me A Module</a>. The current
implementation has evolved and an update to p1184 will be forthcoming.
## Packet Encoding
The protocol is turn-based. The compiler sends a block of one or more
requests to the builder, then waits for a block of responses to all of
those requests. If the builder needs to compile something to satisfy
a request, there may be some time before the response. A builder may
service multiple compilers concurrently, each as a separate connection.
When multiple requests are in a block, the responses are also in a
block, and in corresponding order. The responses must not be
commenced eagerly -- they must wait until the incoming block has ended
(as mentioned above, it is turn-based). To do otherwise risks
deadlock, as there is no requirement for a sending end of the
communication to listen for incoming responses (or new requests) until
it has completed sending its current block.
Every request has a response.
Requests and responses are user-readable text. It is not intended as
a transmission medium to send large binary objects (such as compiled
modules). It is presumed the builder and the compiler share a file
system, for that kind of thing.<sup><a href="#3">3</a></sup>
Messages characters are encoded in UTF8.
Messages are a sequence of octets ending with a NEWLINE (0xa). The lines
consist of a sequence of words, separated by WHITESPACE (0x20 or 0x9).
Words themselves do not contain WHITESPACE. Lines consisting solely
of WHITESPACE (or empty) are ignored.
To encode a block of multiple messages, non-final messages end with a
single word of SEMICOLON (0x3b), immediately before the NEWLINE. Thus
a serial connection can determine whether a block is complete without
decoding the messages.
Words containing characters in the set [-+_/%.A-Za-z0-9] need not be
quoted. Words containing characters outside that set should be
quoted. A zero-length word may be achieved with `''`
Quoted words begin and end with APOSTROPHE (x27). Within the quoted
word, BACKSLASH (x5c) is used as an escape mechanism, with the
following meanings:
* \\n - NEWLINE (0xa)
* \\t - TAB (0x9)
* \\' - APOSTROPHE (')
* \\\\ - BACKSLASH (\\)
Characters in the range [0x00, 0x20) and 0x7f are encoded with one or
two lowercase hex characters. Octets in the range [0x80,0xff) are
UTF8 encodings of unicode characters outside the traditional ASCII set
and passed as such.
Decoding should be more relaxed. Unquoted words containing characters
in the range [0x20,0xff] other than BACKSLASH or APOSTROPHE should be
accepted. In a quoted sequence, `\` followed by one or two lower case
hex characters decode to that octet. Further, words can be
constructed from a mixture of abutted quoted and unquoted sequences.
For instance `FOO' 'bar` would decode to the word `FOO bar`.
Notice that the block continuation marker of `;` is not a valid
encoding of the word `;`, which would be `';'`.
It is recommended that words are separated by single SPACE characters.
## Messages
The message descriptions use `$metavariable` examples.
The request messages are specific to a particular action. The response
messages are more generic, describing their value types, but not their
meaning. Message consumers need to know the response to decode them.
Notice the `Packet::GetRequest()` method records in response packets
what the request being responded to was. Do not confuse this with the
`Packet::GetCode ()` method.
### Responses
The simplest response is a single:
`OK`
This indicates the request was successful.
An error response is:
`ERROR $message`
The message is a human-readable string. It indicates failure of the request.
Pathnames are encoded with:
`PATHNAME $pathname`
Boolean responses use:
`BOOL `(`TRUE`|`FALSE`)
### Handshake Request
The first message is a handshake:
`HELLO $version $compiler $ident`
The `$version` is a numeric value, currently `1`. `$compiler` identifies
the compiler &mdash; builders may need to keep compiled modules from
different compilers separate. `$ident` is an identifier the builder
might use to identify the compilation it is communicating with.
Responses are:
`HELLO $version $builder [$flags]`
A successful handshake. The communication is now connected and other
messages may be exchanged. An ERROR response indicates an unsuccessful
handshake. The communication remains unconnected.
There is nothing restricting a handshake to its own message block. Of
course, if the handshake fails, subsequent non-handshake messages in
the block will fail (producing error responses).
The `$flags` word, if present allows a server to control what requests
might be given. See below.
### C++ Module Requests
A set of requests are specific to C++ modules:
#### Flags
Several requests and one response have an optional `$flags` word.
These are the `Cody::Flags` value pertaining to that request. If
omitted the value 0 is implied. The following flags are available:
* `0`, `None`: No flags.
* `1<<0`, `NameOnly`: The request is for the name only, and not the
CMI contents.
The `NameOnly` flag may be provded in a handshake response, and
indicates that the server is interested in requests only for their
implied dependency information. It may be provided on a request to
indicate that only the CMI name is required, not its contents (for
instance, when preprocessing). Note that a compiler may still make
`NameOnly` requests even if the server did not ask for such.
#### Repository
All relative CMI file names are relative to a repository. (There are
usually no absolute CMI files). The repository may be determined
with:
`MODULE-REPO`
A PATHNAME response is expected. The `$pathname` may be an empty
word, which is equivalent to `.`. When the response is a relative
pathname, it must be relative to the client's current working
directory (which might be a process on a different host to the
server). You may set the repository to `/`, if you with to use paths
relative to the root directory.
#### Exporting
A compilation of a module interface, partition or header unit can
inform the builder with:
`MODULE-EXPORT $module [$flags]`
This will result in a PATHNAME response naming the Compiled Module
Interface pathname to write.
The `MODULE-EXPORT` request does not indicate the module has been
successfully compiled. At most one `MODULE-EXPORT` is to be made, and
as the connection is for a single compilation, the builder may infer
dependency relationships between the module being generated and import
requests made.
Named module names and header unit names are distinguished by making
the latter unambiguously look like file names. Firstly, they must be
fully resolved according to the compiler's usual include path. If
that results in an absolute name file name (beginning with `/`, or
certain other OS-specific sequences), all is well. Otherwise a
relative file name must be prefixed by `./` to be distinguished from a
similarly named named module. This prefixing must occur, even if the
header-unit's name contains characters that cannot appear in a named
module's name.
It is expected that absolute header-unit names convert to relative CMI
names, to keep all CMIs within the CMI repository. This means that
steps must be taken to distinguish the CMIs for `/here` from `./here`,
and this can be achieved by replacing the leading `./` directory with
`,/`, which is visually similar but does not have the self-reference
semantics of dot. Likewise, header-unit names containing `..`
directories, can be remapped to `,,`. (When symlinks are involved
`bob/dob/..` might not be `bob`, of course.) C++ header-unit
semantics are such that there is no need to resolve multiple ways of
spelling a particular header-unit to a unique CMI file.
Successful compilation of an interface is indicated with a subsequent:
`MODULE-COMPILED $module [$flags]`
request. This indicates the CMI file has been written to disk, so
that any other compilations waiting on it may proceed. Depending on
compiler implementation, the CMI may be written before the compilation
completes. A single OK response is expected.
Compilation failure can be inferred by lack of a `MODULE-COMPILED`
request. It is presumed the builder can determine this, as it is also
responsible for launching and reaping the compiler invocations
themselves.
#### Importing
Importation, including that of header-units, uses:
`MODULE-IMPORT $module [$flags]`
A PATHNAME response names the CMI file to be read. Should the builder
have to invoke a compilation to produce the CMI, the response should
be delayed until that occurs. If such a compilation fails, an error
response should be provided to the requestor &mdash; which will then
presumably fail in some manner.
#### Include Translation
Include translation can be determined with:
`INCLUDE-TRANSLATE $header [$flags]`
The header name, `$header`, is the fully resolved header name, in the
above-mentioned unambiguous filename form. The response will either
be a BOOL response indicating textual inclusion, or a PATHNAME
response naming the CMI for such translation. The BOOL value is TRUE,
if the header is known to be a textual header, and FALSE if nothing is
known about it -- the latter might cause diagnostics about incomplete
knowledge.
### GCC LTO Messages
These set of requests are used for GCC LTO jobserver integration with GNU Make
## Building libCody
Libcody is written in C++11. (It's a intended for compilers, so
there'd be a bootstrapping problem if it used the latest and greatest.)
### Using configure and make.
It supports the usual `configure`, `make`, `make check` & `make install`
sequence. It does not support building in the source directory --
that just didn't drop out, and it's not how I build things (because,
again, for compilers). Excitingly it uses my own `joust` test
harness, so you'll need to build and install that somewhere, if you
want the comfort of testing.
The following configure options are available, in addition to the usual set:
* `--enable-checking` Compile with assert-like checking. Defaults to on.
* `--with-tooldir=DIR` Prepend `DIR` to `PATH` when building (`DIR`
need not already include the trailing `/bin`, and the right things
happen). Use this if you need to point to non-standard tools that
you usually don't have in your path. This path is also used when
the configure script searches for programs.
* `--with-toolinc=DIR`, `--with-toollib=DIR`, include path and library
path variants of `--with-tooldir`. If these are siblings of the
tool bin directory, they'll be found automatically.
* `--with-compiler=NAME` Specify a particular compiler to use.
Usually what configure finds is sufficiently usable.
* `--with-bugurl=URL` Override the bugreporting URL. Do this if
you're providing libcody as part of a package that /you/ are
supporting.
* `--enable-maintainer-mode` Specify that rules to rebuild things like
`configure` (with `autoconf`) should be enabled. When not enabled,
you'll get a message if these appear out of date, but that can
happen naturally after an update or clone as `git`, in common with
other VCs, doesn't preserve the relative ordering of file
modifications. You can use `make MAINTAINER=touch` to shut make up,
if this occurs (or manually execute the `autoconf` and related
commands).
When building, you can override the default optimization flags with
`CXXFLAGS=$flags`. I often build a debuggable library with `make
CXXFLAGS=-g3`.
The `Makefile` will also parallelize according to the number of CPUs,
unless you specify explicitly with a `-j` option. This is a little
clunky, as it's not possible to figure out inside the makefile whether
the user provided `-j`. (Or at least I've not figured out how.)
### Using cmake and make
#### In the clang/LLVM project
The primary motivation for a cmake implementation is to allow building
libcody "in tree" in clang/LLVM. In that case, a checkout of libcody
can be placed (or symbolically linked) into clang/tools. This will
configure and build the library along with other LLVM dependencies.
*NOTE* This is not treated as an installable entity (it is present only
for use by the project).
*NOTE* The testing targets would not be appropriate in this configuration;
it is expected that lit-based testing of the required functionality will be
done by the code using the library.
#### Stand-alone
For use on platforms that don't support configure & make effectively, it
is possible to use the cmake & make process in stand-alone mode (similar
to the configure & make process above).
An example use.
```
cmake -DCMAKE_INSTALL_PREFIX=/path/to/installation -DCMAKE_CXX_COMPILER=clang++ /path/to/libcody/source
make
make install
```
Supported flags (additions to the usual cmake ones).
* `-DCODY_CHECKING=ON,OFF`: Compile with assert-like checking. (defaults ON)
* `-DCODY_WITHEXCEPTIONS=ON,OFF`: Compile with C++ exceptions and RTTI enabled.
(defaults OFF, to be compatible with GCC and LLVM).
*TODO*: At present there is no support for `ctest` integration (this should be
feasible, provided that `joust` is installed and can be discovered by `cmake`).
## API
The library defines entities in the `::Cody` namespace.
There are 4 user-visible classes:
* `Packet`: Responses to requests are `Packets`. These have a code,
indicating the response kind, and a payload.
* `Client`: The compiler-end of a connection. Requests may be made
and responses are returned.
* `Server`: The builder-end of a connection. Requests may be waited
for, and responses made. Builders that serve multiple concurrent
connections and spawn compilations to resolve dependencies may need
to derive from this class to provide response queuing.
* `Resolver`: The processing engine of the builder side. User code is
expected to derive from this class and provide virtual function
overriders to affect the semantics of the resolver.
In addition there are a number of helpers to setup connections.
Logically the Client and the Server communicate via a sequential
channel. The channel may be provided by:
* two pipes, with different file descriptors for reading and writing
at each end.
* a socket, which will use the same file descriptor for reading and
writing. the socket can be created in a number of ways, including
Unix domain and IPv6 TCP, for which helpers are provided.
* a direct, in-process, connection, using buffer swapping.
The communication channel is presumed reliable.
Refer to the (currently very sparse) doxygen-generated documentation
for details of the API.
## Examples
To create an in-process resolver, use the following boilerplate:
```
class MyResolver : Cody::Resolver { ... stuff here ... };
Cody::Client *MakeClient (char const *maybe_ident)
{
auto *r = new MyResolver (...);
auto *s = new Cody::Server (r);
auto *c = new Cody::Client (s);
auto t = c->ConnectRequest ("ME", maybe_ident);
if (t.GetCode () == Cody::Client::TC_CONNECT)
;// Yay!
else if (t.GetCode () == Cody::Client::TC_ERROR)
report_error (t.GetString ());
return c;
}
```
For a remotely connecting client:
```
Cody::Client *MakeClient ()
{
char const *err = nullptr;
int fd = OpenInet6 (char const **err, name, port);
if (fd < 0)
{ ... error... return nullptr;}
auto *c = new Cody::Client (fd);
auto t = c->ConnectRequest ("ME", maybe_ident);
if (t.GetCode () == Cody::Client::TC_CONNECT)
;// Yay!
else if (t.GetCode () == Cody::Client::TC_ERROR)
report_error (t.GetString ());
return c;
}
```
# Future Directions
* Current Directory. There is no mechanism to check the builder and
the compiler have the same working directory. Perhaps that should
be addressed.
* Include path canonization and/or header file lookup. This can be
expensive, particularly with many `-I` options, due to the system
calls. Perhaps using a common resource would be cheaper?
* Generated header file lookup/construction. This is essentially the
same problem as importing a module, and build systems are crap at
dealing with this.
* Link-time compilations. Another place the compiler would like to
ask the build system to do things.
* C++20 API entrypoints &mdash; std:string_view would be nice
* Exception-safety audit. Exceptions are not used, but memory
exhaustion could happen. And perhaps user's resolver code employs
exceptions?
<a name="1">1</a>: Or a small town in Wyoming
<a name="2">2</a>: This describes one common implementation technique.
The std itself doesn't require such serializations, but the ability to
create them is kind of the point. Also, 'compiler' is used where we
mean any consumer of a module, and 'build system' where we mean any
producer of a module.
<a name="3">3</a>: Even when the builder is managing a distributed set
of compilations, the builder must have a mechanism to get source files
to, and object files from, the compilations. That scheme can also
transfer the CMI files.

387
libcody/buffer.cc Normal file
View File

@ -0,0 +1,387 @@
// CODYlib -*- mode:c++ -*-
// Copyright (C) 2020 Nathan Sidwell, nathan@acm.org
// License: Apache v2.0
// Cody
#include "internal.hh"
// C++
#include <algorithm>
// C
#include <cstring>
// OS
#include <unistd.h>
#include <cerrno>
// MessageBuffer code
// Lines consist of words and end with a NEWLINE (0xa) char
// Whitespace characters are TAB (0x9) and SPACE (0x20)
// Words consist of non-whitespace chars separated by whitespace.
// Multiple lines in one transaction are indicated by ending non-final
// lines with a SEMICOLON (0x3b) word, immediately before the NEWLINE
// Continuations with ; preceding it
// Words matching regexp [-+_/%.a-zA-Z0-9]+ need no quoting.
// Quoting with '...'
// Anything outside of [-+_/%.a-zA-Z0-9] needs quoting
// Anything outside of <= <space> or DEL or \' or \\ needs escaping.
// Escapes are \\, \', \n, \t, \_, everything else as \<hex><hex>?
// Spaces separate words, UTF8 encoding for non-ascii chars
namespace Cody {
namespace Detail {
static const char CONTINUE = S2C(u8";");
void MessageBuffer::BeginLine ()
{
if (!buffer.empty ())
{
// Terminate the previous line with a continuation
buffer.reserve (buffer.size () + 3);
buffer.push_back (S2C(u8" "));
buffer.push_back (CONTINUE);
buffer.push_back (S2C(u8"\n"));
}
lastBol = buffer.size ();
}
// QUOTE means 'maybe quote', we search it for quote-needing chars
void MessageBuffer::Append (char const *str, bool quote, size_t len)
{
if (len == ~size_t (0))
len = strlen (str);
if (!len && !quote)
return;
// We want to quote characters outside of [-+_A-Za-z0-9/%.], anything
// that could remotely be shell-active. UTF8 encoding for non-ascii.
if (quote && len)
{
quote = false;
// Scan looking for quote-needing characters. We could just
// append until we find one, but that's probably confusing
for (size_t ix = len; ix--;)
{
unsigned char c = (unsigned char)str[ix];
if (!((c >= S2C(u8"a") && c <= S2C(u8"z"))
|| (c >= S2C(u8"A") && c <= S2C(u8"Z"))
|| (c >= S2C(u8"0") && c <= S2C(u8"9"))
|| c == S2C(u8"-") || c == S2C(u8"+") || c == S2C(u8"_")
|| c == S2C(u8"/") || c == S2C(u8"%") || c == S2C(u8".")))
{
quote = true;
break;
}
}
}
// Maximal length of appended string
buffer.reserve (buffer.size () + len * (quote ? 3 : 1) + 2);
if (quote)
buffer.push_back (S2C(u8"'"));
for (auto *end = str + len; str != end;)
{
auto *e = end;
if (quote)
// Look for next escape-needing char. More relaxed than
// the earlier needs-quoting check.
for (e = str; e != end; ++e)
{
unsigned char c = (unsigned char)*e;
if (c < S2C(u8" ") || c == 0x7f
|| c == S2C(u8"\\") || c == S2C(u8"'"))
break;
}
buffer.insert (buffer.end (), str, e);
str = e;
if (str == end)
break;
buffer.push_back (S2C(u8"\\"));
switch (unsigned char c = (unsigned char)*str++)
{
case S2C(u8"\t"):
c = S2C(u8"t");
goto append;
case S2C(u8"\n"):
c = S2C(u8"n");
goto append;
case S2C(u8"'"):
case S2C(u8"\\"):
append:
buffer.push_back (c);
break;
default:
// Full-on escape. Use 2 lower-case hex chars
for (unsigned shift = 8; shift;)
{
shift -= 4;
char nibble = (c >> shift) & 0xf;
nibble += S2C(u8"0");
if (nibble > S2C(u8"9"))
nibble += S2C(u8"a") - (S2C(u8"9") + 1);
buffer.push_back (nibble);
}
}
}
if (quote)
buffer.push_back (S2C(u8"'"));
}
void MessageBuffer::Append (char c)
{
buffer.push_back (c);
}
void MessageBuffer::AppendInteger (unsigned u)
{
std::string v (std::to_string (u));
AppendWord (v);
}
int MessageBuffer::Write (int fd) noexcept
{
size_t limit = buffer.size () - lastBol;
ssize_t count = write (fd, &buffer.data ()[lastBol], limit);
int err = 0;
if (count < 0)
err = errno;
else
{
lastBol += count;
if (size_t (count) != limit)
err = EAGAIN;
}
if (err != EAGAIN && err != EINTR)
{
// Reset for next message
buffer.clear ();
lastBol = 0;
}
return err;
}
int MessageBuffer::Read (int fd) noexcept
{
constexpr size_t blockSize = 200;
size_t lwm = buffer.size ();
size_t hwm = buffer.capacity ();
if (hwm - lwm < blockSize / 2)
hwm += blockSize;
buffer.resize (hwm);
auto iter = buffer.begin () + lwm;
ssize_t count = read (fd, &*iter, hwm - lwm);
buffer.resize (lwm + (count >= 0 ? count : 0));
if (count < 0)
return errno;
if (!count)
// End of file
return -1;
bool more = true;
for (;;)
{
auto newline = std::find (iter, buffer.end (), S2C(u8"\n"));
if (newline == buffer.end ())
break;
more = newline != buffer.begin () && newline[-1] == CONTINUE;
iter = newline + 1;
if (iter == buffer.end ())
break;
if (!more)
{
// There is no continuation, but there are chars after the
// newline. Truncate the buffer and return an error
buffer.resize (iter - buffer.begin ());
return EINVAL;
}
}
return more ? EAGAIN : 0;
}
int MessageBuffer::Lex (std::vector<std::string> &result)
{
result.clear ();
int err = ENOENT;
if (IsAtEnd ())
return ENOENT;
Assert (buffer.back () == S2C(u8"\n"));
auto iter = buffer.begin () + lastBol;
for (std::string *word = nullptr;;)
{
char c = *iter;
++iter;
if (c == S2C(u8" ") || c == S2C(u8"\t"))
{
word = nullptr;
continue;
}
if (c == S2C(u8"\n"))
break;
if (c == CONTINUE)
{
// Line continuation
if (word || *iter != S2C(u8"\n"))
goto malformed;
++iter;
break;
}
if (c <= S2C(u8" ") || c >= 0x7f)
goto malformed;
if (!word)
{
result.emplace_back ();
word = &result.back ();
}
if (c == S2C(u8"'"))
{
// Quoted word
for (;;)
{
c = *iter;
if (c == S2C(u8"\n"))
{
malformed:;
result.clear ();
iter = std::find (iter, buffer.end (), S2C(u8"\n"));
auto back = iter;
if (back[-1] == CONTINUE && back[-2] == S2C(u8" "))
// Smells like a line continuation
back -= 2;
result.emplace_back (&buffer[lastBol],
back - buffer.begin () - lastBol);
++iter;
lastBol = iter - buffer.begin ();
return EINVAL;
}
if (c < S2C(u8" ") || c >= 0x7f)
goto malformed;
++iter;
if (c == S2C(u8"'"))
break;
if (c == S2C(u8"\\"))
// escape
switch (c = *iter)
{
case S2C(u8"\\"):
case S2C(u8"'"):
++iter;
break;
case S2C(u8"n"):
c = S2C(u8"\n");
++iter;
break;
case S2C(u8"_"):
// We used to escape SPACE as \_, so accept that
c = S2C(u8" ");
++iter;
break;
case S2C(u8"t"):
c = S2C(u8"\t");
++iter;
break;
default:
{
unsigned v = 0;
for (unsigned nibble = 0; nibble != 2; nibble++)
{
c = *iter;
if (c < S2C(u8"0"))
{
if (!nibble)
goto malformed;
break;
}
else if (c <= S2C(u8"9"))
c -= S2C(u8"0");
else if (c < S2C(u8"a"))
{
if (!nibble)
goto malformed;
break;
}
else if (c <= S2C(u8"f"))
c -= S2C(u8"a") - 10;
else
{
if (!nibble)
goto malformed;
break;
}
++iter;
v = (v << 4) | c;
}
c = v;
}
}
word->push_back (c);
}
}
else
// Unquoted character
word->push_back (c);
}
lastBol = iter - buffer.begin ();
if (result.empty ())
return ENOENT;
return 0;
}
void MessageBuffer::LexedLine (std::string &str)
{
if (lastBol)
{
size_t pos = lastBol - 1;
for (; pos; pos--)
if (buffer[pos-1] == S2C(u8"\n"))
break;
size_t end = lastBol - 1;
if (buffer[end-1] == CONTINUE && buffer[end-2] == S2C(u8" "))
// Strip line continuation
end -= 2;
str.append (&buffer[pos], end - pos);
}
}
} // Detail
} // Cody

1476
libcody/build-aux/config.guess vendored Executable file

File diff suppressed because it is too large Load Diff

1833
libcody/build-aux/config.sub vendored Executable file

File diff suppressed because it is too large Load Diff

518
libcody/build-aux/install-sh Executable file
View File

@ -0,0 +1,518 @@
#!/bin/sh
# install - install a program, script, or datafile
scriptversion=2018-03-11.20; # UTC
# This originates from X11R5 (mit/util/scripts/install.sh), which was
# later released in X11R6 (xc/config/util/install.sh) with the
# following copyright and license.
#
# Copyright (C) 1994 X Consortium
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC-
# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# Except as contained in this notice, the name of the X Consortium shall not
# be used in advertising or otherwise to promote the sale, use or other deal-
# ings in this Software without prior written authorization from the X Consor-
# tium.
#
#
# FSF changes to this file are in the public domain.
#
# Calling this script install-sh is preferred over install.sh, to prevent
# 'make' implicit rules from creating a file called install from it
# when there is no Makefile.
#
# This script is compatible with the BSD install script, but was written
# from scratch.
tab=' '
nl='
'
IFS=" $tab$nl"
# Set DOITPROG to "echo" to test this script.
doit=${DOITPROG-}
doit_exec=${doit:-exec}
# Put in absolute file names if you don't have them in your path;
# or use environment vars.
chgrpprog=${CHGRPPROG-chgrp}
chmodprog=${CHMODPROG-chmod}
chownprog=${CHOWNPROG-chown}
cmpprog=${CMPPROG-cmp}
cpprog=${CPPROG-cp}
mkdirprog=${MKDIRPROG-mkdir}
mvprog=${MVPROG-mv}
rmprog=${RMPROG-rm}
stripprog=${STRIPPROG-strip}
posix_mkdir=
# Desired mode of installed file.
mode=0755
chgrpcmd=
chmodcmd=$chmodprog
chowncmd=
mvcmd=$mvprog
rmcmd="$rmprog -f"
stripcmd=
src=
dst=
dir_arg=
dst_arg=
copy_on_change=false
is_target_a_directory=possibly
usage="\
Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE
or: $0 [OPTION]... SRCFILES... DIRECTORY
or: $0 [OPTION]... -t DIRECTORY SRCFILES...
or: $0 [OPTION]... -d DIRECTORIES...
In the 1st form, copy SRCFILE to DSTFILE.
In the 2nd and 3rd, copy all SRCFILES to DIRECTORY.
In the 4th, create DIRECTORIES.
Options:
--help display this help and exit.
--version display version info and exit.
-c (ignored)
-C install only if different (preserve the last data modification time)
-d create directories instead of installing files.
-g GROUP $chgrpprog installed files to GROUP.
-m MODE $chmodprog installed files to MODE.
-o USER $chownprog installed files to USER.
-s $stripprog installed files.
-t DIRECTORY install into DIRECTORY.
-T report an error if DSTFILE is a directory.
Environment variables override the default commands:
CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG
RMPROG STRIPPROG
"
while test $# -ne 0; do
case $1 in
-c) ;;
-C) copy_on_change=true;;
-d) dir_arg=true;;
-g) chgrpcmd="$chgrpprog $2"
shift;;
--help) echo "$usage"; exit $?;;
-m) mode=$2
case $mode in
*' '* | *"$tab"* | *"$nl"* | *'*'* | *'?'* | *'['*)
echo "$0: invalid mode: $mode" >&2
exit 1;;
esac
shift;;
-o) chowncmd="$chownprog $2"
shift;;
-s) stripcmd=$stripprog;;
-t)
is_target_a_directory=always
dst_arg=$2
# Protect names problematic for 'test' and other utilities.
case $dst_arg in
-* | [=\(\)!]) dst_arg=./$dst_arg;;
esac
shift;;
-T) is_target_a_directory=never;;
--version) echo "$0 $scriptversion"; exit $?;;
--) shift
break;;
-*) echo "$0: invalid option: $1" >&2
exit 1;;
*) break;;
esac
shift
done
# We allow the use of options -d and -T together, by making -d
# take the precedence; this is for compatibility with GNU install.
if test -n "$dir_arg"; then
if test -n "$dst_arg"; then
echo "$0: target directory not allowed when installing a directory." >&2
exit 1
fi
fi
if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then
# When -d is used, all remaining arguments are directories to create.
# When -t is used, the destination is already specified.
# Otherwise, the last argument is the destination. Remove it from $@.
for arg
do
if test -n "$dst_arg"; then
# $@ is not empty: it contains at least $arg.
set fnord "$@" "$dst_arg"
shift # fnord
fi
shift # arg
dst_arg=$arg
# Protect names problematic for 'test' and other utilities.
case $dst_arg in
-* | [=\(\)!]) dst_arg=./$dst_arg;;
esac
done
fi
if test $# -eq 0; then
if test -z "$dir_arg"; then
echo "$0: no input file specified." >&2
exit 1
fi
# It's OK to call 'install-sh -d' without argument.
# This can happen when creating conditional directories.
exit 0
fi
if test -z "$dir_arg"; then
if test $# -gt 1 || test "$is_target_a_directory" = always; then
if test ! -d "$dst_arg"; then
echo "$0: $dst_arg: Is not a directory." >&2
exit 1
fi
fi
fi
if test -z "$dir_arg"; then
do_exit='(exit $ret); exit $ret'
trap "ret=129; $do_exit" 1
trap "ret=130; $do_exit" 2
trap "ret=141; $do_exit" 13
trap "ret=143; $do_exit" 15
# Set umask so as not to create temps with too-generous modes.
# However, 'strip' requires both read and write access to temps.
case $mode in
# Optimize common cases.
*644) cp_umask=133;;
*755) cp_umask=22;;
*[0-7])
if test -z "$stripcmd"; then
u_plus_rw=
else
u_plus_rw='% 200'
fi
cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;;
*)
if test -z "$stripcmd"; then
u_plus_rw=
else
u_plus_rw=,u+rw
fi
cp_umask=$mode$u_plus_rw;;
esac
fi
for src
do
# Protect names problematic for 'test' and other utilities.
case $src in
-* | [=\(\)!]) src=./$src;;
esac
if test -n "$dir_arg"; then
dst=$src
dstdir=$dst
test -d "$dstdir"
dstdir_status=$?
else
# Waiting for this to be detected by the "$cpprog $src $dsttmp" command
# might cause directories to be created, which would be especially bad
# if $src (and thus $dsttmp) contains '*'.
if test ! -f "$src" && test ! -d "$src"; then
echo "$0: $src does not exist." >&2
exit 1
fi
if test -z "$dst_arg"; then
echo "$0: no destination specified." >&2
exit 1
fi
dst=$dst_arg
# If destination is a directory, append the input filename.
if test -d "$dst"; then
if test "$is_target_a_directory" = never; then
echo "$0: $dst_arg: Is a directory" >&2
exit 1
fi
dstdir=$dst
dstbase=`basename "$src"`
case $dst in
*/) dst=$dst$dstbase;;
*) dst=$dst/$dstbase;;
esac
dstdir_status=0
else
dstdir=`dirname "$dst"`
test -d "$dstdir"
dstdir_status=$?
fi
fi
case $dstdir in
*/) dstdirslash=$dstdir;;
*) dstdirslash=$dstdir/;;
esac
obsolete_mkdir_used=false
if test $dstdir_status != 0; then
case $posix_mkdir in
'')
# Create intermediate dirs using mode 755 as modified by the umask.
# This is like FreeBSD 'install' as of 1997-10-28.
umask=`umask`
case $stripcmd.$umask in
# Optimize common cases.
*[2367][2367]) mkdir_umask=$umask;;
.*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;;
*[0-7])
mkdir_umask=`expr $umask + 22 \
- $umask % 100 % 40 + $umask % 20 \
- $umask % 10 % 4 + $umask % 2
`;;
*) mkdir_umask=$umask,go-w;;
esac
# With -d, create the new directory with the user-specified mode.
# Otherwise, rely on $mkdir_umask.
if test -n "$dir_arg"; then
mkdir_mode=-m$mode
else
mkdir_mode=
fi
posix_mkdir=false
case $umask in
*[123567][0-7][0-7])
# POSIX mkdir -p sets u+wx bits regardless of umask, which
# is incompatible with FreeBSD 'install' when (umask & 300) != 0.
;;
*)
# Note that $RANDOM variable is not portable (e.g. dash); Use it
# here however when possible just to lower collision chance.
tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$
trap 'ret=$?; rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" 2>/dev/null; exit $ret' 0
# Because "mkdir -p" follows existing symlinks and we likely work
# directly in world-writeable /tmp, make sure that the '$tmpdir'
# directory is successfully created first before we actually test
# 'mkdir -p' feature.
if (umask $mkdir_umask &&
$mkdirprog $mkdir_mode "$tmpdir" &&
exec $mkdirprog $mkdir_mode -p -- "$tmpdir/a/b") >/dev/null 2>&1
then
if test -z "$dir_arg" || {
# Check for POSIX incompatibilities with -m.
# HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or
# other-writable bit of parent directory when it shouldn't.
# FreeBSD 6.1 mkdir -m -p sets mode of existing directory.
test_tmpdir="$tmpdir/a"
ls_ld_tmpdir=`ls -ld "$test_tmpdir"`
case $ls_ld_tmpdir in
d????-?r-*) different_mode=700;;
d????-?--*) different_mode=755;;
*) false;;
esac &&
$mkdirprog -m$different_mode -p -- "$test_tmpdir" && {
ls_ld_tmpdir_1=`ls -ld "$test_tmpdir"`
test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1"
}
}
then posix_mkdir=:
fi
rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir"
else
# Remove any dirs left behind by ancient mkdir implementations.
rmdir ./$mkdir_mode ./-p ./-- "$tmpdir" 2>/dev/null
fi
trap '' 0;;
esac;;
esac
if
$posix_mkdir && (
umask $mkdir_umask &&
$doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir"
)
then :
else
# The umask is ridiculous, or mkdir does not conform to POSIX,
# or it failed possibly due to a race condition. Create the
# directory the slow way, step by step, checking for races as we go.
case $dstdir in
/*) prefix='/';;
[-=\(\)!]*) prefix='./';;
*) prefix='';;
esac
oIFS=$IFS
IFS=/
set -f
set fnord $dstdir
shift
set +f
IFS=$oIFS
prefixes=
for d
do
test X"$d" = X && continue
prefix=$prefix$d
if test -d "$prefix"; then
prefixes=
else
if $posix_mkdir; then
(umask=$mkdir_umask &&
$doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break
# Don't fail if two instances are running concurrently.
test -d "$prefix" || exit 1
else
case $prefix in
*\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;;
*) qprefix=$prefix;;
esac
prefixes="$prefixes '$qprefix'"
fi
fi
prefix=$prefix/
done
if test -n "$prefixes"; then
# Don't fail if two instances are running concurrently.
(umask $mkdir_umask &&
eval "\$doit_exec \$mkdirprog $prefixes") ||
test -d "$dstdir" || exit 1
obsolete_mkdir_used=true
fi
fi
fi
if test -n "$dir_arg"; then
{ test -z "$chowncmd" || $doit $chowncmd "$dst"; } &&
{ test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } &&
{ test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false ||
test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1
else
# Make a couple of temp file names in the proper directory.
dsttmp=${dstdirslash}_inst.$$_
rmtmp=${dstdirslash}_rm.$$_
# Trap to clean up those temp files at exit.
trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0
# Copy the file name to the temp name.
(umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") &&
# and set any options; do chmod last to preserve setuid bits.
#
# If any of these fail, we abort the whole thing. If we want to
# ignore errors from any of these, just make sure not to ignore
# errors from the above "$doit $cpprog $src $dsttmp" command.
#
{ test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } &&
{ test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } &&
{ test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } &&
{ test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } &&
# If -C, don't bother to copy if it wouldn't change the file.
if $copy_on_change &&
old=`LC_ALL=C ls -dlL "$dst" 2>/dev/null` &&
new=`LC_ALL=C ls -dlL "$dsttmp" 2>/dev/null` &&
set -f &&
set X $old && old=:$2:$4:$5:$6 &&
set X $new && new=:$2:$4:$5:$6 &&
set +f &&
test "$old" = "$new" &&
$cmpprog "$dst" "$dsttmp" >/dev/null 2>&1
then
rm -f "$dsttmp"
else
# Rename the file to the real destination.
$doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null ||
# The rename failed, perhaps because mv can't rename something else
# to itself, or perhaps because mv is so ancient that it does not
# support -f.
{
# Now remove or move aside any old file at destination location.
# We try this two ways since rm can't unlink itself on some
# systems and the destination file might be busy for other
# reasons. In this case, the final cleanup might fail but the new
# file should still install successfully.
{
test ! -f "$dst" ||
$doit $rmcmd -f "$dst" 2>/dev/null ||
{ $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null &&
{ $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; }
} ||
{ echo "$0: cannot unlink or rename $dst" >&2
(exit 1); exit 1
}
} &&
# Now rename the file to the real destination.
$doit $mvcmd "$dsttmp" "$dst"
}
fi || exit 1
trap '' 0
fi
done
# Local variables:
# eval: (add-hook 'before-save-hook 'time-stamp)
# time-stamp-start: "scriptversion="
# time-stamp-format: "%:y-%02m-%02d.%02H"
# time-stamp-time-zone: "UTC0"
# time-stamp-end: "; # UTC"
# End:

336
libcody/client.cc Normal file
View File

@ -0,0 +1,336 @@
// CODYlib -*- mode:c++ -*-
// Copyright (C) 2020 Nathan Sidwell, nathan@acm.org
// License: Apache v2.0
// Cody
#include "internal.hh"
// C
#include <cerrno>
#include <cstring>
// Client code
namespace Cody {
// These do not need to be members
static Packet ConnectResponse (std::vector<std::string> &words);
static Packet PathnameResponse (std::vector<std::string> &words);
static Packet OKResponse (std::vector<std::string> &words);
static Packet IncludeTranslateResponse (std::vector<std::string> &words);
// Must be consistently ordered with the RequestCode enum
static Packet (*const responseTable[Detail::RC_HWM])
(std::vector<std::string> &) =
{
&ConnectResponse,
&PathnameResponse,
&PathnameResponse,
&PathnameResponse,
&OKResponse,
&IncludeTranslateResponse,
};
Client::Client ()
{
fd.from = fd.to = -1;
}
Client::Client (Client &&src)
: write (std::move (src.write)),
read (std::move (src.read)),
corked (std::move (src.corked)),
is_direct (src.is_direct),
is_connected (src.is_connected)
{
if (is_direct)
server = src.server;
else
{
fd.from = src.fd.from;
fd.to = src.fd.to;
}
}
Client::~Client ()
{
}
Client &Client::operator= (Client &&src)
{
write = std::move (src.write);
read = std::move (src.read);
corked = std::move (src.corked);
is_direct = src.is_direct;
is_connected = src.is_connected;
if (is_direct)
server = src.server;
else
{
fd.from = src.fd.from;
fd.to = src.fd.to;
}
return *this;
}
int Client::CommunicateWithServer ()
{
write.PrepareToWrite ();
read.PrepareToRead ();
if (IsDirect ())
server->DirectProcess (write, read);
else
{
// Write the write buffer
while (int e = write.Write (fd.to))
if (e != EAGAIN && e != EINTR)
return e;
// Read the read buffer
while (int e = read.Read (fd.from))
if (e != EAGAIN && e != EINTR)
return e;
}
return 0;
}
static Packet CommunicationError (int err)
{
std::string e {u8"communication error:"};
e.append (strerror (err));
return Packet (Client::PC_ERROR, std::move (e));
}
Packet Client::ProcessResponse (std::vector<std::string> &words,
unsigned code, bool isLast)
{
if (int e = read.Lex (words))
{
if (e == EINVAL)
{
std::string msg (u8"malformed string '");
msg.append (words[0]);
msg.append (u8"'");
return Packet (Client::PC_ERROR, std::move (msg));
}
else
return Packet (Client::PC_ERROR, u8"missing response");
}
Assert (!words.empty ());
if (words[0] == u8"ERROR")
return Packet (Client::PC_ERROR,
std::move (words.size () == 2 ? words[1]
: u8"malformed error response"));
if (isLast && !read.IsAtEnd ())
return Packet (Client::PC_ERROR,
std::string (u8"unexpected extra response"));
Assert (code < Detail::RC_HWM);
Packet result (responseTable[code] (words));
result.SetRequest (code);
if (result.GetCode () == Client::PC_ERROR && result.GetString ().empty ())
{
std::string msg {u8"malformed response '"};
read.LexedLine (msg);
msg.append (u8"'");
result.GetString () = std::move (msg);
}
else if (result.GetCode () == Client::PC_CONNECT)
is_connected = true;
return result;
}
Packet Client::MaybeRequest (unsigned code)
{
if (IsCorked ())
{
corked.push_back (code);
return Packet (PC_CORKED);
}
if (int err = CommunicateWithServer ())
return CommunicationError (err);
std::vector<std::string> words;
return ProcessResponse(words, code, true);
}
void Client::Cork ()
{
if (corked.empty ())
corked.push_back (-1);
}
std::vector<Packet> Client::Uncork ()
{
std::vector<Packet> result;
if (corked.size () > 1)
{
if (int err = CommunicateWithServer ())
result.emplace_back (CommunicationError (err));
else
{
std::vector<std::string> words;
for (auto iter = corked.begin () + 1; iter != corked.end ();)
{
char code = *iter;
++iter;
result.emplace_back (ProcessResponse (words, code,
iter == corked.end ()));
}
}
}
corked.clear ();
return result;
}
// Now the individual message handlers
// HELLO $vernum $agent $ident
Packet Client::Connect (char const *agent, char const *ident,
size_t alen, size_t ilen)
{
write.BeginLine ();
write.AppendWord (u8"HELLO");
write.AppendInteger (Version);
write.AppendWord (agent, true, alen);
write.AppendWord (ident, true, ilen);
write.EndLine ();
return MaybeRequest (Detail::RC_CONNECT);
}
// HELLO $version $agent [$flags]
Packet ConnectResponse (std::vector<std::string> &words)
{
if (words[0] == u8"HELLO" && (words.size () == 3 || words.size () == 4))
{
char *eptr;
unsigned long val = strtoul (words[1].c_str (), &eptr, 10);
unsigned version = unsigned (val);
if (*eptr || version != val || version < Version)
return Packet (Client::PC_ERROR, u8"incompatible version");
else
{
unsigned flags = 0;
if (words.size () == 4)
{
val = strtoul (words[3].c_str (), &eptr, 10);
flags = unsigned (val);
}
return Packet (Client::PC_CONNECT, flags);
}
}
return Packet (Client::PC_ERROR, u8"");
}
// MODULE-REPO
Packet Client::ModuleRepo ()
{
write.BeginLine ();
write.AppendWord (u8"MODULE-REPO");
write.EndLine ();
return MaybeRequest (Detail::RC_MODULE_REPO);
}
// PATHNAME $dir | ERROR
Packet PathnameResponse (std::vector<std::string> &words)
{
if (words[0] == u8"PATHNAME" && words.size () == 2)
return Packet (Client::PC_PATHNAME, std::move (words[1]));
return Packet (Client::PC_ERROR, u8"");
}
// OK or ERROR
Packet OKResponse (std::vector<std::string> &words)
{
if (words[0] == u8"OK")
return Packet (Client::PC_OK);
else
return Packet (Client::PC_ERROR,
words.size () == 2 ? std::move (words[1]) : "");
}
// MODULE-EXPORT $modulename [$flags]
Packet Client::ModuleExport (char const *module, Flags flags, size_t mlen)
{
write.BeginLine ();
write.AppendWord (u8"MODULE-EXPORT");
write.AppendWord (module, true, mlen);
if (flags != Flags::None)
write.AppendInteger (unsigned (flags));
write.EndLine ();
return MaybeRequest (Detail::RC_MODULE_EXPORT);
}
// MODULE-IMPORT $modulename [$flags]
Packet Client::ModuleImport (char const *module, Flags flags, size_t mlen)
{
write.BeginLine ();
write.AppendWord (u8"MODULE-IMPORT");
write.AppendWord (module, true, mlen);
if (flags != Flags::None)
write.AppendInteger (unsigned (flags));
write.EndLine ();
return MaybeRequest (Detail::RC_MODULE_IMPORT);
}
// MODULE-COMPILED $modulename [$flags]
Packet Client::ModuleCompiled (char const *module, Flags flags, size_t mlen)
{
write.BeginLine ();
write.AppendWord (u8"MODULE-COMPILED");
write.AppendWord (module, true, mlen);
if (flags != Flags::None)
write.AppendInteger (unsigned (flags));
write.EndLine ();
return MaybeRequest (Detail::RC_MODULE_COMPILED);
}
// INCLUDE-TRANSLATE $includename [$flags]
Packet Client::IncludeTranslate (char const *include, Flags flags, size_t ilen)
{
write.BeginLine ();
write.AppendWord (u8"INCLUDE-TRANSLATE");
write.AppendWord (include, true, ilen);
if (flags != Flags::None)
write.AppendInteger (unsigned (flags));
write.EndLine ();
return MaybeRequest (Detail::RC_INCLUDE_TRANSLATE);
}
// BOOL $knowntextualness
// PATHNAME $cmifile
Packet IncludeTranslateResponse (std::vector<std::string> &words)
{
if (words[0] == u8"BOOL" && words.size () == 2)
{
if (words[1] == u8"FALSE")
return Packet (Client::PC_BOOL, 0);
else if (words[1] == u8"TRUE")
return Packet (Client::PC_BOOL, 1);
else
return Packet (Client::PC_ERROR, u8"");
}
else
return PathnameResponse (words);
}
}

View File

@ -0,0 +1,43 @@
# message(STATUS "*top config-ix* CMAKE_SYSTEM : ${CMAKE_SYSTEM}")
include(CheckIncludeFile)
include(CheckIncludeFileCXX)
#include(CheckLibraryExists)
#include(CheckSymbolExists)
include(CheckFunctionExists)
#include(CheckCXXSourceCompiles)
#include(TestBigEndian)
include(CheckCCompilerFlag)
include(CheckCXXCompilerFlag)
# Flags
check_cxx_compiler_flag(-stdlib=libc++ LIBCODY_CXX_HAS_STDLIB_FLAG)
check_cxx_compiler_flag(-fno-enforce-eh-specs LIBCODY_HAS_NOENFORCE)
check_cxx_compiler_flag(-fno-stack-protector LIBCODY_HAS_NOSTACKPROT)
check_cxx_compiler_flag(-fno-threadsafe-statics LIBCODY_HAS_NOTHREADSAFESTATICS)
check_cxx_compiler_flag(-Wno-gnu-zero-variadic-macro-arguments LIBCODY_CXX_W_GZVMA)
# Address github issue #10
if (NOT CODY_WITHEXCEPTIONS)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions -fno-rtti")
if (LIBCODY_HAS_NOENFORCE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-enforce-eh-specs")
endif()
endif()
if (LIBCODY_HAS_NOSTACKPROT)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-stack-protector")
endif()
if (LIBCODY_HAS_NOTHREADSAFESTATICS)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-threadsafe-statics")
endif()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -W -Wall -Woverloaded-virtual -Wshadow")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-invalid-offsetof -Wno-unused-variable")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-missing-field-initializers")
if (LIBCODY_CXX_W_GZVMA)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-gnu-zero-variadic-macro-arguments")
endif ()

800
libcody/cody.hh Normal file
View File

@ -0,0 +1,800 @@
// CODYlib -*- mode:c++ -*-
// Copyright (C) 2020 Nathan Sidwell, nathan@acm.org
// License: Apache v2.0
#ifndef CODY_HH
#define CODY_HH 1
// Have a known-good list of networking systems
#if defined (__unix__) || defined (__MACH__)
#define CODY_NETWORKING 1
#else
#define CODY_NETWORKING 0
#endif
#if 0 // For testing
#undef CODY_NETWORKING
#define CODY_NETWORKING 0
#endif
// C++
#include <memory>
#include <string>
#include <vector>
// C
#include <cstddef>
// OS
#include <errno.h>
#include <sys/types.h>
#if CODY_NETWORKING
#include <sys/socket.h>
#endif
namespace Cody {
// Set version to 1, as this is completely incompatible with 0.
// Fortunately both versions 0 and 1 will recognize each other's HELLO
// messages sufficiently to error out
constexpr unsigned Version = 1;
// FIXME: I guess we need a file-handle abstraction here
// Is windows DWORDPTR still?, or should it be FILE *? (ew).
namespace Detail {
// C++11 doesn't have utf8 character literals :(
template<unsigned I>
constexpr char S2C (char const (&s)[I])
{
static_assert (I == 2, "only single octet strings may be converted");
return s[0];
}
/// Internal buffering class. Used to concatenate outgoing messages
/// and Lex incoming ones.
class MessageBuffer
{
std::vector<char> buffer; ///< buffer holding the message
size_t lastBol = 0; ///< location of the most recent Beginning Of
///< Line, or position we've readed when writing
public:
MessageBuffer () = default;
~MessageBuffer () = default;
MessageBuffer (MessageBuffer &&) = default;
MessageBuffer &operator= (MessageBuffer &&) = default;
public:
///
/// Finalize a buffer to be written. No more lines can be added to
/// the buffer. Use before a sequence of Write calls.
void PrepareToWrite ()
{
buffer.push_back (u8"\n"[0]);
lastBol = 0;
}
///
/// Prepare a buffer for reading. Use before a sequence of Read calls.
void PrepareToRead ()
{
buffer.clear ();
lastBol = 0;
}
public:
/// Begin a message line. Use before a sequence of Append and
/// related calls.
void BeginLine ();
/// End a message line. Use after a sequence of Append and related calls.
void EndLine () {}
public:
/// Append a string to the current line. No whitespace is prepended
/// or appended.
///
/// @param str the string to be written
/// @param maybe_quote indicate if there's a possibility the string
/// contains characters that need quoting. Defaults to false.
/// It is always safe to set
/// this true, but that causes an additional scan of the string.
/// @param len The length of the string. If not specified, strlen
/// is used to find the length.
void Append (char const *str, bool maybe_quote = false,
size_t len = ~size_t (0));
///
/// Add whitespace word separator. Multiple adjacent whitespace is fine.
void Space ()
{
Append (Detail::S2C(u8" "));
}
public:
/// Add a word as with Append, but prefixing whitespace to make a
/// separate word
void AppendWord (char const *str, bool maybe_quote = false,
size_t len = ~size_t (0))
{
if (buffer.size () != lastBol)
Space ();
Append (str, maybe_quote, len);
}
/// Add a word as with AppendWord
/// @param str the string to append
/// @param maybe_quote string might need quoting, as for Append
void AppendWord (std::string const &str, bool maybe_quote = false)
{
AppendWord (str.data (), maybe_quote, str.size ());
}
///
/// Add an integral value, prepending a space.
void AppendInteger (unsigned u);
private:
/// Append a literal character.
/// @param c character to append
void Append (char c);
public:
/// Lex the next input line into a vector of words.
/// @param words filled with a vector of lexed strings
/// @result 0 if no errors, an errno value on lexxing error such as
/// there being no next line (ENOENT), or malformed quoting (EINVAL)
int Lex (std::vector<std::string> &words);
public:
/// Append the most-recently lexxed line to a string. May be useful
/// in error messages. The unparsed line is appended -- before any
/// unquoting.
/// If we had c++17 string_view, we'd simply return a view of the
/// line, and leave it to the caller to do any concatenation.
/// @param l string to-which the lexxed line is appended.
void LexedLine (std::string &l);
public:
/// Detect if we have reached the end of the input buffer.
/// I.e. there are no more lines to Lex
/// @result True if at end
bool IsAtEnd () const
{
return lastBol == buffer.size ();
}
public:
/// Read from end point into a read buffer, as with read(2). This will
/// not block , unless FD is blocking, and there is nothing
/// immediately available.
/// @param fd file descriptor to read from. This may be a regular
/// file, pipe or socket.
/// @result on error returns errno. If end of file occurs, returns
/// -1. At end of message returns 0. If there is more needed
/// returns EAGAIN (or possibly EINTR). If the message is
/// malformed, returns EINVAL.
int Read (int fd) noexcept;
public:
/// Write to an end point from a write buffer, as with write(2). As
/// with Read, this will not usually block.
/// @param fd file descriptor to write to. This may be a regular
/// file, pipe or socket.
/// @result on error returns errno.
/// At end of message returns 0. If there is more to write
/// returns EAGAIN (or possibly EINTR).
int Write (int fd) noexcept;
};
///
/// Request codes. Perhaps this should be exposed? These are likely
/// useful to servers that queue requests.
enum RequestCode
{
RC_CONNECT,
RC_MODULE_REPO,
RC_MODULE_EXPORT,
RC_MODULE_IMPORT,
RC_MODULE_COMPILED,
RC_INCLUDE_TRANSLATE,
RC_HWM
};
/// Internal file descriptor tuple. It's used as an anonymous union member.
struct FD
{
int from; ///< Read from this FD
int to; ///< Write to this FD
};
}
// Flags for various requests
enum class Flags : unsigned
{
None,
NameOnly = 1<<0, // Only querying for CMI names, not contents
};
inline Flags operator& (Flags a, Flags b)
{
return Flags (unsigned (a) & unsigned (b));
}
inline Flags operator| (Flags a, Flags b)
{
return Flags (unsigned (a) | unsigned (b));
}
///
/// Response data for a request. Returned by Client's request calls,
/// which return a single Packet. When the connection is Corked, the
/// Uncork call will return a vector of Packets.
class Packet
{
public:
///
/// Packet is a variant structure. These are the possible content types.
enum Category { INTEGER, STRING, VECTOR};
private:
// std:variant is a C++17 thing, so we're doing this ourselves.
union
{
size_t integer; ///< Integral value
std::string string; ///< String value
std::vector<std::string> vector; ///< Vector of string value
};
Category cat : 2; ///< Discriminator
private:
unsigned short code = 0; ///< Packet type
unsigned short request = 0;
public:
Packet (unsigned c, size_t i = 0)
: integer (i), cat (INTEGER), code (c)
{
}
Packet (unsigned c, std::string &&s)
: string (std::move (s)), cat (STRING), code (c)
{
}
Packet (unsigned c, std::string const &s)
: string (s), cat (STRING), code (c)
{
}
Packet (unsigned c, std::vector<std::string> &&v)
: vector (std::move (v)), cat (VECTOR), code (c)
{
}
// No non-move constructor from a vector. You should not be doing
// that.
// Only move constructor and move assignment
Packet (Packet &&t)
{
Create (std::move (t));
}
Packet &operator= (Packet &&t)
{
Destroy ();
Create (std::move (t));
return *this;
}
~Packet ()
{
Destroy ();
}
private:
///
/// Variant move creation from another packet
void Create (Packet &&t);
///
/// Variant destruction
void Destroy ();
public:
///
/// Return the packet type
unsigned GetCode () const
{
return code;
}
///
/// Return the packet type
unsigned GetRequest () const
{
return request;
}
void SetRequest (unsigned r)
{
request = r;
}
///
/// Return the category of the packet's payload
Category GetCategory () const
{
return cat;
}
public:
///
/// Return an integral payload. Undefined if the category is not INTEGER
size_t GetInteger () const
{
return integer;
}
///
/// Return (a reference to) a string payload. Undefined if the
/// category is not STRING
std::string const &GetString () const
{
return string;
}
std::string &GetString ()
{
return string;
}
///
/// Return (a reference to) a constant vector of strings payload.
/// Undefined if the category is not VECTOR
std::vector<std::string> const &GetVector () const
{
return vector;
}
///
/// Return (a reference to) a non-conatant vector of strings payload.
/// Undefined if the category is not VECTOR
std::vector<std::string> &GetVector ()
{
return vector;
}
};
class Server;
///
/// Client-side (compiler) object.
class Client
{
public:
/// Response packet codes
enum PacketCode
{
PC_CORKED, ///< Messages are corked
PC_CONNECT, ///< Packet is integer version
PC_ERROR, ///< Packet is error string
PC_OK,
PC_BOOL,
PC_PATHNAME
};
private:
Detail::MessageBuffer write; ///< Outgoing write buffer
Detail::MessageBuffer read; ///< Incoming read buffer
std::string corked; ///< Queued request tags
union
{
Detail::FD fd; ///< FDs connecting to server
Server *server; ///< Directly connected server
};
bool is_direct = false; ///< Discriminator
bool is_connected = false; /// Connection handshake succesful
private:
Client ();
public:
/// Direct connection constructor.
/// @param s Server to directly connect
Client (Server *s)
: Client ()
{
is_direct = true;
server = s;
}
/// Communication connection constructor
/// @param from file descriptor to read from
/// @param to file descriptor to write to, defaults to from
Client (int from, int to = -1)
: Client ()
{
fd.from = from;
fd.to = to < 0 ? from : to;
}
~Client ();
// We have to provide our own move variants, because of the variant member.
Client (Client &&);
Client &operator= (Client &&);
public:
///
/// Direct connection predicate
bool IsDirect () const
{
return is_direct;
}
///
/// Successful handshake predicate
bool IsConnected () const
{
return is_connected;
}
public:
///
/// Get the read FD
/// @result the FD to read from, -1 if a direct connection
int GetFDRead () const
{
return is_direct ? -1 : fd.from;
}
///
/// Get the write FD
/// @result the FD to write to, -1 if a direct connection
int GetFDWrite () const
{
return is_direct ? -1 : fd.to;
}
///
/// Get the directly-connected server
/// @result the server, or nullptr if a communication connection
Server *GetServer () const
{
return is_direct ? server : nullptr;
}
public:
///
/// Perform connection handshake. All othe requests will result in
/// errors, until handshake is succesful.
/// @param agent compiler identification
/// @param ident compilation identifiation (maybe nullptr)
/// @param alen length of agent string, if known
/// @param ilen length of ident string, if known
/// @result packet indicating success (or deferrment) of the
/// connection, payload is optional flags
Packet Connect (char const *agent, char const *ident,
size_t alen = ~size_t (0), size_t ilen = ~size_t (0));
/// std::string wrapper for connection
/// @param agent compiler identification
/// @param ident compilation identification
Packet Connect (std::string const &agent, std::string const &ident)
{
return Connect (agent.c_str (), ident.c_str (),
agent.size (), ident.size ());
}
public:
/// Request compiler module repository
/// @result packet indicating repo
Packet ModuleRepo ();
public:
/// Inform of compilation of a named module interface or partition,
/// or a header unit
/// @param str module or header-unit
/// @param len name length, if known
/// @result CMI name (or deferrment/error)
Packet ModuleExport (char const *str, Flags flags, size_t len = ~size_t (0));
Packet ModuleExport (char const *str)
{
return ModuleExport (str, Flags::None, ~size_t (0));
}
Packet ModuleExport (std::string const &s, Flags flags = Flags::None)
{
return ModuleExport (s.c_str (), flags, s.size ());
}
public:
/// Importation of a module, partition or header-unit
/// @param str module or header-unit
/// @param len name length, if known
/// @result CMI name (or deferrment/error)
Packet ModuleImport (char const *str, Flags flags, size_t len = ~size_t (0));
Packet ModuleImport (char const *str)
{
return ModuleImport (str, Flags::None, ~size_t (0));
}
Packet ModuleImport (std::string const &s, Flags flags = Flags::None)
{
return ModuleImport (s.c_str (), flags, s.size ());
}
public:
/// Successful compilation of a module interface, partition or
/// header-unit. Must have been preceeded by a ModuleExport
/// request.
/// @param str module or header-unit
/// @param len name length, if known
/// @result OK (or deferment/error)
Packet ModuleCompiled (char const *str, Flags flags, size_t len = ~size_t (0));
Packet ModuleCompiled (char const *str)
{
return ModuleCompiled (str, Flags::None, ~size_t (0));
}
Packet ModuleCompiled (std::string const &s, Flags flags = Flags::None)
{
return ModuleCompiled (s.c_str (), flags, s.size ());
}
/// Include translation query.
/// @param str header unit name
/// @param len name length, if known
/// @result Packet indicating include translation boolean, or CMI
/// name (or deferment/error)
Packet IncludeTranslate (char const *str, Flags flags,
size_t len = ~size_t (0));
Packet IncludeTranslate (char const *str)
{
return IncludeTranslate (str, Flags::None, ~size_t (0));
}
Packet IncludeTranslate (std::string const &s, Flags flags = Flags::None)
{
return IncludeTranslate (s.c_str (), flags, s.size ());
}
public:
/// Cork the connection. All requests are queued up. Each request
/// call will return a PC_CORKED packet.
void Cork ();
/// Uncork the connection. All queued requests are sent to the
/// server, and a block of responses waited for.
/// @result A vector of packets, containing the in-order responses to the
/// queued requests.
std::vector<Packet> Uncork ();
///
/// Indicate corkedness of connection
bool IsCorked () const
{
return !corked.empty ();
}
private:
Packet ProcessResponse (std::vector<std::string> &, unsigned code,
bool isLast);
Packet MaybeRequest (unsigned code);
int CommunicateWithServer ();
};
/// This server-side class is used to resolve requests from one or
/// more clients. You are expected to derive from it and override the
/// virtual functions it provides. The connection resolver may return
/// a different resolved object to service the remainder of the
/// connection -- for instance depending on the compiler that is
/// making the requests.
class Resolver
{
public:
Resolver () = default;
virtual ~Resolver ();
protected:
/// Mapping from a module or header-unit name to a CMI file name.
/// @param module module name
/// @result CMI name
virtual std::string GetCMIName (std::string const &module);
/// Return the CMI file suffix to use
/// @result CMI suffix, a statically allocated string
virtual char const *GetCMISuffix ();
public:
/// When the requests of a directly-connected server are processed,
/// we may want to wait for the requests to complete (for instance a
/// set of subjobs).
/// @param s directly connected server.
virtual void WaitUntilReady (Server *s);
public:
/// Provide an error response.
/// @param s the server to provide the response to.
/// @param msg the error message
virtual void ErrorResponse (Server *s, std::string &&msg);
public:
/// Connection handshake. Provide response to server and return new
/// (or current) resolver, or nullptr.
/// @param s server to provide response to
/// @param version the client's version number
/// @param agent the client agent (compiler identification)
/// @param ident the compilation identification (may be empty)
/// @result nullptr in the case of an error. An error response will
/// be sent. If handing off to another resolver, return that,
/// otherwise this
virtual Resolver *ConnectRequest (Server *s, unsigned version,
std::string &agent, std::string &ident);
public:
// return 0 on ok, ERRNO on failure, -1 on unspecific error
virtual int ModuleRepoRequest (Server *s);
virtual int ModuleExportRequest (Server *s, Flags flags,
std::string &module);
virtual int ModuleImportRequest (Server *s, Flags flags,
std::string &module);
virtual int ModuleCompiledRequest (Server *s, Flags flags,
std::string &module);
virtual int IncludeTranslateRequest (Server *s, Flags flags,
std::string &include);
};
/// This server-side (build system) class handles a single connection
/// to a client. It has 3 states, READING:accumulating a message
/// block froma client, WRITING:writing a message block to a client
/// and PROCESSING:resolving requests. If the server does not spawn
/// jobs to build needed artifacts, the PROCESSING state will be brief.
class Server
{
public:
enum Direction
{
READING, // Server is waiting for completion of a (set of)
// requests from client. The next state will be PROCESSING.
WRITING, // Server is writing a (set of) responses to client.
// The next state will be READING.
PROCESSING // Server is processing client request(s). The next
// state will be WRITING.
};
private:
Detail::MessageBuffer write;
Detail::MessageBuffer read;
Resolver *resolver;
Detail::FD fd;
bool is_connected = false;
Direction direction : 2;
public:
Server (Resolver *r);
Server (Resolver *r, int from, int to = -1)
: Server (r)
{
fd.from = from;
fd.to = to >= 0 ? to : from;
}
~Server ();
Server (Server &&);
Server &operator= (Server &&);
public:
bool IsConnected () const
{
return is_connected;
}
public:
void SetDirection (Direction d)
{
direction = d;
}
public:
Direction GetDirection () const
{
return direction;
}
int GetFDRead () const
{
return fd.from;
}
int GetFDWrite () const
{
return fd.to;
}
Resolver *GetResolver () const
{
return resolver;
}
public:
/// Process requests from a directly-connected client. This is a
/// small wrapper around ProcessRequests, with some buffer swapping
/// for communication. It is expected that such processessing is
/// immediate.
/// @param from message block from client
/// @param to message block to client
void DirectProcess (Detail::MessageBuffer &from, Detail::MessageBuffer &to);
public:
/// Process the messages queued in the read buffer. We enter the
/// PROCESSING state, and each message line causes various resolver
/// methods to be called. Once processed, the server may need to
/// wait for all the requests to be ready, or it may be able to
/// immediately write responses back.
void ProcessRequests ();
public:
/// Accumulate an error response.
/// @param error the error message to encode
/// @param elen length of error, if known
void ErrorResponse (char const *error, size_t elen = ~size_t (0));
void ErrorResponse (std::string const &error)
{
ErrorResponse (error.data (), error.size ());
}
/// Accumulate an OK response
void OKResponse ();
/// Accumulate a boolean response
void BoolResponse (bool);
/// Accumulate a pathname response
/// @param path (may be nullptr, or empty)
/// @param rlen length, if known
void PathnameResponse (char const *path, size_t plen = ~size_t (0));
void PathnameResponse (std::string const &path)
{
PathnameResponse (path.data (), path.size ());
}
public:
/// Accumulate a (successful) connection response
/// @param agent the server-side agent
/// @param alen agent length, if known
void ConnectResponse (char const *agent, size_t alen = ~size_t (0));
void ConnectResponse (std::string const &agent)
{
ConnectResponse (agent.data (), agent.size ());
}
public:
/// Write message block to client. Semantics as for
/// MessageBuffer::Write.
/// @result errno or completion (0).
int Write ()
{
return write.Write (fd.to);
}
/// Initialize for writing a message block. All responses to the
/// incomping message block must be complete Enters WRITING state.
void PrepareToWrite ()
{
write.PrepareToWrite ();
direction = WRITING;
}
public:
/// Read message block from client. Semantics as for
/// MessageBuffer::Read.
/// @result errno, eof (-1) or completion (0)
int Read ()
{
return read.Read (fd.from);
}
/// Initialize for reading a message block. Enters READING state.
void PrepareToRead ()
{
read.PrepareToRead ();
direction = READING;
}
};
// Helper network stuff
#if CODY_NETWORKING
// Socket with specific address
int OpenSocket (char const **, sockaddr const *sock, socklen_t len);
int ListenSocket (char const **, sockaddr const *sock, socklen_t len,
unsigned backlog);
// Local domain socket (eg AF_UNIX)
int OpenLocal (char const **, char const *name);
int ListenLocal (char const **, char const *name, unsigned backlog = 0);
// ipv6 socket
int OpenInet6 (char const **e, char const *name, int port);
int ListenInet6 (char const **, char const *name, int port,
unsigned backlog = 0);
#endif
// FIXME: Mapping file utilities?
}
#endif // CODY_HH

29
libcody/config.h.in Normal file
View File

@ -0,0 +1,29 @@
/* config.h.in. Generated from configure.ac by autoheader. */
/* Bug reporting location */
#undef BUGURL
/* Enable checking */
#undef NMS_CHECKING
/* Define to the address where bug reports for this package should be sent. */
#undef PACKAGE_BUGREPORT
/* Define to the full name of this package. */
#undef PACKAGE_NAME
/* Define to the full name and version of this package. */
#undef PACKAGE_STRING
/* Define to the one symbol short name of this package. */
#undef PACKAGE_TARNAME
/* Define to the home page for this package. */
#undef PACKAGE_URL
/* Define to the version of this package. */
#undef PACKAGE_VERSION
#undef _FORTIFY_SOURCE
#define _GNU_SOURCE 1

280
libcody/config.m4 Normal file
View File

@ -0,0 +1,280 @@
# Nathan's Common Config -*- mode:autoconf -*-
# Copyright (C) 2020 Nathan Sidwell, nathan@acm.org
# License: Apache v2.0
AC_DEFUN([NMS_NOT_IN_SOURCE],
[if test -e configure ; then
AC_MSG_ERROR([Do not build in the source tree. Reasons])
fi])
# thanks to Zack Weinberg for fixing this!
AC_DEFUN([NMS_TOOLS],
[AC_SUBST([tools], [])
AC_ARG_WITH([tools],
AS_HELP_STRING([--with-tools=DIR],[tool directory]),
[AS_CASE([$withval],
[yes], [AC_MSG_ERROR([--with-tools requires an argument])],
[no], [:],
[tools="${withval%/bin}"])])
if test -n "$tools" ; then
if test -d "$tools/bin"; then
PATH="$tools/bin:$PATH"
AC_MSG_NOTICE([Using tools in $tools])
else
AC_MSG_ERROR([tool location does not exist])
fi
fi])
AC_DEFUN([NMS_TOOL_DIRS],
[if test "$tools" && test -d "$tools/include" ; then
CXX+=" -I$tools/include"
fi
if test "$tools" && test -d "$tools/lib" ; then
toollib="$tools/lib"
if os=$(CXX -print-multi-os-directory 2>/dev/null) ; then
toollib+="/${os}"
fi
LDFLAGS+=" -L $toollib"
unset toollib
fi])
AC_DEFUN([NMS_NUM_CPUS],
[AC_MSG_CHECKING([number of CPUs])
AS_CASE([$build],
[*-*-darwin*], [NUM_CPUS=$(sysctl -n hw.ncpu 2>/dev/null)],
[NUM_CPUS=$(grep -c '^processor' /proc/cpuinfo 2>/dev/null)])
test "$NUM_CPUS" = 0 && NUM_CPUS=
AC_MSG_RESULT([${NUM_CPUS:-unknown}])
test "$NUM_CPUS" = 1 && NUM_CPUS=
AC_SUBST(NUM_CPUS)])
AC_DEFUN([NMS_MAINTAINER_MODE],
[AC_ARG_ENABLE([maintainer-mode],
AS_HELP_STRING([--enable-maintainer-mode],
[enable maintainer mode. Add rules to rebuild configurey bits]),,
[enable_maintainer_mode=no])
AS_CASE([$enable_maintainer_mode],
[yes], [maintainer_mode=yes],
[no], [maintainer=no],
[AC_MSG_ERROR([unknown maintainer mode $enable_maintainer_mode])])
AC_MSG_CHECKING([maintainer-mode])
AC_MSG_RESULT([$maintainer_mode])
test "$maintainer_mode" = yes && MAINTAINER=yes
AC_SUBST(MAINTAINER)])
AC_DEFUN([NMS_CXX_COMPILER],
[AC_ARG_WITH([compiler],
AS_HELP_STRING([--with-compiler=NAME],[which compiler to use]),
AC_MSG_CHECKING([C++ compiler])
if test "$withval" = "yes" ; then
AC_MSG_ERROR([NAME not specified])
elif test "$withval" = "no" ; then
AC_MSG_ERROR([Gonna need a C++ compiler!])
else
CXX="${withval}"
AC_MSG_RESULT([$CXX])
fi)])
AC_DEFUN([NMS_CXX_11],
[AC_MSG_CHECKING([whether $CXX is for C++11])
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
[#if __cplusplus != 201103
#error "C++11 is required"
#endif
]])],
[AC_MSG_RESULT([yes])],
[CXX_ORIG="$CXX"
CXX+=" -std=c++11"
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
[#if __cplusplus != 201103
#error "C++11 is required"
#endif
]])],
AC_MSG_RESULT([adding -std=c++11]),
[CXX="$CXX_ORIG"
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
[#if __cplusplus > 201103
#error "C++11 is required"
#endif
]])],
AC_MSG_RESULT([> C++11]),
AC_MSG_RESULT([no])
AC_MSG_ERROR([C++11 is required])]))
unset CXX_ORIG])])
AC_DEFUN([NMS_CXX_20],
[AC_MSG_CHECKING([whether $CXX is for C++20])
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
[#if __cplusplus <= 201703
#error "C++20 is required"
#endif
]])],
[AC_MSG_RESULT([yes])],
[CXX+=" -std=c++20"
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
[#if __cplusplus <= 201703
#error "C++20 is required"
#endif
]])],
AC_MSG_RESULT([adding -std=c++20]),
AC_MSG_RESULT([no])
AC_MSG_ERROR([C++20 is required])]))
AC_MSG_CHECKING([whether C++20 support is sufficiently advanced])
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
#include <version>
// There doesn't seem to be a feature macro for __VA_OPT__ :(
#define VARIADIC(X,...) X __VA_OPT__((__VA_ARGS__))
#define X(Y,Z) 1
int ary[VARIADIC(X,Y,Z)];
#if __cpp_constinit < 201907
#error "C++20 constinit required"
cpp_constinit is __cpp_constinit
#endif
#if __cpp_if_constexpr < 201606
#error "C++20 constexpr required"
cpp_constexpr is __cpp_if_constexpr
#endif
#if __cpp_concepts < 201907
#error "C++20 concepts required"
cpp_concepts is __cpp_concepts
#endif
#if __cpp_structured_bindings < 201606
#error "C++20 structured bindings required"
cpp_structured_bindings is __cpp_structured_bindings
#endif
#if __cpp_lib_int_pow2 < 202002
#error "std::has_single_bit required"
cpp_lib_int_pow2 is __cpp_lib_int_pow2
#endif
]])],
AC_MSG_RESULT([yes 🙂]),
AC_MSG_RESULT([no 🙁])
AC_MSG_ERROR([C++20 support is too immature]))])
AC_DEFUN([NMS_ENABLE_EXCEPTIONS],
[AC_ARG_ENABLE([exceptions],
AS_HELP_STRING([--enable-exceptions],
[enable exceptions & rtti]),,
[enable_exceptions="no"])
AS_CASE([$enable_exceptions],
[yes], [nms_exceptions=yes],
[no], [nms_exceptions=no],
[AC_MSG_ERROR([unknown exceptions $enable_exceptions])])
AC_MSG_CHECKING([exceptions])
AC_MSG_RESULT([$nms_exceptions])
if test "$nms_exceptions" != no ; then
EXCEPTIONS=yes
fi
AC_SUBST(EXCEPTIONS)])
AC_DEFUN([NMS_LINK_OPT],
[AC_MSG_CHECKING([adding $1 to linker])
ORIG_LDFLAGS="$LDFLAGS"
LDFLAGS+=" $1"
AC_LINK_IFELSE([AC_LANG_PROGRAM([])],
[AC_MSG_RESULT([ok])],
[LDFLAGS="$ORIG_LDFLAGS"
AC_MSG_RESULT([no])])
unset ORIG_LDFLAGS])
AC_DEFUN([NMS_BUGURL],
[AC_MSG_CHECKING([bugurl])
AC_ARG_WITH(bugurl,
AS_HELP_STRING([--with-bugurl=URL],[where to report bugs]),
AS_CASE(["$withval"],
[yes], [AC_MSG_ERROR([--with-bugurl requires an argument])],
[no], [BUGURL=""],
[BUGURL="${withval}"]),
[BUGURL="${PACKAGE_BUGREPORT}"])
AC_MSG_RESULT($BUGURL)
AC_DEFINE_UNQUOTED(BUGURL,"$BUGURL",[Bug reporting location])])
AC_DEFUN([NMS_DISTRIBUTION],
[AC_ARG_ENABLE([distribution],
AS_HELP_STRING([--enable-distribution],
[enable distribution. Inhibit components that prevent distribution]),,
[enable_distribution="no"])
AS_CASE([$enable_distribution],
[yes], [nms_distribution=yes],
[no], [nms_distribution=no],
[AC_MSG_ERROR([unknown distribution $enable_distribution])])
AC_MSG_CHECKING([distribution])
AC_MSG_RESULT([$nms_distribution])])
AC_DEFUN([NMS_ENABLE_CHECKING],
[AC_ARG_ENABLE([checking],
AS_HELP_STRING([--enable-checking],
[enable run-time checking]),,
[enable_checking="yes"])
AS_CASE([$enable_checking],
[yes|all|yes,*], [nms_checking=yes],
[no|none|release], [nms_checking=],
[AC_MSG_ERROR([unknown check "$enable_checking"])])
AC_MSG_CHECKING([checking])
AC_MSG_RESULT([${nms_checking:-no}])
if test "$nms_checking" = yes ; then
AC_DEFINE_UNQUOTED([NMS_CHECKING], [0${nms_checking:+1}], [Enable checking])
fi])
AC_DEFUN([NMS_WITH_BINUTILS],
[AC_MSG_CHECKING([binutils])
AC_ARG_WITH(bfd,
AS_HELP_STRING([--with-bfd=DIR], [location of libbfd]),
if test "$withval" = "yes" ; then
AC_MSG_ERROR([DIR not specified])
elif test "$withval" = "no" ; then
AC_MSG_RESULT(installed)
else
AC_MSG_RESULT(${withval})
CPPFLAGS+=" -I${withval}/include"
LDFLAGS+=" -L${withval}/lib"
fi,
AC_MSG_RESULT(installed))])
AC_DEFUN([NMS_ENABLE_BACKTRACE],
[AC_REQUIRE([NMS_DISTRIBUTION])
AC_ARG_ENABLE([backtrace],
AS_HELP_STRING([--enable-backtrace],[provide backtrace on fatality.]),,
[enable_backtrace="maybe"])
if test "${enable_backtrace:-maybe}" != no ; then
AC_CHECK_HEADERS(execinfo.h)
AC_CHECK_FUNCS(backtrace)
if test "$nms_distribution" = no ; then
AC_DEFINE([HAVE_DECL_BASENAME], [1], [Needed for demangle.h])
# libiberty prevents distribution because of licensing
AC_CHECK_HEADERS([demangle.h libiberty/demangle.h],[break])
# libbfd prevents distribution because of licensing
AC_CHECK_HEADERS([bfd.h])
AC_SEARCH_LIBS([bfd_openr],[bfd],[LIBS+="-lz -liberty -ldl"],,[-lz -liberty -ldl])
fi
if test "$ac_cv_func_backtrace" = yes ; then
nms_backtrace=yes
ldbacktrace=-rdynamic
AC_DEFINE([NMS_BACKTRACE], [1], [Enable backtrace])
elif test "$enable_backtrace" = yes ; then
AC_MSG_ERROR([Backtrace unavailable])
fi
AC_SUBST([ldbacktrace])
fi
AC_MSG_CHECKING([backtrace])
AC_MSG_RESULT([${nms_backtrace:-no}])])
AC_DEFUN([NMS_CONFIG_FILES],
[CONFIG_FILES="Makefile $1"
SUBDIRS="$2"
for generated in config.h.in configure ; do
if test $srcdir/configure.ac -nt $srcdir/$generated ; then
touch $srcdir/$generated
fi
done
for dir in . $SUBDIRS
do
CONFIG_FILES+=" $dir/Makesub"
test -f ${srcdir}/$dir/tests/Makesub.in && CONFIG_FILES+=" $dir/tests/Makesub"
done
AC_CONFIG_FILES([$CONFIG_FILES])
AC_SUBST(configure_args,[$ac_configure_args])
AC_SUBST(SUBDIRS)
AC_SUBST(CONFIG_FILES)])

4201
libcody/configure vendored Executable file

File diff suppressed because it is too large Load Diff

37
libcody/configure.ac Normal file
View File

@ -0,0 +1,37 @@
# CODYlib -*- mode:autoconf -*-
# Copyright (C) 2020 Nathan Sidwell, nathan@acm.org
# License: Apache v2.0
AC_INIT([codylib],[0.0],[github.com/urnathan/libcody])
AC_CONFIG_SRCDIR(cody.hh)
m4_include(config.m4)
AC_CONFIG_AUX_DIR(build-aux)
AC_SUBST(PACKAGE_VERSION)
NMS_NOT_IN_SOURCE
AC_CANONICAL_HOST
NMS_TOOLS
NMS_NUM_CPUS
NMS_MAINTAINER_MODE
NMS_CXX_COMPILER
AC_LANG(C++)
AC_PROG_CXX
NMS_CXX_11
NMS_TOOL_DIRS
NMS_LINK_OPT([-Wl,--no-undefined])
NMS_CONFIG_FILES([gdbinit dox.cfg])
NMS_BUGURL
NMS_ENABLE_CHECKING
NMS_ENABLE_EXCEPTIONS
AC_CONFIG_HEADERS([config.h])
AC_CHECK_TOOL([AR],[ar])
AC_CHECK_PROG([DOXYGEN],[doxygen],[doxygen],[: NOTdoxygen])
AC_CHECK_PROG([ALOY],[aloy],[aloy],[: Joust testsuite missing])
AH_VERBATIM([_GNU_SOURCE],[#define _GNU_SOURCE 1])
AH_VERBATIM([_FORTIFY_SOURCE],[#undef _FORTIFY_SOURCE])
AC_OUTPUT

2478
libcody/dox.cfg.in Normal file

File diff suppressed because it is too large Load Diff

78
libcody/fatal.cc Normal file
View File

@ -0,0 +1,78 @@
// CODYlib -*- mode:c++ -*-
// Copyright (C) 2019-2020 Nathan Sidwell, nathan@acm.org
// License: Apache v2.0
// Cody
#include "internal.hh"
// C
#include <csignal>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
namespace Cody {
#if NMS_CHECKING
void (AssertFailed) (Location loc) noexcept
{
(HCF) ("assertion failed", loc);
}
void (Unreachable) (Location loc) noexcept
{
(HCF) ("unreachable reached", loc);
}
#endif
void (HCF) (char const *msg
#if NMS_CHECKING
, Location const loc
#endif
) noexcept
{ // HCF - you goofed!
__asm__ volatile ("nop"); // HCF - you goofed!
#if !NMS_CHECKING
constexpr Location loc (nullptr, 0);
#endif
fprintf (stderr, "CODYlib: %s", msg ? msg : "internal error");
if (char const *file = loc.File ())
{
char const *src = SRCDIR;
if (src[0])
{
size_t l = strlen (src);
if (!strncmp (src, file, l) && file[l] == '/')
file += l + 1;
}
fprintf (stderr, " at %s:%u", file, loc.Line ());
}
fprintf (stderr, "\n");
raise (SIGABRT);
exit (2);
}
void BuildNote (FILE *stream) noexcept
{
fprintf (stream, "Version %s.\n", PACKAGE_NAME " " PACKAGE_VERSION);
fprintf (stream, "Report bugs to %s.\n", BUGURL[0] ? BUGURL : "you");
if (PACKAGE_URL[0])
fprintf (stream, "See %s for more information.\n", PACKAGE_URL);
if (REVISION[0])
fprintf (stream, "Source %s.\n", REVISION);
fprintf (stream, "Build is %s & %s.\n",
#if !NMS_CHECKING
"un"
#endif
"checked",
#if !__OPTIMIZE__
"un"
#endif
"optimized");
}
}

11
libcody/gdbinit.in Normal file
View File

@ -0,0 +1,11 @@
# CODYlib -*- mode:autoconf -*-
# Copyright (C) 2020 Nathan Sidwell, nathan@acm.org
# License: Apache v2.0
dir @srcdir@
# some default breakpoints, to catch the fatal exit
set breakpoint pending on
break HCF
set complaints 0
break exit
set breakpoint pending off

125
libcody/internal.hh Normal file
View File

@ -0,0 +1,125 @@
// CODYlib -*- mode:c++ -*-
// Copyright (C) 2020 Nathan Sidwell, nathan@acm.org
// License: Apache v2.0
#include "cody.hh"
// C++
#if __GNUC__ >= 10
#define CODY_LOC_BUILTIN 1
#elif !defined (__has_include)
#elif __has_include (<source_location>)
#include <source_location>
#define CODY_LOC_SOURCE 1
#endif
// C
#include <cstdio>
namespace Cody {
// Location is needed regardless of checking, to make the fatal
// handler simpler
class Location
{
protected:
char const *file;
unsigned line;
public:
constexpr Location (char const *file_
#if CODY_LOC_BUILTIN
= __builtin_FILE ()
#elif !CODY_LOC_SOURCE
= nullptr
#endif
, unsigned line_
#if CODY_LOC_BUILTIN
= __builtin_LINE ()
#elif !CODY_LOC_SOURCE
= 0
#endif
)
:file (file_), line (line_)
{
}
#if !CODY_LOC_BUILTIN && CODY_LOC_SOURCE
constexpr Location (source_location loc = source_location::current ())
: Location (loc.file (), loc.line ())
{
}
#endif
public:
constexpr char const *File () const
{
return file;
}
constexpr unsigned Line () const
{
return line;
}
};
void HCF [[noreturn]]
(
char const *msg
#if NMS_CHECKING
, Location const = Location ()
#if !CODY_LOC_BUILTIN && !CODY_LOC_SOURCE
#define HCF(M) HCF ((M), Cody::Location (__FILE__, __LINE__))
#endif
#endif
) noexcept;
#if NMS_CHECKING
void AssertFailed [[noreturn]] (Location loc = Location ()) noexcept;
void Unreachable [[noreturn]] (Location loc = Location ()) noexcept;
#if !CODY_LOC_BUILTIN && !CODY_LOC_SOURCE
#define AssertFailed() AssertFailed (Cody::Location (__FILE__, __LINE__))
#define Unreachable() Unreachable (Cody::Location (__FILE__, __LINE__))
#endif
// Do we have __VA_OPT__, alas no specific feature macro for it :(
// From stack overflow
// https://stackoverflow.com/questions/48045470/portably-detect-va-opt-support
// Relies on having variadic macros, but they're a C++11 thing, so
// we're good
#define HAVE_ARG_3(a,b,c,...) c
#define HAVE_VA_OPT_(...) HAVE_ARG_3(__VA_OPT__(,),true,false,)
#define HAVE_VA_OPT HAVE_VA_OPT_(?)
// Oh, for lazily evaluated function parameters
#if HAVE_VA_OPT
// Assert is variadic, so you can write Assert (TPL<A,B>(C)) without
// extraneous parens. I don't think we need that though.
#define Assert(EXPR, ...) \
(__builtin_expect (bool (EXPR __VA_OPT__ (, __VA_ARGS__)), true) \
? (void)0 : AssertFailed ())
#else
// If you don't have the GNU ,##__VA_ARGS__ pasting extension, we'll
// need another fallback
#define Assert(EXPR, ...) \
(__builtin_expect (bool (EXPR, ##__VA_ARGS__), true) \
? (void)0 : AssertFailed ())
#endif
#else
// Not asserting, use EXPR in an unevaluated context
#if HAVE_VA_OPT
#define Assert(EXPR, ...) \
((void)sizeof (bool (EXPR __VA_OPT__ (, __VA_ARGS__))), (void)0)
#else
#define Assert(EXPR, ...) \
((void)sizeof (bool (EXPR, ##__VA_ARGS__)), (void)0)
#endif
inline void Unreachable () noexcept
{
__builtin_unreachable ();
}
#endif
// FIXME: This should be user visible in some way
void BuildNote (FILE *stream) noexcept;
}

140
libcody/netclient.cc Normal file
View File

@ -0,0 +1,140 @@
// CODYlib -*- mode:c++ -*-
// Copyright (C) 2020 Nathan Sidwell, nathan@acm.org
// License: Apache v2.0
// Cody
#include "internal.hh"
#if CODY_NETWORKING
// C
#include <cerrno>
#include <cstring>
// OS
#include <netdb.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/un.h>
#ifndef AI_NUMERICSERV
#define AI_NUMERICSERV 0
#endif
// Client-side networking helpers
namespace Cody {
int OpenSocket (char const **e, sockaddr const *addr, socklen_t len)
{
char const *errstr = nullptr;
int fd = socket (addr->sa_family, SOCK_STREAM, 0);
if (fd < 0)
{
errstr = "creating socket";
fail:;
int err = errno;
if (e)
*e = errstr;
if (fd >= 0)
close (fd);
errno = err;
return -1;
}
if (connect (fd, addr, len) < 0)
{
errstr = "connecting socket";
goto fail;
}
return fd;
}
int OpenLocal (char const **e, char const *name)
{
sockaddr_un addr;
size_t len = strlen (name);
if (len >= sizeof (addr.sun_path))
{
errno = ENAMETOOLONG;
return -1;
}
memset (&addr, 0, offsetof (sockaddr_un, sun_path));
addr.sun_family = AF_UNIX;
memcpy (addr.sun_path, name, len + 1);
return OpenSocket (e, (sockaddr *)&addr, sizeof (addr));
}
int OpenInet6 (char const **e, char const *name, int port)
{
addrinfo *addrs = nullptr;
int fd = -1;
char const *errstr = nullptr;
fd = socket (AF_INET6, SOCK_STREAM, 0);
if (fd < 0)
{
errstr = "socket";
fail:;
int err = errno;
if (e)
*e = errstr;
if (fd >= 0)
close (fd);
if (addrs)
freeaddrinfo (addrs);
errno = err;
return -1;
}
addrinfo hints;
hints.ai_flags = AI_NUMERICSERV;
hints.ai_family = AF_INET6;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = 0;
hints.ai_addrlen = 0;
hints.ai_addr = nullptr;
hints.ai_canonname = nullptr;
hints.ai_next = nullptr;
/* getaddrinfo requires a port number, but is quite happy to accept
invalid ones. So don't rely on it. */
if (int err = getaddrinfo (name, "0", &hints, &addrs))
{
errstr = gai_strerror (err);
// What's the best errno to set?
errno = 0;
goto fail;
}
sockaddr_in6 addr;
memset (&addr, 0, sizeof (addr));
addr.sin6_family = AF_INET6;
for (struct addrinfo *next = addrs; next; next = next->ai_next)
if (next->ai_family == AF_INET6
&& next->ai_socktype == SOCK_STREAM)
{
sockaddr_in6 *in6 = (sockaddr_in6 *)next->ai_addr;
in6->sin6_port = htons (port);
if (ntohs (in6->sin6_port) != port)
errno = EINVAL;
else if (!connect (fd, next->ai_addr, next->ai_addrlen))
goto done;
}
errstr = "connecting";
goto fail;
done:;
freeaddrinfo (addrs);
return fd;
}
}
#endif

153
libcody/netserver.cc Normal file
View File

@ -0,0 +1,153 @@
// CODYlib -*- mode:c++ -*-
// Copyright (C) 2020 Nathan Sidwell, nathan@acm.org
// License: Apache v2.0
// Cody
#include "internal.hh"
#if CODY_NETWORKING
// C
#include <cerrno>
#include <cstring>
// OS
#include <netdb.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/un.h>
#ifndef AI_NUMERICSERV
#define AI_NUMERICSERV 0
#endif
// Server-side networking helpers
namespace Cody {
int ListenSocket (char const **e, sockaddr const *addr, socklen_t len,
unsigned backlog)
{
char const *errstr = nullptr;
int fd = socket (addr->sa_family, SOCK_STREAM, 0);
if (fd < 0)
{
errstr = "creating socket";
fail:;
int err = errno;
if (e)
*e = errstr;
if (fd >= 0)
close (fd);
errno = err;
return -1;
}
if (bind (fd, addr, len) < 0)
{
errstr = "binding socket";
goto fail;
}
if (listen (fd, backlog ? backlog : 17) < 0)
{
errstr = "listening socket";
goto fail;
}
return fd;
}
int ListenLocal (char const **e, char const *name, unsigned backlog)
{
sockaddr_un addr;
size_t len = strlen (name);
if (len >= sizeof (addr.sun_path))
{
errno = ENAMETOOLONG;
return -1;
}
memset (&addr, 0, offsetof (sockaddr_un, sun_path));
addr.sun_family = AF_UNIX;
memcpy (addr.sun_path, name, len + 1);
return ListenSocket (e, (sockaddr *)&addr, sizeof (addr), backlog);
}
int ListenInet6 (char const **e, char const *name, int port, unsigned backlog)
{
addrinfo *addrs = nullptr;
int fd = -1;
char const *errstr = nullptr;
fd = socket (AF_INET6, SOCK_STREAM, 0);
if (fd < 0)
{
errstr = "creating socket";
fail:;
int err = errno;
if (e)
*e = errstr;
if (fd >= 0)
close (fd);
if (addrs)
freeaddrinfo (addrs);
errno = err;
return -1;
}
addrinfo hints;
hints.ai_flags = AI_NUMERICSERV;
hints.ai_family = AF_INET6;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = 0;
hints.ai_addrlen = 0;
hints.ai_addr = nullptr;
hints.ai_canonname = nullptr;
hints.ai_next = nullptr;
/* getaddrinfo requires a port number, but is quite happy to accept
invalid ones. So don't rely on it. */
if (int err = getaddrinfo (name, "0", &hints, &addrs))
{
errstr = gai_strerror (err);
// What's the best errno to set?
errno = 0;
goto fail;
}
sockaddr_in6 addr;
memset (&addr, 0, sizeof (addr));
addr.sin6_family = AF_INET6;
for (struct addrinfo *next = addrs; next; next = next->ai_next)
if (next->ai_family == AF_INET6
&& next->ai_socktype == SOCK_STREAM)
{
sockaddr_in6 *in6 = (sockaddr_in6 *)next->ai_addr;
in6->sin6_port = htons (port);
if (ntohs (in6->sin6_port) != port)
errno = EINVAL;
else if (!bind (fd, next->ai_addr, next->ai_addrlen))
goto listen;
}
errstr = "binding socket";
goto fail;
listen:;
freeaddrinfo (addrs);
if (listen (fd, backlog ? backlog : 17) < 0)
{
errstr = "listening socket";
goto fail;
}
return fd;
}
}
#endif

50
libcody/packet.cc Normal file
View File

@ -0,0 +1,50 @@
// CODYlib -*- mode:c++ -*-
// Copyright (C) 2020 Nathan Sidwell, nathan@acm.org
// License: Apache v2.0
// Cody
#include "internal.hh"
namespace Cody {
void Packet::Destroy ()
{
switch (cat)
{
case STRING:
// Silly scope destructor name rules
using S = std::string;
string.~S ();
break;
case VECTOR:
using V = std::vector<std::string>;
vector.~V ();
break;
default:;
}
}
void Packet::Create (Packet &&t)
{
cat = t.cat;
code = t.code;
request = t.request;
switch (cat)
{
case STRING:
new (&string) std::string (std::move (t.string));
break;
case VECTOR:
new (&vector) std::vector<std::string> (std::move (t.vector));
break;
default:
integer = t.integer;
break;
}
}
}

209
libcody/resolver.cc Normal file
View File

@ -0,0 +1,209 @@
// CODYlib -*- mode:c++ -*-
// Copyright (C) 2020 Nathan Sidwell, nathan@acm.org
// License: Apache v2.0
// Cody
#include "internal.hh"
// OS
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#if (defined (__unix__) \
|| (defined (__Apple__) \
&& defined (__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) \
&& __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 101000))
// Autoconf test?
#define HAVE_FSTATAT 1
#else
#define HAVE_FSTATAT 0
#endif
// Resolver code
#if __windows__
inline bool IsDirSep (char c)
{
return c == '/' || c == '\\';
}
inline bool IsAbsPath (char const *str)
{
// IIRC windows has the concept of per-drive current directories,
// which make drive-using paths confusing. Let's not get into that.
return IsDirSep (str)
|| (((str[0] >= 'A' && str[1] <= 'Z')
|| (str[0] >= 'a' && str[1] <= 'z'))&& str[1] == ':');
}
#else
inline bool IsDirSep (char c)
{
return c == '/';
}
inline bool IsAbsPath (char const *str)
{
return IsDirSep (str[0]);
}
#endif
constexpr char DIR_SEPARATOR = '/';
constexpr char DOT_REPLACE = ','; // Replace . directories
constexpr char COLON_REPLACE = '-'; // Replace : (partition char)
constexpr char const REPO_DIR[] = "cmi.cache";
namespace Cody {
Resolver::~Resolver ()
{
}
char const *Resolver::GetCMISuffix ()
{
return "cmi";
}
std::string Resolver::GetCMIName (std::string const &module)
{
std::string result;
result.reserve (module.size () + 8);
bool is_header = false;
bool is_abs = false;
if (IsAbsPath (module.c_str ()))
is_header = is_abs = true;
else if (module.front () == '.' && IsDirSep (module.c_str ()[1]))
is_header = true;
if (is_abs)
{
result.push_back ('.');
result.append (module);
}
else
result = std::move (module);
if (is_header)
{
if (!is_abs)
result[0] = DOT_REPLACE;
/* Map .. to DOT_REPLACE, DOT_REPLACE. */
for (size_t ix = 1; ; ix++)
{
ix = result.find ('.', ix);
if (ix == result.npos)
break;
if (ix + 2 > result.size ())
break;
if (result[ix + 1] != '.')
continue;
if (!IsDirSep (result[ix - 1]))
continue;
if (!IsDirSep (result[ix + 2]))
continue;
result[ix] = DOT_REPLACE;
result[ix + 1] = DOT_REPLACE;
}
}
else if (COLON_REPLACE != ':')
{
// There can only be one colon in a module name
auto colon = result.find (':');
if (colon != result.npos)
result[colon] = COLON_REPLACE;
}
if (char const *suffix = GetCMISuffix ())
{
result.push_back ('.');
result.append (suffix);
}
return result;
}
void Resolver::WaitUntilReady (Server *)
{
}
Resolver *Resolver::ConnectRequest (Server *s, unsigned version,
std::string &, std::string &)
{
if (version > Version)
s->ErrorResponse ("version mismatch");
else
s->ConnectResponse ("default");
return this;
}
int Resolver::ModuleRepoRequest (Server *s)
{
s->PathnameResponse (REPO_DIR);
return 0;
}
// Deprecated resolver functions
int Resolver::ModuleExportRequest (Server *s, Flags, std::string &module)
{
auto cmi = GetCMIName (module);
s->PathnameResponse (cmi);
return 0;
}
int Resolver::ModuleImportRequest (Server *s, Flags, std::string &module)
{
auto cmi = GetCMIName (module);
s->PathnameResponse (cmi);
return 0;
}
int Resolver::ModuleCompiledRequest (Server *s, Flags, std::string &)
{
s->OKResponse ();
return 0;
}
int Resolver::IncludeTranslateRequest (Server *s, Flags, std::string &include)
{
bool xlate = false;
// This is not the most efficient
auto cmi = GetCMIName (include);
struct stat statbuf;
#if HAVE_FSTATAT
int fd_dir = open (REPO_DIR, O_RDONLY | O_CLOEXEC | O_DIRECTORY);
if (fd_dir >= 0
&& fstatat (fd_dir, cmi.c_str (), &statbuf, 0) == 0
&& S_ISREG (statbuf.st_mode))
// Sadly can't easily check if this process has read access,
// except by trying to open it.
xlate = true;
if (fd_dir >= 0)
close (fd_dir);
#else
std::string append = REPO_DIR;
append.push_back (DIR_SEPARATOR);
append.append (cmi);
if (stat (append.c_str (), &statbuf) == 0
|| S_ISREG (statbuf.st_mode))
xlate = true;
#endif
if (xlate)
s->PathnameResponse (cmi);
else
s->BoolResponse (false);
return 0;
}
void Resolver::ErrorResponse (Server *server, std::string &&msg)
{
server->ErrorResponse (msg);
}
}

306
libcody/server.cc Normal file
View File

@ -0,0 +1,306 @@
// CODYlib -*- mode:c++ -*-
// Copyright (C) 2020 Nathan Sidwell, nathan@acm.org
// License: Apache v2.0
// Cody
#include "internal.hh"
// C++
#include <tuple>
// C
#include <cerrno>
#include <cstring>
// Server code
namespace Cody {
// These do not need to be members
static Resolver *ConnectRequest (Server *, Resolver *,
std::vector<std::string> &words);
static int ModuleRepoRequest (Server *, Resolver *,
std::vector<std::string> &words);
static int ModuleExportRequest (Server *, Resolver *,
std::vector<std::string> &words);
static int ModuleImportRequest (Server *, Resolver *,
std::vector<std::string> &words);
static int ModuleCompiledRequest (Server *, Resolver *,
std::vector<std::string> &words);
static int IncludeTranslateRequest (Server *, Resolver *,
std::vector<std::string> &words);
namespace {
using RequestFn = int (Server *, Resolver *, std::vector<std::string> &);
using RequestPair = std::tuple<char const *, RequestFn *>;
static RequestPair
const requestTable[Detail::RC_HWM] =
{
// Same order as enum RequestCode
RequestPair {u8"HELLO", nullptr},
RequestPair {u8"MODULE-REPO", ModuleRepoRequest},
RequestPair {u8"MODULE-EXPORT", ModuleExportRequest},
RequestPair {u8"MODULE-IMPORT", ModuleImportRequest},
RequestPair {u8"MODULE-COMPILED", ModuleCompiledRequest},
RequestPair {u8"INCLUDE-TRANSLATE", IncludeTranslateRequest},
};
}
Server::Server (Resolver *r)
: resolver (r), direction (READING)
{
PrepareToRead ();
}
Server::Server (Server &&src)
: write (std::move (src.write)),
read (std::move (src.read)),
resolver (src.resolver),
is_connected (src.is_connected),
direction (src.direction)
{
fd.from = src.fd.from;
fd.to = src.fd.to;
}
Server::~Server ()
{
}
Server &Server::operator= (Server &&src)
{
write = std::move (src.write);
read = std::move (src.read);
resolver = src.resolver;
is_connected = src.is_connected;
direction = src.direction;
fd.from = src.fd.from;
fd.to = src.fd.to;
return *this;
}
void Server::DirectProcess (Detail::MessageBuffer &from,
Detail::MessageBuffer &to)
{
read.PrepareToRead ();
std::swap (read, from);
ProcessRequests ();
resolver->WaitUntilReady (this);
write.PrepareToWrite ();
std::swap (to, write);
}
void Server::ProcessRequests (void)
{
std::vector<std::string> words;
direction = PROCESSING;
while (!read.IsAtEnd ())
{
int err = 0;
unsigned ix = Detail::RC_HWM;
if (!read.Lex (words))
{
Assert (!words.empty ());
while (ix--)
{
if (words[0] != std::get<0> (requestTable[ix]))
continue; // not this one
if (ix == Detail::RC_CONNECT)
{
// CONNECT
if (IsConnected ())
err = -1;
else if (auto *r = ConnectRequest (this, resolver, words))
resolver = r;
else
err = -1;
}
else
{
if (!IsConnected ())
err = -1;
else if (int res = (std::get<1> (requestTable[ix])
(this, resolver, words)))
err = res;
}
break;
}
}
if (err || ix >= Detail::RC_HWM)
{
// Some kind of error
std::string msg;
if (err > 0)
msg = u8"error processing '";
else if (ix >= Detail::RC_HWM)
msg = u8"unrecognized '";
else if (IsConnected () && ix == Detail::RC_CONNECT)
msg = u8"already connected '";
else if (!IsConnected () && ix != Detail::RC_CONNECT)
msg = u8"not connected '";
else
msg = u8"malformed '";
read.LexedLine (msg);
msg.append (u8"'");
if (err > 0)
{
msg.append (u8" ");
msg.append (strerror (err));
}
resolver->ErrorResponse (this, std::move (msg));
}
}
}
// Return numeric value of STR as an unsigned. Returns ~0u on error
// (so that value is not representable).
static unsigned ParseUnsigned (std::string &str)
{
char *eptr;
unsigned long val = strtoul (str.c_str (), &eptr, 10);
if (*eptr || unsigned (val) != val)
return ~0u;
return unsigned (val);
}
Resolver *ConnectRequest (Server *s, Resolver *r,
std::vector<std::string> &words)
{
if (words.size () < 3 || words.size () > 4)
return nullptr;
if (words.size () == 3)
words.emplace_back (u8"");
unsigned version = ParseUnsigned (words[1]);
if (version == ~0u)
return nullptr;
return r->ConnectRequest (s, version, words[2], words[3]);
}
int ModuleRepoRequest (Server *s, Resolver *r,std::vector<std::string> &words)
{
if (words.size () != 1)
return -1;
return r->ModuleRepoRequest (s);
}
int ModuleExportRequest (Server *s, Resolver *r, std::vector<std::string> &words)
{
if (words.size () < 2 || words.size () > 3 || words[1].empty ())
return -1;
Flags flags = Flags::None;
if (words.size () == 3)
{
unsigned val = ParseUnsigned (words[2]);
if (val == ~0u)
return -1;
flags = Flags (val);
}
return r->ModuleExportRequest (s, flags, words[1]);
}
int ModuleImportRequest (Server *s, Resolver *r, std::vector<std::string> &words)
{
if (words.size () < 2 || words.size () > 3 || words[1].empty ())
return -1;
Flags flags = Flags::None;
if (words.size () == 3)
{
unsigned val = ParseUnsigned (words[2]);
if (val == ~0u)
return -1;
flags = Flags (val);
}
return r->ModuleImportRequest (s, flags, words[1]);
}
int ModuleCompiledRequest (Server *s, Resolver *r,
std::vector<std::string> &words)
{
if (words.size () < 2 || words.size () > 3 || words[1].empty ())
return -1;
Flags flags = Flags::None;
if (words.size () == 3)
{
unsigned val = ParseUnsigned (words[2]);
if (val == ~0u)
return -1;
flags = Flags (val);
}
return r->ModuleCompiledRequest (s, flags, words[1]);
}
int IncludeTranslateRequest (Server *s, Resolver *r,
std::vector<std::string> &words)
{
if (words.size () < 2 || words.size () > 3 || words[1].empty ())
return -1;
Flags flags = Flags::None;
if (words.size () == 3)
{
unsigned val = ParseUnsigned (words[2]);
if (val == ~0u)
return -1;
flags = Flags (val);
}
return r->IncludeTranslateRequest (s, flags, words[1]);
}
void Server::ErrorResponse (char const *error, size_t elen)
{
write.BeginLine ();
write.AppendWord (u8"ERROR");
write.AppendWord (error, true, elen);
write.EndLine ();
}
void Server::OKResponse ()
{
write.BeginLine ();
write.AppendWord (u8"OK");
write.EndLine ();
}
void Server::ConnectResponse (char const *agent, size_t alen)
{
is_connected = true;
write.BeginLine ();
write.AppendWord (u8"HELLO");
write.AppendInteger (Version);
write.AppendWord (agent, true, alen);
write.EndLine ();
}
void Server::PathnameResponse (char const *cmi, size_t clen)
{
write.BeginLine ();
write.AppendWord (u8"PATHNAME");
write.AppendWord (cmi, true, clen);
write.EndLine ();
}
void Server::BoolResponse (bool truthiness)
{
write.BeginLine ();
write.AppendWord (u8"BOOL");
write.AppendWord (truthiness ? u8"TRUE" : u8"FALSE");
write.EndLine ();
}
}

View File

@ -0,0 +1,30 @@
// Test client connection handshake
// RUN: <<HELLO 1 TESTING
// RUN: $subdir$stem | ezio -p OUT $test |& ezio -p ERR $test
// RUN-END:
// OUT-NEXT:^HELLO {:[0-9]+} TEST IDENT$
// OUT-NEXT:$EOF
// ERR-NEXT:Code:{:[0-9]+}$
// ERR-NEXT:Version:1$
// ERR-NEXT:$EOF
// Cody
#include "cody.hh"
// C++
#include <iostream>
using namespace Cody;
int main (int, char *[])
{
Client client (0, 1);
auto token = client.Connect ("TEST", "IDENT");
std::cerr << "Code:" << token.GetCode () << '\n';
std::cerr << "Version:" << token.GetInteger () << '\n';
}

View File

@ -0,0 +1,73 @@
// CODYlib -*- mode:c++ -*-
// Copyright (C) 2020 Nathan Sidwell, nathan@acm.org
// License: Apache v2.0
// RUN: <<bob 'frob dob''\nF\_b\20\61\\'
// RUN: $subdir$stem |& ezio $test
// CHECK-NEXT: ^line:0 word:0 'bob'
// CHECK-NEXT: ^line:0 word:1 'frob dob$
// CHECK-OPTION: matchSpace
// CHECK-NEXT: ^F b a\'$
// CHECK-NEXT: $EOF
/* RUN: <<line-1 word:1 ;
RUN: <<'line 2' ;
RUN: <<
*/
// RUN: $subdir$stem |& ezio -p CHECK2 $test
// CHECK2-NEXT: line:0 word:0 'line-1'
// CHECK2-NEXT: line:0 word:1 'word:1'
// CHECK2-NEXT: line:1 word:0 'line 2'
// CHECK2-NEXT: error:No
// CHECK2-NEXT: $EOF
// RUN: <<'
// RUN: $subdir$stem |& ezio -p CHECK3 $test
// CHECK3-NEXT: error:Invalid argument
// CHECK3-NEXT: line:0 word:0 '''
// CHECK3-NEXT: $EOF
/* RUN: << ;
RUN: <<'\g'
*/
// RUN: $subdir$stem |& ezio -p CHECK4 $test
// CHECK4-NEXT: error:No
// CHECK4-NEXT: error:Invalid argument
// CHECK4-NEXT: line:1 word:0 ''\g''
// CHECK4-NEXT: $EOF
// RUN-END:
// Cody
#include "cody.hh"
// C++
#include <iostream>
// C
#include <cstring>
using namespace Cody;
int main (int, char *[])
{
Detail::MessageBuffer reader;
reader.PrepareToRead ();
while (int e = reader.Read (0))
if (e != EAGAIN && e != EINTR)
break;
std::vector<std::string> words;
for (unsigned line = 0; !reader.IsAtEnd (); line++)
{
if (int e = reader.Lex (words))
std::cerr << "error:" << strerror (e) << '\n';
for (unsigned ix = 0; ix != words.size (); ix++)
{
auto &word = words[ix];
std::cerr << "line:" << line << " word:" << ix
<< " '" << word << "'\n";
}
}
return 0;
}

View File

@ -0,0 +1,48 @@
// CODYlib -*- mode:c++ -*-
// Copyright (C) 2020 Nathan Sidwell, nathan@acm.org
// License: Apache v2.0
// Test message encoding, both string quoting and continuation lines
// RUN: $subdir$stem |& ezio $test
// RUN-END:
// The ¯ is utf8-encoded as c2 af
// CHECK-NEXT: ^bob 'frob dob''\n¯\\'$
// CHECK-NEXT: ^2 ;$
// CHECK-NEXT: ^3$
// CHECK-NEXT: $EOF
// Cody
#include "cody.hh"
using namespace Cody;
int main (int, char *[])
{
Detail::MessageBuffer writer;
writer.BeginLine ();
writer.AppendWord ("bob");
writer.AppendWord ("frob dob", true);
writer.Append ("\n\xc2\xaf\\", true);
writer.EndLine ();
writer.PrepareToWrite ();
while (int err = writer.Write (2))
if (err != EAGAIN && err != EINTR)
break;
writer.BeginLine ();
writer.Append ("2", true);
writer.EndLine ();
writer.BeginLine ();
writer.Append ("3", true);
writer.EndLine ();
writer.PrepareToWrite ();
while (int err = writer.Write (2))
if (err != EAGAIN && err != EINTR)
break;
return 0;
}

View File

@ -0,0 +1,97 @@
// Test client message round tripping
/*
RUN: <<HELLO 1 TESTING ;
RUN: <<PATHNAME REPO ;
RUN: <<PATHNAME biz/bar ;
RUN: <<PATHNAME blob ;
RUN: <<BOOL FALSE ;
RUN: << BOOL TRUE ;
RUN: << PATHNAME foo ;
RUN: <<OK
*/
// RUN: $subdir$stem | ezio -p OUT $test |& ezio -p ERR $test
// RUN-END:
/*
OUT-NEXT:^HELLO {:[0-9]+} TEST IDENT ;$
OUT-NEXT:^MODULE-REPO ;
OUT-NEXT:^MODULE-EXPORT bar ;
OUT-NEXT:^MODULE-IMPORT foo ;
OUT-NEXT:^INCLUDE-TRANSLATE baz.frob ;
OUT-NEXT:^INCLUDE-TRANSLATE ./corge ;
OUT-NEXT:^INCLUDE-TRANSLATE ./quux ;
OUT-NEXT:^MODULE-COMPILED bar
*/
// OUT-NEXT:$EOF
// ERR-NEXT:Code:1$
// ERR-NEXT:Integer:1$
// ERR-NEXT:Code:5$
// ERR-NEXT:String:REPO$
// ERR-NEXT:Code:5$
// ERR-NEXT:String:biz/bar$
// ERR-NEXT:Code:5$
// ERR-NEXT:String:blob$
// ERR-NEXT:Code:4$
// ERR-NEXT:Integer:0$
// ERR-NEXT:Code:4$
// ERR-NEXT:Integer:1$
// ERR-NEXT:Code:5$
// ERR-NEXT:String:foo
// ERR-NEXT:Code:3$
// ERR-NEXT:Integer:
// ERR-NEXT:$EOF
// Cody
#include "cody.hh"
// C++
#include <iostream>
using namespace Cody;
int main (int, char *[])
{
Client client (0, 1);
client.Cork ();
if (client.Connect ("TEST", "IDENT").GetCode () != Client::PC_CORKED)
std::cerr << "Not corked!\n";
if (client.ModuleRepo ().GetCode () != Client::PC_CORKED)
std::cerr << "Not corked!\n";
if (client.ModuleExport ("bar").GetCode () != Client::PC_CORKED)
std::cerr << "Not corked!\n";
if (client.ModuleImport ("foo").GetCode () != Client::PC_CORKED)
std::cerr << "Not corked!\n";
if (client.IncludeTranslate ("baz.frob").GetCode () != Client::PC_CORKED)
std::cerr << "Not corked!\n";
if (client.IncludeTranslate ("./corge").GetCode () != Client::PC_CORKED)
std::cerr << "Not corked!\n";
if (client.IncludeTranslate ("./quux").GetCode () != Client::PC_CORKED)
std::cerr << "Not corked!\n";
if (client.ModuleCompiled ("bar").GetCode () != Client::PC_CORKED)
std::cerr << "Not corked!\n";
auto result = client.Uncork ();
for (auto iter = result.begin (); iter != result.end (); ++iter)
{
std::cerr << "Code:" << iter->GetCode () << '\n';
switch (iter->GetCategory ())
{
case Packet::INTEGER:
std::cerr << "Integer:" << iter->GetInteger () << '\n';
break;
case Packet::STRING:
std::cerr << "String:" << iter->GetString () << '\n';
break;
case Packet::VECTOR:
{
auto const &v = iter->GetVector ();
for (unsigned ix = 0; ix != v.size (); ix++)
std::cerr << "Vector[" << ix << "]:" << v[ix] << '\n';
}
break;
}
}
}

View File

@ -0,0 +1,76 @@
// Test resolver pivot
// RUN:<<HELLO 1 TEST IDENT ;
// RUN:<<MODULE-REPO ;
// RUN:<<HELLO 1 TEST IDENT
// RUN: $subdir$stem | ezio -p OUT1 $test |& ezio -p ERR1 $test
// OUT1-NEXT:HELLO 1 default ;
// OUT1-NEXT:PATHNAME cmi.cache ;
// OUT1-NEXT:ERROR 'already connected
// OUT1-NEXT:$EOF
// ERR1-NEXT:resolver is handler
// ERR1-NEXT:$EOF
// RUN:<<MODULE-REPO ;
// RUN:<<HELLO 1 TEST IDENT ;
// RUN:<<MODULE-REPO
// RUN: $subdir$stem | ezio -p OUT2 $test |& ezio -p ERR2 $test
// OUT2-NEXT:ERROR 'not connected
// OUT2-NEXT:HELLO 1 default ;
// OUT2-NEXT:PATHNAME cmi.cache
// OUT2-NEXT:$EOF
// ERR2-NEXT:resolver is handler
// ERR2-NEXT:$EOF
// RUN-END:
#include "cody.hh"
#include <iostream>
using namespace Cody;
class Handler : public Resolver
{
virtual Handler *ConnectRequest (Server *s, unsigned ,
std::string &, std::string &)
{
ErrorResponse (s, "unexpected connect call");
return nullptr;
}
};
Handler handler;
class Initial : public Resolver
{
virtual Handler *ConnectRequest (Server *s, unsigned v,
std::string &agent, std::string &ident)
{
Resolver::ConnectRequest (s, v, agent, ident);
return &handler;
}
};
Initial initial;
int main (int, char *[])
{
Server server (&initial, 0, 1);
while (int e = server.Read ())
if (e != EAGAIN && e != EINTR)
break;
server.ProcessRequests ();
if (server.GetResolver () == &handler)
std::cerr << "resolver is handler\n";
else if (server.GetResolver () == &initial)
std::cerr << "resolver is initial\n";
else
std::cerr << "resolver is surprising\n";
server.PrepareToWrite ();
while (int e = server.Write ())
if (e != EAGAIN && e != EINTR)
break;
}

View File

@ -0,0 +1,68 @@
// Test server message round tripping
/*
RUN:<<HELLO 1 TEST IDENT ;
RUN:<<MODULE-REPO ;
RUN:<<MODULE-EXPORT bar ;
RUN:<<MODULE-IMPORT foo ;
RUN:<<NOT A COMMAND ;
RUN:<<INCLUDE-TRANSLATE baz.frob ;
RUN:<<INCLUDE-TRANSLATE ./quux ;
RUN:<<MODULE-COMPILED bar ;
RUN:<<MODULE-IMPORT ''
*/
// RUN: $subdir$stem | ezio -p OUT1 $test |& ezio -p ERR1 $test
// These all fail because there's nothing in the server interpretting stuff
/*
OUT1-NEXT: ^HELLO 1 default ;
OUT1-NEXT: ^PATHNAME cmi.cache ;
OUT1-NEXT: ^PATHNAME bar.cmi ;
OUT1-NEXT: ^PATHNAME foo.cmi ;
OUT1-NEXT: ^ERROR 'unrecognized \'NOT
OUT1-NEXT: ^BOOL FALSE ;
OUT1-NEXT: ^BOOL FALSE ;
OUT1-NEXT: ^OK
OUT1-NEXT: ^ERROR 'malformed
*/
// OUT1-NEXT:$EOF
// ERR1-NEXT:$EOF
/*
RUN:<<HELLO 1 TEST IDENT
RUN:<<MODULE-REPO
*/
// RUN: $subdir$stem | ezio -p OUT2 $test |& ezio -p ERR2 $test
/*
OUT2-NEXT: ^HELLO 1 default
*/
// OUT2-NEXT:$EOF
// ERR2-NEXT:$EOF
// RUN-END:
// Cody
#include "cody.hh"
// C++
#include <iostream>
using namespace Cody;
int main (int, char *[])
{
Resolver r;
Server server (&r, 0, 1);
while (int e = server.Read ())
if (e != EAGAIN && e != EINTR)
break;
server.ProcessRequests ();
if (server.GetResolver () != &r)
std::cerr << "resolver changed\n";
server.PrepareToWrite ();
while (int e = server.Write ())
if (e != EAGAIN && e != EINTR)
break;
}

36
libcody/tests/Makesub.in Normal file
View File

@ -0,0 +1,36 @@
# CODYlib -*- mode:Makefile -*-
# Copyright (C) 2019-2020 Nathan Sidwell, nathan@acm.org
# License: Apache v2.0
ALOY := @ALOY@
TESTS := $(patsubst $(srcdir)/%.cc,%,\
$(wildcard $(srcdir)/tests/*/*.cc))
TESTDIRS = $(shell cd $(srcdir)/${<D} ; echo *(/))
testdir := $(and $(filter-out /%,$(srcdir)),../)$(srcdir)/tests
check:: tests/cody.defs $(TESTS)
+cd ${<D} && srcbuilddir=$(srcdir)/tests JOUST=${<F} \
$(ALOY) -t kratos -o cody -g $(testdir)/jouster $(TESTDIRS)
ifeq ($(firstword $(aloy)),:)
@echo WARNING: tests were not run as Joust test harness was not found
endif
tests/cody.defs: tests/Makesub
echo '# Automatically generated by Make' >$@
echo 'testdir=$(testdir)' >>$@
echo 'timelimit=60' >>$@
echo 'memlimit=1' >>$@
echo 'cpulimit=60' >>$@
echo 'filelimit=1' >>$@
echo 'SHELL=$(SHELL)' >>$@
$(TESTS): %: %.o libcody.a
$(CXX) $(LDFLAGS) $< -lcody $(LIBS) -o $@
clean::
rm -f $(TESTS)
rm -f $(TESTS:=.o) $(TESTS:=.d)
ifeq ($(filter clean%,$(MAKECMDGOALS)),)
-include $(TESTS:=.d)
endif

11
libcody/tests/jouster Executable file
View File

@ -0,0 +1,11 @@
#! /bin/zsh
# CODYlib -*- mode:Makefile -*-
# Copyright (C) 2019-2020 Nathan Sidwell, nathan@acm.org
# License Apache v2.0
pushd ${0%/*}
setopt nullglob
for subdir in $@ ; do
echo $subdir/*(.^*)
done
popd