diff --git a/CMakeLists.txt b/CMakeLists.txt index d992bcfe39f9..f9cff1f60dbe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -349,11 +349,14 @@ add_subdirectory(compiler) add_subdirectory(frontend) add_subdirectory(tools/chpldoc) -# We are not ready to include 'chpldef' in an official release just yet. -if (NOT CMAKE_BUILD_TYPE STREQUAL "Release") - if (EXISTS tools/chpldef) - add_subdirectory(tools/chpldef) - endif() +# We are not ready to include 'chpldef' in an official release just yet. This +# check works because the 'chpldef' source files are currently not included +# in source releases. +# message(STATUS "CMAKE_CURRENT_SOURCE_DIR: ${CMAKE_CURRENT_SOURCE_DIR}") +# message(STATUS "CMAKE_CURRENT_BINARY_DIR: ${CMAKE_CURRENT_BINARY_DIR}") +if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/tools/chpldef) + message(STATUS "Added chpldef subdirectory...") + add_subdirectory(tools/chpldef) endif() # Adjust the install rpath for chpl and chpldoc diff --git a/Makefile b/Makefile index 98a7b0363087..90de852e535c 100644 --- a/Makefile +++ b/Makefile @@ -111,11 +111,6 @@ third-party-chpldoc-venv: FORCE cd third-party && $(MAKE) chpldoc-venv; \ fi -third-party-chpldef-venv: FORCE - @if [ -z "$$CHPL_DONT_BUILD_CHPLDEF_VENV" ]; then \ - cd third-party && $(MAKE) chpldef-venv; \ - fi - third-party-c2chapel-venv: FORCE @if [ -z "$$CHPL_DONT_BUILD_C2CHAPEL_VENV" ]; then \ cd third-party && $(MAKE) c2chapel-venv; \ @@ -129,11 +124,14 @@ chpldoc: third-party-chpldoc-venv @cd modules && $(MAKE) @test -r Makefile.devel && $(MAKE) man-chpldoc || echo "" -chpldef: compiler third-party-chpldef-venv +chpldef: FORCE @echo "Making chpldef..." @cd third-party && $(MAKE) llvm - @cd third-party && $(MAKE) CHPL_MAKE_HOST_TARGET=--host jemalloc cd compiler && $(MAKE) chpldef + @cd modules && $(MAKE) + +chpldef-fast: FORCE + cd compiler && $(MAKE) chpldef-fast always-build-test-venv: FORCE -@if [ -n "$$CHPL_ALWAYS_BUILD_TEST_VENV" ]; then \ diff --git a/Makefile.devel b/Makefile.devel index b7c4862b75e7..ef0b60ee4345 100644 --- a/Makefile.devel +++ b/Makefile.devel @@ -74,6 +74,9 @@ run-frontend-linters: FORCE run-dyno-linters: run-frontend-linters FORCE +test-chpldef: FORCE + @cd compiler && $(MAKE) test-chpldef + SPECTEST_DIR = ./test/release/examples/spec spectests: FORCE rm -rf $(SPECTEST_DIR) diff --git a/compiler/Makefile b/compiler/Makefile index 51a00bc69052..1a740c37aa5d 100644 --- a/compiler/Makefile +++ b/compiler/Makefile @@ -205,11 +205,24 @@ $(CHPLDOC): FORCE $(COMPILER_BUILD) $(MAKEALLCHPLDOCSUBDIRS) | $(CHPL_BIN_DIR) chpldoc: FORCE $(CHPLDOC) -$(CHPLDEF): FORCE $(CHPLDEF_OBJS) | $(CHPL_BIN_DIR) $(CHPL) +MAKEALLCHPLDEFSUBDIRS = $(CHPLDEF_SUBDIRS:%=%.makedir) + +$(CHPLDEF): FORCE $(COMPILER_BUILD) $(MAKEALLCHPLDEFSUBDIRS) | $(CHPL_BIN_DIR) @cd $(COMPILER_BUILD) && $(CMAKE) $(CHPL_MAKE_HOME) $(CMAKE_FLAGS) && $(MAKE) chpldef chpldef: FORCE $(CHPLDEF) +chpldef-fast: FORCE + @cd $(COMPILER_BUILD) && $(MAKE) chpldef + +test-chpldef: FORCE $(CHPLDEF) + @echo "Making and running the chpldef tests..." + @cd $(COMPILER_BUILD) && $(CMAKE) $(CHPL_MAKE_HOME) $(CMAKE_FLAGS_NO_NDEBUG) && $(MAKE) chpldef-tests + @echo "Making symbolic link to chpldef tests in build/chpldef-test" + @cd ../build && rm -f chpldef-test && ln -s $(COMPILER_BUILD)/tools/chpldef/test chpldef-test + JOBSFLAG=`echo "$$MAKEFLAGS" | sed -n 's/.*\(-j\|--jobs=\) *\([0-9][0-9]*\).*/-j\2/p'` ; \ + cd $(COMPILER_BUILD)/tools/chpldef/test && ctest $$JOBSFLAG . ; + $(COMPILER_BUILD): mkdir -p $@ diff --git a/frontend/include/chpl/framework/ID.h b/frontend/include/chpl/framework/ID.h index 0d554c5972de..008ef28a3a4c 100644 --- a/frontend/include/chpl/framework/ID.h +++ b/frontend/include/chpl/framework/ID.h @@ -217,6 +217,8 @@ class ID final { return symbolPath_.isEmpty(); } + explicit operator bool() const { return !isEmpty(); } + size_t hash() const { (void)numChildIds_; // this field is intentionally not hashed std::hash hasher; diff --git a/frontend/include/chpl/framework/Location.h b/frontend/include/chpl/framework/Location.h index 8841e6063122..7d0c4a1b04cb 100644 --- a/frontend/include/chpl/framework/Location.h +++ b/frontend/include/chpl/framework/Location.h @@ -86,6 +86,20 @@ class Location final { return chpl::hash(path_, firstLine_, firstColumn_, lastLine_, lastColumn_); } + /** True if this contains 'loc' or if this and 'loc' are equal. */ + bool contains(const Location& loc) { + return firstLine() <= loc.firstLine() && + firstColumn() <= loc.firstColumn() && + lastLine() >= loc.lastLine() && + lastColumn() >= loc.lastColumn(); + } + + inline bool operator>(const Location& rhs) { + if (firstLine() > rhs.firstLine()) return true; + return firstLine() == rhs.firstLine() && + firstColumn() > rhs.firstColumn(); + } + void stringify(std::ostream& os, StringifyKind stringifyKind) const; // TODO: We could probably save some space by using a variable byte diff --git a/frontend/include/chpl/framework/query-impl.h b/frontend/include/chpl/framework/query-impl.h index 32fc6ba81317..4a5b8dcf4aae 100644 --- a/frontend/include/chpl/framework/query-impl.h +++ b/frontend/include/chpl/framework/query-impl.h @@ -565,9 +565,9 @@ Context::querySetterUpdateResult( #if CHPL_QUERY_TIMING_AND_TRACE_ENABLED -#define QUERY_BEGIN_TIMING() \ - context->queryBeginTrace(BEGIN_QUERY_FUNC_NAME, BEGIN_QUERY_ARGS); \ - auto QUERY_STOPWATCH = context->makeQueryTimingStopwatch(BEGIN_QUERY_MAP) +#define QUERY_BEGIN_TIMING(context__) \ + context__->queryBeginTrace(BEGIN_QUERY_FUNC_NAME, BEGIN_QUERY_ARGS); \ + auto QUERY_STOPWATCH = context__->makeQueryTimingStopwatch(BEGIN_QUERY_MAP) #else @@ -590,7 +590,7 @@ Context::querySetterUpdateResult( return QUERY_GET_SAVED(); \ } \ auto QUERY_RECOMPUTATION_MARKER = context->markRecomputing(false); \ - QUERY_BEGIN_TIMING(); + QUERY_BEGIN_TIMING(context); /** QUERY_BEGIN_INPUT is like QUERY_BEGIN but should be used @@ -602,7 +602,7 @@ Context::querySetterUpdateResult( return QUERY_GET_SAVED(); \ } \ auto QUERY_RECOMPUTATION_MARKER = context->markRecomputing(false); \ - QUERY_BEGIN_TIMING(); + QUERY_BEGIN_TIMING(context); /** Write diff --git a/frontend/include/chpl/parsing/FileContents.h b/frontend/include/chpl/parsing/FileContents.h index 124b4d60647b..aeace742e621 100644 --- a/frontend/include/chpl/parsing/FileContents.h +++ b/frontend/include/chpl/parsing/FileContents.h @@ -28,9 +28,6 @@ #include namespace chpl { - -class ErrorBase; - namespace parsing { @@ -73,7 +70,7 @@ class FileContents { std::swap(error_, other.error_); } static bool update(FileContents& keep, FileContents& addin) { - return defaultUpdate(keep, addin); + return chpl::defaultUpdate(keep, addin); } void mark(Context* context) const { if (error_ != nullptr) error_->mark(context); diff --git a/modules/dists/BlockCycDist.chpl b/modules/dists/BlockCycDist.chpl index 047ca98c3d47..e4dd38461de6 100644 --- a/modules/dists/BlockCycDist.chpl +++ b/modules/dists/BlockCycDist.chpl @@ -190,8 +190,6 @@ record blockCycDist: writeSerializable { forwarding const chpl_distHelp: chpl_PrivatizedDistHelper(unmanaged BlockCyclicImpl(rank, idxType)); - pragma "last resort" - @unstable("passing arguments other than 'boundingBox' and 'targetLocales' to 'blockCycDist' is currently unstable") proc init(startIdx, blocksize, targetLocales: [] locale = Locales, @@ -211,16 +209,6 @@ record blockCycDist: writeSerializable { value); } - proc init(boundingBox: domain, - targetLocales: [] locale = Locales) - { - this.init(boundingBox, targetLocales, - /* by specifying even one unstable argument, this should select - the whole unstable constructor, which has defaults for everything - else. */ - dataParTasksPerLocale=getDataParTasksPerLocale()); - } - proc init(_pid : int, _instance, _unowned : bool) { this.rank = _instance.rank; this.idxType = _instance.idxType; diff --git a/modules/internal/ChapelArray.chpl b/modules/internal/ChapelArray.chpl index b34496bb49dc..3e354627b311 100644 --- a/modules/internal/ChapelArray.chpl +++ b/modules/internal/ChapelArray.chpl @@ -2216,6 +2216,12 @@ module ChapelArray { return try! "%?".format(x); } + pragma "last resort" + @chpldoc.nodoc + operator :(in x: [] ?et, type t: et) where t == et { + return x; + } + pragma "fn returns aliasing array" @chpldoc.nodoc operator #(arr: [], counts: integral) { diff --git a/modules/internal/ChapelBase.chpl b/modules/internal/ChapelBase.chpl index 4da4182de960..e90916a42324 100644 --- a/modules/internal/ChapelBase.chpl +++ b/modules/internal/ChapelBase.chpl @@ -2168,8 +2168,6 @@ module ChapelBase { isIntegralType(t) || isRealType(t); - inline operator :(x:bool, type t:bool) do - return __primitive("cast", t, x); inline operator :(x:bool, type t:integral) do return __primitive("cast", t, x); inline operator :(x:bool, type t:chpl_anyreal) do diff --git a/modules/standard/IO.chpl b/modules/standard/IO.chpl index 7fc3833fdaaa..c7683e0954d7 100644 --- a/modules/standard/IO.chpl +++ b/modules/standard/IO.chpl @@ -2098,12 +2098,6 @@ proc _modestring(mode:ioMode) { } } -@deprecated("open with a 'style' argument is deprecated") -proc open(path:string, mode:ioMode, hints=ioHintSet.empty, - style:iostyle): file throws { - return openHelper(path, mode, hints, style:iostyleInternal); -} - /* Open a file on a filesystem. Note that once the file is open, you will need to @@ -2129,6 +2123,13 @@ proc open(path:string, mode:ioMode, hints=ioHintSet.empty): file throws { return openHelper(path, mode, hints); } + +@deprecated("open with a 'style' argument is deprecated") +proc open(path:string, mode:ioMode, hints=ioHintSet.empty, + style:iostyle): file throws { + return openHelper(path, mode, hints, style:iostyleInternal); +} + private proc openHelper(path:string, mode:ioMode, hints=ioHintSet.empty, style:iostyleInternal = defaultIOStyleInternal()): file throws { @@ -8206,10 +8207,15 @@ proc fileWriter.writeBinary(b: bytes, size: int = b.size) throws { } } +@chpldoc.nodoc +private proc isSuitableForBinaryReadWrite(arr: _array) param { + return chpl__isDROrDRView(arr); +} + /* Write an array of binary numbers to a ``fileWriter`` - Note that this routine currently requires a 1D rectangular non-strided array. + Note that this routine currently requires a local rectangular non-strided array. :arg data: an array of numbers to write to the fileWriter :arg endian: :type:`ioendian` compile-time argument that specifies the byte @@ -8223,7 +8229,7 @@ proc fileWriter.writeBinary(b: bytes, size: int = b.size) throws { due to a :ref:`system error`. */ proc fileWriter.writeBinary(const ref data: [?d] ?t, param endian:ioendian = ioendian.native) throws - where data.rank == 1 && data.isRectangular() && data.strides == strideKind.one && ( + where isSuitableForBinaryReadWrite(data) && data.strides == strideKind.one && ( isIntegralType(t) || isRealType(t) || isImagType(t) || isComplexType(t) ) { var e : errorCode = 0; @@ -8233,9 +8239,11 @@ proc fileWriter.writeBinary(const ref data: [?d] ?t, param endian:ioendian = ioe const tSize = c_sizeof(t) : c_ssize_t; // Allow either DefaultRectangular arrays or dense slices of DR arrays - const denseDR = chpl__isDROrDRView(data) && - data._value.isDataContiguous(d._value); - if endian == ioendian.native && data.locale == this._home && denseDR { + if !data._value.isDataContiguous(d._value) { + throw new IllegalArgumentError("writeBinary() array data must be contiguous"); + } else if data.locale != this._home { + throw new IllegalArgumentError("writeBinary() array data must be on same locale as 'fileWriter'"); + } else if endian == ioendian.native { e = try qio_channel_write_amt(false, this._channel_internal, data[d.low], data.size:c_ssize_t * tSize); if e != 0 then @@ -8244,7 +8252,7 @@ proc fileWriter.writeBinary(const ref data: [?d] ?t, param endian:ioendian = ioe for b in data { select (endian) { when ioendian.native { - e = try _write_binary_internal(this._channel_internal, _iokind.native, b); + compilerError("unreachable"); } when ioendian.big { e = try _write_binary_internal(this._channel_internal, _iokind.big, b); @@ -8261,10 +8269,17 @@ proc fileWriter.writeBinary(const ref data: [?d] ?t, param endian:ioendian = ioe } } + +@chpldoc.nodoc +proc fileWriter.writeBinary(const ref data: [?d] ?t, param endian:ioendian = ioendian.native) throws { + compilerError("writeBinary() only supports local, rectangular, non-strided arrays"); +} + + /* Write an array of binary numbers to a ``fileWriter`` - Note that this routine currently requires a 1D rectangular non-strided array. + Note that this routine currently requires a local rectangular non-strided array. :arg data: an array of numbers to write to the fileWriter :arg endian: :type:`ioendian` specifies the byte order in which @@ -8277,7 +8292,7 @@ proc fileWriter.writeBinary(const ref data: [?d] ?t, param endian:ioendian = ioe due to a :ref:`system error`. */ proc fileWriter.writeBinary(const ref data: [] ?t, endian:ioendian) throws - where data.rank == 1 && data.isRectangular() && data.strides == strideKind.one && ( + where isSuitableForBinaryReadWrite(data) && data.strides == strideKind.one && ( isIntegralType(t) || isRealType(t) || isImagType(t) || isComplexType(t) ) { select (endian) { @@ -8293,6 +8308,12 @@ proc fileWriter.writeBinary(const ref data: [] ?t, endian:ioendian) throws } } +@chpldoc.nodoc +proc fileWriter.writeBinary(const ref data: [] ?t, endian:ioendian) throws +{ + compilerError("writeBinary() only supports local, rectangular, non-strided arrays"); +} + /* Read a binary number from the ``fileReader`` @@ -8487,7 +8508,7 @@ config param ReadBinaryArrayReturnInt = false; @deprecated(notes="The variant of `readBinary(data: [])` that returns a `bool` is deprecated; please recompile with `-sReadBinaryArrayReturnInt=true` to use the new variant") proc fileReader.readBinary(ref data: [] ?t, param endian = ioendian.native): bool throws where ReadBinaryArrayReturnInt == false && - data.rank == 1 && data.isRectangular() && data.strides == strideKind.one && ( + isSuitableForBinaryReadWrite(data) && data.strides == strideKind.one && ( isIntegralType(t) || isRealType(t) || isImagType(t) || isComplexType(t) ) { var e : errorCode = 0, @@ -8547,7 +8568,7 @@ proc fileReader.readBinary(ref data: [] ?t, param endian = ioendian.native): boo */ proc fileReader.readBinary(ref data: [?d] ?t, param endian = ioendian.native): int throws where ReadBinaryArrayReturnInt == true && - data.rank == 1 && data.isRectangular() && data.strides == strideKind.one && ( + isSuitableForBinaryReadWrite(data) && data.strides == strideKind.one && ( isIntegralType(t) || isRealType(t) || isImagType(t) || isComplexType(t) ) { var e : errorCode = 0, @@ -8556,10 +8577,11 @@ proc fileReader.readBinary(ref data: [?d] ?t, param endian = ioendian.native): i on this._home { try this.lock(); defer { this.unlock(); } - // Allow either DefaultRectangular arrays or dense slices of DR arrays - const denseDR = chpl__isDROrDRView(data) && - data._value.isDataContiguous(d._value); - if data.locale == this._home && denseDR && endian == ioendian.native { + if !data._value.isDataContiguous(d._value) { + throw new IllegalArgumentError("readBinary() array data must be contiguous"); + } else if data.locale != this._home { + throw new IllegalArgumentError("readBinary() array data must be on same locale as 'fileReader'"); + } else if endian == ioendian.native { e = qio_channel_read(false, this._channel_internal, data[d.low], (data.size * c_sizeof(data.eltType)) : c_ssize_t, numRead); if e != 0 && e != EEOF then throw createSystemOrChplError(e); @@ -8567,7 +8589,7 @@ proc fileReader.readBinary(ref data: [?d] ?t, param endian = ioendian.native): i for (i, b) in zip(data.domain, data) { select (endian) { when ioendian.native { - e = try _read_binary_internal(this._channel_internal, _iokind.native, b); + compilerError("unreachable"); } when ioendian.big { e = try _read_binary_internal(this._channel_internal, _iokind.big, b); @@ -8598,7 +8620,7 @@ proc fileReader.readBinary(ref data: [?d] ?t, param endian = ioendian.native): i until ``data`` is full or EOF is reached. An :class:`~OS.UnexpectedEofError` is thrown if EOF is reached before the array is filled. - Note that this routine currently requires a 1D rectangular non-strided array. + Note that this routine currently requires a local rectangular non-strided array. :arg data: an array to read into – existing values are overwritten. :arg endian: :type:`ioendian` specifies the byte order in which @@ -8614,7 +8636,7 @@ proc fileReader.readBinary(ref data: [?d] ?t, param endian = ioendian.native): i @deprecated(notes="The variant of `readBinary(data: [])` that returns a `bool` is deprecated; please recompile with `-sReadBinaryArrayReturnInt=true` to use the new variant") proc fileReader.readBinary(ref data: [] ?t, endian: ioendian):bool throws where ReadBinaryArrayReturnInt == false && - data.rank == 1 && data.isRectangular() && data.strides == strideKind.one && ( + isSuitableForBinaryReadWrite(data) && data.strides == strideKind.one && ( isIntegralType(t) || isRealType(t) || isImagType(t) || isComplexType(t) ) { var rv: bool = false; @@ -8640,7 +8662,7 @@ proc fileReader.readBinary(ref data: [] ?t, endian: ioendian):bool throws Binary values of the type ``data.eltType`` are consumed from the fileReader until ``data`` is full or EOF is reached. - Note that this routine currently requires a 1D rectangular non-strided array. + Note that this routine currently requires a local rectangular non-strided array. :arg data: an array to read into – existing values are overwritten. :arg endian: :type:`ioendian` specifies the byte order in which @@ -8654,7 +8676,7 @@ proc fileReader.readBinary(ref data: [] ?t, endian: ioendian):bool throws */ proc fileReader.readBinary(ref data: [] ?t, endian: ioendian):int throws where ReadBinaryArrayReturnInt == true && - data.rank == 1 && data.isRectangular() && data.strides == strideKind.one && ( + isSuitableForBinaryReadWrite(data) && data.strides == strideKind.one && ( isIntegralType(t) || isRealType(t) || isImagType(t) || isComplexType(t) ) { var nr: int = 0; @@ -8674,6 +8696,19 @@ proc fileReader.readBinary(ref data: [] ?t, endian: ioendian):int throws return nr; } +@chpldoc.nodoc +proc fileReader.readBinary(ref data: [] ?t, endian: ioendian):int throws +{ + compilerError("readBinary() only supports local, rectangular, non-strided arrays"); +} + +@chpldoc.nodoc +proc fileReader.readBinary(ref data: [] ?t, param endian = ioendian.native): bool throws +{ + compilerError("readBinary() only supports local, rectangular, non-strided arrays"); +} + + /* Read up to ``maxBytes`` bytes from a ``fileReader`` into a :class:`~CTypes.c_ptr` diff --git a/test/distributions/robust/arithmetic/resizing/resizeFromOtherLocale.chpl b/test/distributions/robust/arithmetic/resizing/resizeFromOtherLocale.chpl index cb4e1c0d091a..032812f9fbfc 100644 --- a/test/distributions/robust/arithmetic/resizing/resizeFromOtherLocale.chpl +++ b/test/distributions/robust/arithmetic/resizing/resizeFromOtherLocale.chpl @@ -38,7 +38,7 @@ proc buildSpace(Dom) { return Dom dmapped stencilDist(boundingBox={1..3}); } else if distType == DistType.blockcyclic { - return Dom dmapped BlockCyclic(startIdx=1, blocksize=2); + return Dom dmapped blockCycDist(startIdx=1, blocksize=2); } else { compilerError("Compiling with unknown DistType."); diff --git a/test/expressions/casts/promotedArraySelfCast.chpl b/test/expressions/casts/promotedArraySelfCast.chpl new file mode 100644 index 000000000000..e9f249b3167e --- /dev/null +++ b/test/expressions/casts/promotedArraySelfCast.chpl @@ -0,0 +1,7 @@ +record R { var x: int; } + +var A = [new R(45), new R(33)]; + +var B = A: R; + +writeln(B); diff --git a/test/expressions/casts/promotedArraySelfCast.good b/test/expressions/casts/promotedArraySelfCast.good new file mode 100644 index 000000000000..c9bf18134d32 --- /dev/null +++ b/test/expressions/casts/promotedArraySelfCast.good @@ -0,0 +1 @@ +(x = 45) (x = 33) diff --git a/test/io/serializers/unstableWriting.good b/test/io/serializers/unstableWriting.good index dba51ec4b2f1..4969d2311b34 100644 --- a/test/io/serializers/unstableWriting.good +++ b/test/io/serializers/unstableWriting.good @@ -1,22 +1,22 @@ -$CHPL_HOME/modules/standard/JSON.chpl:66: In method 'serializeValue': -$CHPL_HOME/modules/standard/JSON.chpl:79: warning: Serialization of ranges with non-default Serializer is unstable, and may change in the future - $CHPL_HOME/modules/standard/IO.chpl:5921: called as jsonSerializer.serializeValue(writer: fileWriter(false,jsonSerializer), val: range(int(64),both,one)) from method '_serializeOne' - $CHPL_HOME/modules/standard/IO.chpl:9102: called as (fileWriter(true,jsonSerializer))._serializeOne(x: range(int(64),both,one), loc: locale) from method 'write' - $CHPL_HOME/modules/standard/IO.chpl:9164: called as (fileWriter(true,jsonSerializer)).write(args(0): range(int(64),both,one), args(1): chpl_ioNewline) from method 'writeln' +$CHPL_HOME/modules/standard/JSON.chpl:nnnn: In method 'serializeValue': +$CHPL_HOME/modules/standard/JSON.chpl:nnnn: warning: Serialization of ranges with non-default Serializer is unstable, and may change in the future + $CHPL_HOME/modules/standard/IO.chpl:nnnn: called as jsonSerializer.serializeValue(writer: fileWriter(false,jsonSerializer), val: range(int(64),both,one)) from method '_serializeOne' + $CHPL_HOME/modules/standard/IO.chpl:nnnn: called as (fileWriter(true,jsonSerializer))._serializeOne(x: range(int(64),both,one), loc: locale) from method 'write' + $CHPL_HOME/modules/standard/IO.chpl:nnnn: called as (fileWriter(true,jsonSerializer)).write(args(0): range(int(64),both,one), args(1): chpl_ioNewline) from method 'writeln' unstableWriting.chpl:7: called as (fileWriter(true,jsonSerializer)).writeln(args(0): range(int(64),both,one)) from function 'helper' unstableWriting.chpl:11: called as helper(thing: range(int(64),both,one)) -$CHPL_HOME/modules/standard/JSON.chpl:66: In method 'serializeValue': -$CHPL_HOME/modules/standard/JSON.chpl:79: warning: Serialization of rectangular domains with non-default Serializer is unstable, and may change in the future - $CHPL_HOME/modules/standard/IO.chpl:5921: called as jsonSerializer.serializeValue(writer: fileWriter(false,jsonSerializer), val: domain(1,int(64),one)) from method '_serializeOne' - $CHPL_HOME/modules/standard/IO.chpl:9102: called as (fileWriter(true,jsonSerializer))._serializeOne(x: domain(1,int(64),one), loc: locale) from method 'write' - $CHPL_HOME/modules/standard/IO.chpl:9164: called as (fileWriter(true,jsonSerializer)).write(args(0): domain(1,int(64),one), args(1): chpl_ioNewline) from method 'writeln' +$CHPL_HOME/modules/standard/JSON.chpl:nnnn: In method 'serializeValue': +$CHPL_HOME/modules/standard/JSON.chpl:nnnn: warning: Serialization of rectangular domains with non-default Serializer is unstable, and may change in the future + $CHPL_HOME/modules/standard/IO.chpl:nnnn: called as jsonSerializer.serializeValue(writer: fileWriter(false,jsonSerializer), val: domain(1,int(64),one)) from method '_serializeOne' + $CHPL_HOME/modules/standard/IO.chpl:nnnn: called as (fileWriter(true,jsonSerializer))._serializeOne(x: domain(1,int(64),one), loc: locale) from method 'write' + $CHPL_HOME/modules/standard/IO.chpl:nnnn: called as (fileWriter(true,jsonSerializer)).write(args(0): domain(1,int(64),one), args(1): chpl_ioNewline) from method 'writeln' unstableWriting.chpl:7: called as (fileWriter(true,jsonSerializer)).writeln(args(0): domain(1,int(64),one)) from function 'helper' unstableWriting.chpl:12: called as helper(thing: domain(1,int(64),one)) -$CHPL_HOME/modules/standard/JSON.chpl:66: In method 'serializeValue': -$CHPL_HOME/modules/standard/JSON.chpl:79: warning: Serialization of iterators with non-default Serializer is unstable, and may change in the future - $CHPL_HOME/modules/standard/IO.chpl:5921: called as jsonSerializer.serializeValue(writer: fileWriter(false,jsonSerializer), val: iterator) from method '_serializeOne' - $CHPL_HOME/modules/standard/IO.chpl:9102: called as (fileWriter(true,jsonSerializer))._serializeOne(x: iterator, loc: locale) from method 'write' - $CHPL_HOME/modules/standard/IO.chpl:9164: called as (fileWriter(true,jsonSerializer)).write(args(0): iterator, args(1): chpl_ioNewline) from method 'writeln' +$CHPL_HOME/modules/standard/JSON.chpl:nnnn: In method 'serializeValue': +$CHPL_HOME/modules/standard/JSON.chpl:nnnn: warning: Serialization of iterators with non-default Serializer is unstable, and may change in the future + $CHPL_HOME/modules/standard/IO.chpl:nnnn: called as jsonSerializer.serializeValue(writer: fileWriter(false,jsonSerializer), val: iterator) from method '_serializeOne' + $CHPL_HOME/modules/standard/IO.chpl:nnnn: called as (fileWriter(true,jsonSerializer))._serializeOne(x: iterator, loc: locale) from method 'write' + $CHPL_HOME/modules/standard/IO.chpl:nnnn: called as (fileWriter(true,jsonSerializer)).write(args(0): iterator, args(1): chpl_ioNewline) from method 'writeln' unstableWriting.chpl:7: called as (fileWriter(true,jsonSerializer)).writeln(args(0): iterator) from function 'helper' unstableWriting.chpl:13: called as helper(thing: iterator) "1..10" diff --git a/test/io/serializers/unstableWriting.prediff b/test/io/serializers/unstableWriting.prediff new file mode 100755 index 000000000000..7f8b7caeb136 --- /dev/null +++ b/test/io/serializers/unstableWriting.prediff @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# +# Designed to squash out line numbers from modules files to prevent +# sensitivities to code locations. Borrowed from +# `test/functions/operatorOverloads/operatorMethods/nestedMethodBad.prediff` +# + +tmpfile=$2 + +tmptmp=`mktemp "tmp.XXXXXX"` + +regex='\|CHPL_HOME/modules|s/:[0-9:]*:/:nnnn:/' + +sed -e "$regex" $tmpfile > $tmptmp + +mv $tmptmp $tmpfile diff --git a/test/library/standard/IO/readBinary/multiloc/binaryArray.notest b/test/library/standard/IO/readBinary/multiloc/binaryArray.notest new file mode 100644 index 000000000000..ed51dbd48b58 --- /dev/null +++ b/test/library/standard/IO/readBinary/multiloc/binaryArray.notest @@ -0,0 +1 @@ +bug: uncaught exceptions from serialization code seem to cause GASNet segfaults diff --git a/test/library/standard/IO/writeBinary/multiDimArraySlices-block.good b/test/library/standard/IO/writeBinary/multiDimArraySlices-block.good new file mode 100644 index 000000000000..c6bbba4d9e46 --- /dev/null +++ b/test/library/standard/IO/writeBinary/multiDimArraySlices-block.good @@ -0,0 +1 @@ +multiDimArraySlices.chpl:15: error: writeBinary() only supports local, rectangular, non-strided arrays diff --git a/test/library/standard/IO/writeBinary/multiDimArraySlices-col.good b/test/library/standard/IO/writeBinary/multiDimArraySlices-col.good new file mode 100644 index 000000000000..1f5e43974a14 --- /dev/null +++ b/test/library/standard/IO/writeBinary/multiDimArraySlices-col.good @@ -0,0 +1,13 @@ +100.01 100.02 100.03 100.04 100.05 100.06 100.07 100.08 100.09 100.1 +200.01 200.02 200.03 200.04 200.05 200.06 200.07 200.08 200.09 200.1 +300.01 300.02 300.03 300.04 300.05 300.06 300.07 300.08 300.09 300.1 +400.01 400.02 400.03 400.04 400.05 400.06 400.07 400.08 400.09 400.1 +500.01 500.02 500.03 500.04 500.05 500.06 500.07 500.08 500.09 500.1 +600.01 600.02 600.03 600.04 600.05 600.06 600.07 600.08 600.09 600.1 +700.01 700.02 700.03 700.04 700.05 700.06 700.07 700.08 700.09 700.1 +800.01 800.02 800.03 800.04 800.05 800.06 800.07 800.08 800.09 800.1 +900.01 900.02 900.03 900.04 900.05 900.06 900.07 900.08 900.09 900.1 +1000.01 1000.02 1000.03 1000.04 1000.05 1000.06 1000.07 1000.08 1000.09 1000.1 +uncaught IllegalArgumentError: writeBinary() array data must be contiguous + multiDimArraySlices.chpl:15: thrown here + multiDimArraySlices.chpl:15: uncaught here diff --git a/test/library/standard/IO/writeBinary/multiDimArraySlices-row.good b/test/library/standard/IO/writeBinary/multiDimArraySlices-row.good new file mode 100644 index 000000000000..c0221128965c --- /dev/null +++ b/test/library/standard/IO/writeBinary/multiDimArraySlices-row.good @@ -0,0 +1,21 @@ +100.01 100.02 100.03 100.04 100.05 100.06 100.07 100.08 100.09 100.1 +200.01 200.02 200.03 200.04 200.05 200.06 200.07 200.08 200.09 200.1 +300.01 300.02 300.03 300.04 300.05 300.06 300.07 300.08 300.09 300.1 +400.01 400.02 400.03 400.04 400.05 400.06 400.07 400.08 400.09 400.1 +500.01 500.02 500.03 500.04 500.05 500.06 500.07 500.08 500.09 500.1 +600.01 600.02 600.03 600.04 600.05 600.06 600.07 600.08 600.09 600.1 +700.01 700.02 700.03 700.04 700.05 700.06 700.07 700.08 700.09 700.1 +800.01 800.02 800.03 800.04 800.05 800.06 800.07 800.08 800.09 800.1 +900.01 900.02 900.03 900.04 900.05 900.06 900.07 900.08 900.09 900.1 +1000.01 1000.02 1000.03 1000.04 1000.05 1000.06 1000.07 1000.08 1000.09 1000.1 +0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 +200.01 200.02 200.03 200.04 200.05 200.06 200.07 200.08 200.09 200.1 +0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 +Matched! diff --git a/test/library/standard/IO/writeBinary/multiDimArraySlices.chpl b/test/library/standard/IO/writeBinary/multiDimArraySlices.chpl new file mode 100644 index 000000000000..7d2e5149a7ca --- /dev/null +++ b/test/library/standard/IO/writeBinary/multiDimArraySlices.chpl @@ -0,0 +1,32 @@ +use IO, BlockDist; + +config const useCol = false; +config param useBlock = false; + +var Dom = if useBlock then blockDist.createDomain({1..10, 1..10}) + else {1..10, 1..10}; +var Slice = if useCol then {1..10, 2..2} else {2..2, 1..10}; + +var A: [Dom] real = [(i,j) in Dom] i * 100 + j/100.0; + +writeln(A); + +var outfile = open("out.bin", ioMode.cw); +outfile.writer().writeBinary(A[Slice]); +outfile.close(); + +var B: [Dom] real; + +var infile = open("out.bin", ioMode.r); +infile.reader().readBinary(B[Slice]); +infile.close(); + +writeln(B); + +const equalIn = + reduce (A[Slice] == B[Slice]); +const equalZero = + reduce (B == 0); + +if equalIn + equalZero != Dom.size then + halt("Mismatch between A and B"); +else + writeln("Matched!"); diff --git a/test/library/standard/IO/writeBinary/multiDimArraySlices.compopts b/test/library/standard/IO/writeBinary/multiDimArraySlices.compopts new file mode 100644 index 000000000000..3ef106c10abf --- /dev/null +++ b/test/library/standard/IO/writeBinary/multiDimArraySlices.compopts @@ -0,0 +1,2 @@ +-sReadBinaryArrayReturnInt=true +-sReadBinaryArrayReturnInt=true -suseBlock=true # multiDimArraySlices-block.good diff --git a/test/library/standard/IO/writeBinary/multiDimArraySlices.execopts b/test/library/standard/IO/writeBinary/multiDimArraySlices.execopts new file mode 100644 index 000000000000..0974b2a558d9 --- /dev/null +++ b/test/library/standard/IO/writeBinary/multiDimArraySlices.execopts @@ -0,0 +1,2 @@ + # multiDimArraySlices-row.good +--useCol=true # multiDimArraySlices-col.good diff --git a/test/library/standard/IO/writeBinary/multiloc/writeArray.notest b/test/library/standard/IO/writeBinary/multiloc/writeArray.notest new file mode 100644 index 000000000000..ed51dbd48b58 --- /dev/null +++ b/test/library/standard/IO/writeBinary/multiloc/writeArray.notest @@ -0,0 +1 @@ +bug: uncaught exceptions from serialization code seem to cause GASNet segfaults diff --git a/test/library/standard/IO/writeBinary/readWriteMultidimArray-block.good b/test/library/standard/IO/writeBinary/readWriteMultidimArray-block.good new file mode 100644 index 000000000000..0ead49fa3d38 --- /dev/null +++ b/test/library/standard/IO/writeBinary/readWriteMultidimArray-block.good @@ -0,0 +1 @@ +readWriteMultidimArray.chpl:13: error: writeBinary() only supports local, rectangular, non-strided arrays diff --git a/test/library/standard/IO/writeBinary/readWriteMultidimArray.chpl b/test/library/standard/IO/writeBinary/readWriteMultidimArray.chpl new file mode 100644 index 000000000000..35878cc636c1 --- /dev/null +++ b/test/library/standard/IO/writeBinary/readWriteMultidimArray.chpl @@ -0,0 +1,27 @@ +use IO, BlockDist; + +config param useBlock = false; + +var Dom = if useBlock then blockDist.createDomain({1..10, 1..10}) + else {1..10, 1..10}; + +var A: [Dom] real = [(i,j) in Dom] i * 100 + j/100.0; + +writeln(A); + +var outfile = open("out.bin", ioMode.cw); +outfile.writer().writeBinary(A); +outfile.close(); + +var B: [Dom] real; + +var infile = open("out.bin", ioMode.r); +infile.reader().readBinary(B); +infile.close(); + +writeln(B); + +if + reduce (A == B) != Dom.size then + halt("Mismatch between A and B"); +else + writeln("Matched!"); diff --git a/test/library/standard/IO/writeBinary/readWriteMultidimArray.compopts b/test/library/standard/IO/writeBinary/readWriteMultidimArray.compopts new file mode 100644 index 000000000000..d587385b0c9a --- /dev/null +++ b/test/library/standard/IO/writeBinary/readWriteMultidimArray.compopts @@ -0,0 +1,2 @@ +-sReadBinaryArrayReturnInt=true +-sReadBinaryArrayReturnInt=true -suseBlock=true # readWriteMultidimArray-block.good diff --git a/test/library/standard/IO/writeBinary/readWriteMultidimArray.good b/test/library/standard/IO/writeBinary/readWriteMultidimArray.good new file mode 100644 index 000000000000..a8d4eb1b3a70 --- /dev/null +++ b/test/library/standard/IO/writeBinary/readWriteMultidimArray.good @@ -0,0 +1,21 @@ +100.01 100.02 100.03 100.04 100.05 100.06 100.07 100.08 100.09 100.1 +200.01 200.02 200.03 200.04 200.05 200.06 200.07 200.08 200.09 200.1 +300.01 300.02 300.03 300.04 300.05 300.06 300.07 300.08 300.09 300.1 +400.01 400.02 400.03 400.04 400.05 400.06 400.07 400.08 400.09 400.1 +500.01 500.02 500.03 500.04 500.05 500.06 500.07 500.08 500.09 500.1 +600.01 600.02 600.03 600.04 600.05 600.06 600.07 600.08 600.09 600.1 +700.01 700.02 700.03 700.04 700.05 700.06 700.07 700.08 700.09 700.1 +800.01 800.02 800.03 800.04 800.05 800.06 800.07 800.08 800.09 800.1 +900.01 900.02 900.03 900.04 900.05 900.06 900.07 900.08 900.09 900.1 +1000.01 1000.02 1000.03 1000.04 1000.05 1000.06 1000.07 1000.08 1000.09 1000.1 +100.01 100.02 100.03 100.04 100.05 100.06 100.07 100.08 100.09 100.1 +200.01 200.02 200.03 200.04 200.05 200.06 200.07 200.08 200.09 200.1 +300.01 300.02 300.03 300.04 300.05 300.06 300.07 300.08 300.09 300.1 +400.01 400.02 400.03 400.04 400.05 400.06 400.07 400.08 400.09 400.1 +500.01 500.02 500.03 500.04 500.05 500.06 500.07 500.08 500.09 500.1 +600.01 600.02 600.03 600.04 600.05 600.06 600.07 600.08 600.09 600.1 +700.01 700.02 700.03 700.04 700.05 700.06 700.07 700.08 700.09 700.1 +800.01 800.02 800.03 800.04 800.05 800.06 800.07 800.08 800.09 800.1 +900.01 900.02 900.03 900.04 900.05 900.06 900.07 900.08 900.09 900.1 +1000.01 1000.02 1000.03 1000.04 1000.05 1000.06 1000.07 1000.08 1000.09 1000.1 +Matched! diff --git a/test/release/examples/benchmarks/shootout/mandelbrot-fast.chpl b/test/release/examples/benchmarks/shootout/mandelbrot-fast.chpl index 9d987f9dab29..2b81cc04e7dc 100644 --- a/test/release/examples/benchmarks/shootout/mandelbrot-fast.chpl +++ b/test/release/examples/benchmarks/shootout/mandelbrot-fast.chpl @@ -60,13 +60,12 @@ proc main() { } // Get a lock-free, binary fileWriter on 'stdout' - var w = (new file(1)).writer(locking=false, - serializer=new binarySerializer()); + var w = (new file(1)).writer(locking=false); // Write the file header and the image array. w.writef("P4\n"); w.writef("%i %i\n", n, n); - w.write(image); + w.writeBinary(image); } // diff --git a/test/release/examples/benchmarks/shootout/mandelbrot.chpl b/test/release/examples/benchmarks/shootout/mandelbrot.chpl index 35870247190b..ff3ad95bc9f3 100644 --- a/test/release/examples/benchmarks/shootout/mandelbrot.chpl +++ b/test/release/examples/benchmarks/shootout/mandelbrot.chpl @@ -55,10 +55,9 @@ proc main() { // Get a lock-free, binary fileWriter on 'stdout', write the file header, // and the image array. // - var w = (new file(1)).writer(locking=false, - serializer=new binarySerializer()); + var w = (new file(1)).writer(locking=false); w.writef("P4\n"); w.writef("%i %i\n", n, n); - w.write(image); + w.writeBinary(image); } diff --git a/test/studies/shootout/mandelbrot/bradc/mandelbrot-blc.chpl b/test/studies/shootout/mandelbrot/bradc/mandelbrot-blc.chpl index d0b115dc241d..59ed271a4bcf 100644 --- a/test/studies/shootout/mandelbrot/bradc/mandelbrot-blc.chpl +++ b/test/studies/shootout/mandelbrot/bradc/mandelbrot-blc.chpl @@ -60,12 +60,12 @@ proc main() { } // Get a lock-free writer channel on 'stdout' - var w = (new file(1)).writer(serializer=new binarySerializer(), locking=false); + var w = (new file(1)).writer(locking=false); // Write the file header and the image array. w.writef("P4\n"); w.writef("%i %i\n", n, n); - w.write(image); + w.writeBinary(image); } // diff --git a/test/studies/shootout/mandelbrot/ferguson/mandelbrot-opt2.chpl b/test/studies/shootout/mandelbrot/ferguson/mandelbrot-opt2.chpl index f794bf8acffe..8b5ae37f1c62 100644 --- a/test/studies/shootout/mandelbrot/ferguson/mandelbrot-opt2.chpl +++ b/test/studies/shootout/mandelbrot/ferguson/mandelbrot-opt2.chpl @@ -27,8 +27,7 @@ proc main() { var c_im:real = (upper.im - lower.im) * ipart / size + lower.im; var start = datastart+ipart*cols; var end = datastart+(ipart+1)*cols; - var writer = f.writer(serializer=new binarySerializer(), locking=false, - region=start..#end); + var writer = f.writer(locking=false, region=start..#end); for rstart in 0..#cols { @@ -56,7 +55,7 @@ proc main() { if mask == 0 then break; } - writer.write(mask:uint(8)); + writer.writeBinary(mask:uint(8)); } writer.close(); diff --git a/test/studies/shootout/mandelbrot/ferguson/mandelbrot-tricks.chpl b/test/studies/shootout/mandelbrot/ferguson/mandelbrot-tricks.chpl index c0488e03b734..56bda54bc44f 100644 --- a/test/studies/shootout/mandelbrot/ferguson/mandelbrot-tricks.chpl +++ b/test/studies/shootout/mandelbrot/ferguson/mandelbrot-tricks.chpl @@ -71,12 +71,12 @@ proc main() { } // Get a lock-free writer channel on 'stdout' - var w = (new file(1)).writer(serializer=new binarySerializer(), locking=false); + var w = (new file(1)).writer(locking=false); // Write the file header and the image array. w.writef("P4\n"); w.writef("%i %i\n", n, n); - w.write(image); + w.writeBinary(image); } // diff --git a/test/studies/shootout/mandelbrot/lydia/mandelbrot-unzipped.chpl b/test/studies/shootout/mandelbrot/lydia/mandelbrot-unzipped.chpl index 7fbfa73435cb..c4d1d0e483b1 100644 --- a/test/studies/shootout/mandelbrot/lydia/mandelbrot-unzipped.chpl +++ b/test/studies/shootout/mandelbrot/lydia/mandelbrot-unzipped.chpl @@ -49,9 +49,9 @@ proc main() } var f = new file(1); - var w = f.writer(serializer=new binarySerializer(), locking=false); + var w = f.writer(locking=false); w.writef("P4\n%i %i\n", size, size); - w.write(byteArr); + w.writeBinary(byteArr); } diff --git a/third-party/Makefile b/third-party/Makefile index 62d9a8a86248..970e218093cd 100644 --- a/third-party/Makefile +++ b/third-party/Makefile @@ -87,9 +87,6 @@ test-venv: FORCE chpldoc-venv: FORCE cd chpl-venv && $(MAKE) chpldoc-venv -chpldef-venv: FORCE - cd chpl-venv && $(MAKE) chpldef-venv - c2chapel-venv: FORCE cd chpl-venv && $(MAKE) c2chapel-venv diff --git a/third-party/chpl-venv/Makefile b/third-party/chpl-venv/Makefile index 50ece1506a17..107cddb90f70 100644 --- a/third-party/chpl-venv/Makefile +++ b/third-party/chpl-venv/Makefile @@ -90,8 +90,6 @@ test-venv: install-chpldeps chpldoc-venv: install-chpldeps -chpldef-venv: install-chpldeps - c2chapel-venv: install-chpldeps install-requirements: install-chpldeps diff --git a/tools/chpldef/CMakeLists.txt b/tools/chpldef/CMakeLists.txt index 14aafef2a4fd..e382a97c6413 100644 --- a/tools/chpldef/CMakeLists.txt +++ b/tools/chpldef/CMakeLists.txt @@ -15,19 +15,42 @@ # See the License for the specific language governing permissions and # limitations under the License. -add_executable(chpldef - chpldef.cpp +# Put all the binary source files into an object blob. +add_library(chpldef-objects command-line-flags.cpp + compiler-gadgets.cpp + compute-goto-declaration.cpp + compute-lifecycle.cpp + compute-synchronization.cpp + events.cpp + Format.cpp Logger.cpp Message.cpp - message-compute.cpp misc.cpp protocol-types.cpp Server.cpp Transport.cpp) -set_property(TARGET chpldef PROPERTY CXX_STANDARD 17) -target_link_libraries(chpldef ChplFrontend) -target_include_directories(chpldef PRIVATE + +# Link the Chapel frontend to the object blob. +target_link_libraries(chpldef-objects ChplFrontend) +target_include_directories(chpldef-objects PRIVATE ${CHPL_MAIN_INCLUDE_DIR} ${CHPL_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) +set_property(TARGET chpldef-objects PROPERTY CXX_STANDARD 17) + +# Create the 'chpldef' executable. +add_executable(chpldef chpldef.cpp) +target_link_libraries(chpldef chpldef-objects) +set(CHPLDEF_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) +target_include_directories(chpldef PRIVATE + ${CHPL_MAIN_INCLUDE_DIR} + ${CHPL_INCLUDE_DIR} + ${CHPLDEF_INCLUDE_DIR}) +set_property(TARGET chpldef PROPERTY CXX_STANDARD 17) + +# Enable testing if the test folder exists. +if (IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/test") + enable_testing() + add_subdirectory(test) +endif() diff --git a/tools/chpldef/Format.cpp b/tools/chpldef/Format.cpp new file mode 100644 index 000000000000..2a8a2b019941 --- /dev/null +++ b/tools/chpldef/Format.cpp @@ -0,0 +1,48 @@ +/* + * Copyright 2020-2023 Hewlett Packard Enterprise Development LP + * Copyright 2004-2019 Cray Inc. + * Other additional copyright holders may be indicated within. + * + * The entirety of this work is 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. + */ + +#include "events.h" +#include "Server.h" +#include "chpl/parsing/parsing-queries.h" +#include + +namespace chpldef { + +void Format::writeit(const chpl::uast::AstNode& t) { + ctx_->withChapel([&](auto chapel) { + ss_ << chpl::uast::asttags::tagToString(t.tag()) << " "; + if (auto nd = t.toNamedDecl()) ss_ << "'" << nd->name().c_str() << "' "; + auto loc = chpl::parsing::locateAst(chapel, &t); + ss_ << "[" << loc.path().c_str() << ":" << loc.firstLine(); + ss_ << ":" << loc.firstColumn() << "]"; + }); +} + +void Format::writeit(const chpl::Location& t) { + ss_ << t.path().c_str() << ":" << t.firstLine() << ":" << t.firstColumn(); + ss_ << "-" << t.lastLine() << ":" << t.lastColumn(); +} + +void Format::writeit(const chpldef::Message& msg) { + ss_ << Message::tagToString(msg.tag()); + if (!msg.isNotification()) ss_ << " (" << msg.idToString() << ")"; +} + +} // end namespace 'chpldef' diff --git a/tools/chpldef/Format.h b/tools/chpldef/Format.h new file mode 100644 index 000000000000..7e3ff5e3059f --- /dev/null +++ b/tools/chpldef/Format.h @@ -0,0 +1,65 @@ +/* + * Copyright 2020-2023 Hewlett Packard Enterprise Development LP + * Copyright 2004-2019 Cray Inc. + * Other additional copyright holders may be indicated within. + * + * The entirety of this work is 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. + */ + +#ifndef CHPL_TOOLS_CHPLDEF_FORMAT_H +#define CHPL_TOOLS_CHPLDEF_FORMAT_H + +#include "Message.h" +#include "chpl/uast/AstNode.h" +#include "chpl/framework/Location.h" +#include "chpl/framework/UniqueString.h" +#include + +namespace chpldef { + +class Format { +public: + enum Detail { + DEFAULT = 0 + }; +private: + std::stringstream ss_; + Server* ctx_; + Detail dt_; + + void writeit(const chpl::uast::AstNode& t); + void writeit(const chpl::Location& t); + void writeit(const chpldef::Message& t); +public: + Format(Server* ctx, Detail dt) : ctx_(ctx), dt_(dt) {} + ~Format() = default; + + inline std::string read() const { return ss_.str(); } + + template + void write(const T& t) { + std::ignore = dt_; writeit(t); + } + + template + void write(T* const& t) { + if (t == nullptr) { ss_ << ""; return; } + auto& tx = *t; write(tx); + } +}; + +} // end namespace 'chpldef' + +#endif diff --git a/tools/chpldef/Logger.cpp b/tools/chpldef/Logger.cpp index eeea926a562e..8c9e84f3b17b 100644 --- a/tools/chpldef/Logger.cpp +++ b/tools/chpldef/Logger.cpp @@ -69,6 +69,7 @@ Logger& Logger::operator=(Logger&& other) noexcept { filePath_ = other.filePath_; flushImmediately_ = other.flushImmediately_; level_ = other.level_; + header_ = other.header_; stream_.swap(other.stream_); if (other.stream_.is_open()) other.stream_.close(); return *this; @@ -127,14 +128,10 @@ void Logger::flush() { void Logger::logit(const char* msg) { if (!isLogging()) return; - switch (output_) { - case STDERR: std::cerr << msg; break; - case STDOUT: std::cout << msg; break; - case FILEPATH: stream_ << msg; break; - } - if (flushImmediately_) { - flush(); - } + auto& out = stream(); + if (!header_.empty()) out << header_ << " "; + out << msg; + if (flushImmediately_) flush(); } void Logger::logit(std::string msg) { diff --git a/tools/chpldef/Logger.h b/tools/chpldef/Logger.h index 4732fffe26b3..3d02b3de96ce 100644 --- a/tools/chpldef/Logger.h +++ b/tools/chpldef/Logger.h @@ -21,11 +21,9 @@ #ifndef CHPL_TOOLS_CHPLDEF_LOGGER_H #define CHPL_TOOLS_CHPLDEF_LOGGER_H -#include "./misc.h" - +#include "misc.h" #include "llvm/ADT/Optional.h" #include "llvm/Support/JSON.h" - #include #include #include @@ -47,6 +45,7 @@ class Logger { std::ofstream stream_; bool flushImmediately_ = false; Level level_ = MESSAGES; + std::string header_; Logger(Output output, std::string filePath); @@ -70,6 +69,15 @@ class Logger { inline void setLevel(Level level) { this->level_ = level; } void setFlushImmediately(bool flushImmediately); inline bool flushImmediately() const { return flushImmediately_; } + void setHeader(std::string header) { header_ = std::move(header); } + inline const std::string& header() const { return header_; } + + /** Get a mutable handle to the underlying output stream. */ + inline auto& stream() { + return output_ == STDERR ? std::cerr : output_ == STDOUT ? std::cout + : stream_; + } + /** Returns 'true' if the log was started. */ bool start(); void stop(); diff --git a/tools/chpldef/Makefile.include b/tools/chpldef/Makefile.include index 6f1fb120001c..be26645bafa0 100644 --- a/tools/chpldef/Makefile.include +++ b/tools/chpldef/Makefile.include @@ -19,15 +19,17 @@ DYNO_CHPLDEF_OBJDIR = $(COMPILER_BUILD)/chpldef DYNO_CHPLDEF_SRCS = \ - chpldef.cpp \ - command-line-flags.cpp \ - Logger.cpp \ - Message.cpp \ - message-handle.cpp \ - misc.cpp \ - protocol-types.cpp \ - Server.cpp \ - Transport.cpp + chpldef.cpp \ + command-line-flags.cpp \ + compute-goto-declaration.cpp \ + compute-lifecycle.cpp \ + compute-synchronization.cpp \ + Logger.cpp \ + Message.cpp \ + misc.cpp \ + protocol-types.cpp \ + Server.cpp \ + Transport.cpp SRCS = $(DYNO_CHPLDEF_SRCS) diff --git a/tools/chpldef/Message.cpp b/tools/chpldef/Message.cpp index 24fe75a44eab..eefaf2076210 100644 --- a/tools/chpldef/Message.cpp +++ b/tools/chpldef/Message.cpp @@ -18,7 +18,8 @@ * limitations under the License. */ -#include "./Message.h" +#include "Message.h" +#include "Server.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/JSON.h" #include @@ -26,16 +27,43 @@ namespace chpldef { +/** Forward declare specializations so that they are instantiated. */ +#define CHPLDEF_MESSAGE(name__, x1__, x2__, x3__) \ + template chpl::owned name__::create(JsonValue id, Params p); \ + template chpl::owned \ + name__::createFromJson(JsonValue id, JsonValue j); \ + template opt name__::pack() const; \ + template void name__::handle(Server* ctx); \ + template void name__::handle(Server* ctx, Response* r); \ + template void name__::handle(Server* ctx, Result r); \ + template<> name__::ComputeResult \ + name__::compute(Server* ctx, ComputeParams p); \ + template name__::~name__(); +#include "message-macro-list.h" +#undef CHPLDEF_MESSAGE + bool Message::isIdValid(const JsonValue& id) { return id.kind() == JsonValue::Number || id.kind() == JsonValue::String || id.kind() == JsonValue::Null; } +Message::Behavior Message::behavior(Tag tag) { + switch (tag) { + #define CHPLDEF_MESSAGE(name__, outbound__, notify__, rpc__) \ + case MessageTag::name__: \ + return determineBehavior(outbound__, notify__); + #include "message-macro-list.h" + #undef CHPLDEF_MESSAGE + default: break; + } + return Message::NO_BEHAVIOR; +} + // TODO: Need to build an error message or record what went wrong. Create // an 'Invalid' subclass and use that to record any error that occurs past // the point of parsing the message ID. -chpl::owned Message::request(Server* ctx, const JsonValue& j) { +chpl::owned Message::create(Server* ctx, JsonValue j) { auto objPtr = j.getAsObject(); if (!objPtr) { ctx->verbose("Failed to unpack JSON object from %s\n", @@ -44,7 +72,7 @@ chpl::owned Message::request(Server* ctx, const JsonValue& j) { } // Determine what method is to be called. - Message::Tag tag = Message::UNSET; + auto tag = MessageTag::UNSET; if (auto optMethod = objPtr->getString("method")) { auto str = optMethod->str(); @@ -52,15 +80,14 @@ chpl::owned Message::request(Server* ctx, const JsonValue& j) { tag = Message::jsonRpcMethodNameToTag(str); // TODO: We can create an 'Invalid' request to represent this failure. - const bool hasTag = tag != Message::UNSET && tag != Message::INVALID; - + bool hasTag = tag != MessageTag::UNSET && tag != MessageTag::INVALID; if (!hasTag) { CHPLDEF_FATAL(ctx, "Unrecognized method '%s'\n", str.c_str()); return nullptr; } } - CHPL_ASSERT(tag != Message::RESPONSE); + if (tag == MessageTag::RESPONSE) CHPLDEF_TODO(); ctx->verbose("Constructing message with tag '%s'\n", tagToString(tag)); @@ -83,21 +110,23 @@ chpl::owned Message::request(Server* ctx, const JsonValue& j) { } // Before building a specific request, get the params. - const JsonValue params(nullptr); - const JsonValue* p = ¶ms; - if (auto ptr = objPtr->get("params")) { - auto k = ptr->kind(); + JsonValue params(nullptr); + if (auto fieldPtr = objPtr->get("params")) { + auto& field = *fieldPtr; + auto k = field.kind(); if (k != JsonValue::Array && k != JsonValue::Object) { CHPLDEF_TODO(); return nullptr; } else { - p = ptr; + std::swap(field, params); } } switch (tag) { #define CHPLDEF_MESSAGE(name__, x1__, x2__, x3__) \ - case name__: return name__::create(std::move(id), *p); + case MessageTag::name__: { \ + return name__::createFromJson(std::move(id), std::move(params)); \ + } break; #include "./message-macro-list.h" #undef CHPLDEF_MESSAGE default: break; @@ -108,66 +137,86 @@ chpl::owned Message::request(Server* ctx, const JsonValue& j) { return nullptr; } -// TODO: Is outbound format the same as for incoming requests? -template -JsonValue Request::pack() const { - return p.toJson(); +template +static opt packIncomingRequest(const M* msg) { + if (auto r = msg->result()) { + if (msg->hasError()) { + Response rsp(msg->id(), msg->error(), msg->note(), nullptr); + return rsp.pack(); + } else { + auto data = r->toJson(); + Response rsp(msg->id(), Message::OK, std::string(), std::move(data)); + return rsp.pack(); + } + } + return {}; } -template -static Response createSuccessfulResponse(const T* t) { - CHPL_ASSERT(t && t->isCompleted() && !t->hasError()); - auto r = t->result(); - CHPL_ASSERT(r); - auto data = r->toJson(); - auto ret = Response::create(t->id(), std::move(data)); - return ret; +template +static opt packIncomingNotify(const M* msg) { + return {}; } -opt Message::response(Server* ctx, const Message* msg) { - opt ret; +template +static opt packOutboundRequest(const M* msg) { + JsonObject obj { + { "jsonrpc", "2.0" }, + { "id", msg->id() }, + }; - if (msg->tag() == Message::UNSET || msg->tag() == Message::INVALID || - msg->tag() == Message::RESPONSE) { + if (auto p = msg->params()) { + obj["params"] = p->toJson(); + JsonValue ret(std::move(obj)); return ret; } - switch (msg->status()) { - case COMPLETED: { - CHPL_ASSERT(!msg->hasError()); - switch (msg->tag()) { - #define CHPLDEF_MESSAGE(name__, x1__, x2__, x3__) \ - case name__: return createSuccessfulResponse(msg->to##name__()); - #include "./message-macro-list.h" - #undef CHPLDEF_MESSAGE - default: break; - } - } break; - case FAILED: { - CHPL_ASSERT(msg->hasError()); - JsonValue data(nullptr); - ret = Response::create(msg->id(), msg->error(), msg->note(), - std::move(data)); - } break; + return {}; +} + +template +static opt packOutboundNotify(const M* msg) { + if (auto ret = packOutboundRequest(msg)) { + if (auto obj = ret->getAsObject()) { + obj->erase("id"); + } + return ret; + } + return {}; +} + +template +opt TemplatedMessage::pack() const { + switch (behavior()) { + case INCOMING_REQUEST: return packIncomingRequest(this); + case INCOMING_NOTIFY: return packIncomingNotify(this); + case OUTBOUND_REQUEST: return packOutboundRequest(this); + case OUTBOUND_NOTIFY: return packOutboundNotify(this); default: break; } - return ret; + // All subclasses of 'TemplatedMessage' should have a behavior. + CHPLDEF_IMPOSSIBLE(); + return {}; } -bool Message::isOutbound() const { - if (tag_ == Message::RESPONSE) return true; +bool Message::isOutbound(Tag tag) { + if (tag == MessageTag::RESPONSE) return true; #define CHPLDEF_MESSAGE(name__, outbound__, x2__, x3__) \ - if (tag_ == Message::name__) return outbound__; + if (tag == MessageTag::name__) return outbound__; #include "./message-macro-list.h" #undef CHPLDEF_MESSAGE return false; } +bool Message::isIncoming(Tag tag) { + if (tag == MessageTag::RESPONSE) return true; + return !isOutbound(tag); +} + bool Message::isNotification(Tag tag) { - if (tag == Message::RESPONSE) return true; + if (tag == MessageTag::RESPONSE) return false; #define CHPLDEF_MESSAGE(name__, x1__, notification__, x3__) \ - if (tag == Message::name__) return notification__; + if (tag == MessageTag::name__) return notification__; #include "./message-macro-list.h" #undef CHPLDEF_MESSAGE return false; @@ -180,7 +229,7 @@ const char* Message::jsonRpcMethodName() const { const char* Message::tagToJsonRpcMethodName(Message::Tag tag) { switch (tag) { #define CHPLDEF_MESSAGE(name__, x1__, x2__, rpc__) \ - case name__: return #rpc__; + case MessageTag::name__: return #rpc__; #include "./message-macro-list.h" #undef CHPLDEF_MESSAGE default: break; @@ -192,10 +241,10 @@ const char* Message::tagToJsonRpcMethodName(Message::Tag tag) { // TODO: Can use a trie if this becomes a performance bottleneck. Message::Tag Message::jsonRpcMethodNameToTag(const char* str) { #define CHPLDEF_MESSAGE(name__, x1__, x2__, rpc__) \ - if (!strcmp(#rpc__, str)) return name__; + if (!strcmp(#rpc__, str)) return MessageTag::name__; #include "./message-macro-list.h" #undef CHPLDEF_MESSAGE - return Message::INVALID; + return MessageTag::INVALID; } Message::Tag Message::jsonRpcMethodNameToTag(std::string str) { @@ -205,38 +254,60 @@ Message::Tag Message::jsonRpcMethodNameToTag(std::string str) { std::string Message::idToString() const { if (auto optStr = id().getAsString()) return optStr->str(); if (auto optInt = id().getAsInteger()) return std::to_string(*optInt); - if (id().kind() == JsonValue::Null) return "<>"; + if (id().kind() == JsonValue::Null) return ""; CHPLDEF_IMPOSSIBLE(); return {}; } -template -Message::Error Request::unpack(const JsonValue& j, P& p, - std::string* note) { +template +Message::Error +TemplatedMessage::unpack(JsonValue j, Params& p, std::string& note) { llvm::json::Path::Root root; if (p.fromJson(j, root)) return Message::OK; - *note = "Failed to unpack JSON"; + note = "Failed to unpack JSON for parameters"; return Message::ERR_INVALID_PARAMS; } -/** Define factory functions that unpack and construct the message. */ -#define CHPLDEF_MESSAGE(name__, x1__, x2__, x3__) \ - chpl::owned \ - name__::create(JsonValue id, const JsonValue& j) { \ - name__::Params p; \ - std::string note; \ - auto error = unpack(j, p, ¬e); \ - auto req = new name__(std::move(id), error, std::move(note), \ - std::move(p)); \ - auto ret = chpl::toOwned(req); \ - return ret; \ - } -#include "./message-macro-list.h" -#undef CHPLDEF_MESSAGE +template +Message::Error +TemplatedMessage::unpack(JsonValue j, Result& r, std::string& note) { + llvm::json::Path::Root root; + if (r.fromJson(j, root)) return Message::OK; + note = "Failed to unpack JSON for result"; + return Message::ERR_INVALID_PARAMS; +} + +template +Message::Error +TemplatedMessage::unpack(Response* rsp, Result& r, std::string& note) { + CHPLDEF_TODO(); + return Message::OK; +} + +template +chpl::owned> +TemplatedMessage::create(JsonValue id, Params p) { + auto msg = new TemplatedMessage(K, std::move(id), Message::OK, {}, + std::move(p)); + auto ret = chpl::toOwned<>(msg); + return ret; +} + +template +chpl::owned> +TemplatedMessage::createFromJson(JsonValue id, JsonValue j) { + Params p = {}; + std::string note; + auto error = unpack(std::move(j), p, note); + auto msg = new TemplatedMessage(K, std::move(id), error, + std::move(note), + std::move(p)); + auto ret = chpl::toOwned<>(msg); + return ret; +} static JsonObject -createResponseError(Message::Error e, std::string note, - JsonValue data) { +packResponseError(Message::Error e, std::string note, JsonValue data) { JsonObject ret { { "code", Message::errorToInt(e) }, { "message", std::move(note) } @@ -246,66 +317,72 @@ createResponseError(Message::Error e, std::string note, return ret; } -JsonValue Response::pack() const { - JsonObject ret { +opt Response::pack() const { + JsonObject obj { { "jsonrpc", "2.0" }, { "id", this->id() } }; if (hasError()) { - ret["error"] = createResponseError(error(), note(), data()); + obj["error"] = packResponseError(error(), note(), data()); } else { - ret["result"] = data(); + obj["result"] = data(); } + JsonValue ret(std::move(obj)); return ret; } -Response Response::create(JsonValue id, JsonValue data) { +chpl::owned Response::create(JsonValue id, JsonValue data) { CHPL_ASSERT(isIdValid(id)); - auto ret = Response(std::move(id), Message::OK, std::string(), - std::move(data)); + auto rsp = new Response(std::move(id), Message::OK, std::string(), + std::move(data)); + auto ret = chpl::toOwned(rsp); return ret; } -Response Response::create(JsonValue id, Message::Error error, - std::string note, - JsonValue data) { +chpl::owned Response::create(JsonValue id, Message::Error error, + std::string note, + JsonValue data) { CHPL_ASSERT(isIdValid(id) && error != Message::OK); - auto ret = Response(std::move(id), error, std::move(note), - std::move(data)); + auto rsp = new Response(std::move(id), error, std::move(note), + std::move(data)); + auto ret = chpl::toOwned(rsp); return ret; } const Response* Message::toResponse() const { - if (tag_ == Message::RESPONSE) return static_cast(this); + if (tag_ == MessageTag::RESPONSE) + return static_cast(this); return nullptr; } Response* Message::toResponse() { - if (tag_ == Message::RESPONSE) return static_cast(this); + if (tag_ == MessageTag::RESPONSE) + return static_cast(this); return nullptr; } #define CHPLDEF_MESSAGE(name__, x1__, x2__, x3__) \ - const class name__* Message::to##name__() const { \ - if (tag_ == Message::name__) \ - return static_cast(this); \ + const name__* Message::to##name__() const { \ + if (tag_ == MessageTag::name__) \ + return static_cast(this); \ return nullptr; \ } \ - class name__* Message::to##name__() { \ - if (tag_ == Message::name__) return static_cast(this); \ + name__* Message::to##name__() { \ + if (tag_ == MessageTag::name__) \ + return static_cast(this); \ return nullptr; \ } #include "./message-macro-list.h" #undef CHPLDEF_MESSAGE const char* Message::tagToString(Tag tag) { - if (tag == Message::UNSET) return "Unset"; - if (tag == Message::INVALID) return "Invalid"; - if (tag == Message::RESPONSE) return "Response"; + if (tag == MessageTag::UNSET) return "Unset"; + if (tag == MessageTag::INVALID) return "Invalid"; + if (tag == MessageTag::RESPONSE) return "Response"; #define CHPLDEF_MESSAGE(name__, x1__, x2__, x3__) \ - if (tag == name__) return #name__; + if (tag == MessageTag::name__) return #name__; #include "./message-macro-list.h" #undef CHPLDEF_MESSAGE return nullptr; @@ -331,86 +408,137 @@ const char* Message::errorToString(Error error) { return nullptr; } -template -static bool -doHandleNotification(Server* ctx, M* msg, const P& p, CR& cr) { - if (msg->status() == Message::PROGRESSING) CHPLDEF_TODO(); - if (msg->status() != Message::PENDING) return false; - - ctx->message("Handling notification '%s'\n", msg->tagToString()); - - cr = M::compute(ctx, p); - - if (cr.isProgressingCallAgain) CHPLDEF_TODO(); - - ctx->message("Notification complete...\n"); +const char* Message::behaviorToString(Message::Behavior b) { + switch (b) { + case Message::INCOMING_REQUEST: return "incoming request"; + case Message::INCOMING_NOTIFY: return "incoming notification"; + case Message::OUTBOUND_REQUEST: return "outbound request"; + case Message::OUTBOUND_NOTIFY: return "outbound notification"; + case Message::NO_BEHAVIOR: return "none"; + } - return true; + return nullptr; } -template -static bool -doHandleRequest(Server* ctx, M* msg, const P& p, CR& cr) { - if (msg->status() == Message::PROGRESSING) CHPLDEF_TODO(); - if (msg->status() != Message::PENDING) return false; - - ctx->message("Handling request '%s' with ID %s\n", - msg->tagToString(), - msg->idToString().c_str()); - - cr = M::compute(ctx, p); +// Use this pattern so that we may implement the TemplatedMessage class with +// an unbounded number of implementation methods that are not documented +// in the header. Because the handler is marked 'friend', it can access the +// message state. +template +class TemplatedMessageHandler { +public: + static constexpr auto BEHAVIOR = M::BEHAVIOR; + using Params = typename M::Params; + using Result = typename M::Result; + using ComputeResult = typename M::ComputeResult; +private: + Server* ctx_ = nullptr; + M* msg_ = nullptr; +public: + TemplatedMessageHandler(Server* ctx, M* msg) : ctx_(ctx), msg_(msg) {} + TemplatedMessageHandler() = default; + + inline std::string fmt() { return ctx_->fmt(static_cast(msg_)); } + inline const char* dsc() const { + constexpr bool hasBehavior = BEHAVIOR != Message::NO_BEHAVIOR; + auto ret = hasBehavior ? Message::behaviorToString(BEHAVIOR) : "message"; + return ret; + } - if (cr.isProgressingCallAgain) CHPLDEF_TODO(); + void logComputationPrelude() { + ctx_->message("Handling %s '%s'\n", dsc(), fmt().c_str()); + } - if (cr.error != Message::OK) { - auto cstr = Message::errorToString(cr.error); - ctx->message("Request failed with code '%s'\n", cstr); - return false; + void logComputationEpilogue(const ComputeResult& cr) { + if (cr.error != Message::OK) { + ctx_->message("The %s %s failed with code '%s'\n", dsc(), fmt().c_str(), + Message::errorToString(cr.error)); + } else { + ctx_->message("The %s '%s' is complete...\n", dsc(), fmt().c_str()); + } } - ctx->message("Request '%s' complete...\n", msg->idToString().c_str()); + template + void issueComputationCall() {} + + template <> + void issueComputationCall() { + logComputationPrelude(); + auto cr = M::compute(ctx_, msg_->p); + logComputationEpilogue(cr); + if (cr.isProgressingCallAgain) CHPLDEF_TODO(); + if (cr.error != Message::OK) { + msg_->markFailed(cr.error, std::move(cr.note)); + } else { + msg_->r = std::move(cr.result); + msg_->markCompleted(); + } + } - return true; -} + template <> + void issueComputationCall() { + issueComputationCall(); + } -template -static bool -doHandleMessage(Server* ctx, M* msg, const P& p, R& r) { - if (msg->isNotification()) return doHandleNotification(ctx, msg, p, r); - return doHandleRequest(ctx, msg, p, r); -} + template + void issueComputationCall(Result r) {} + + template <> + void issueComputationCall(Result r) { + logComputationPrelude(); + msg_->r = std::move(r); + auto cr = M::compute(ctx_, msg_->r); + logComputationEpilogue(cr); + if (cr.isProgressingCallAgain) CHPLDEF_TODO(); + if (cr.error != Message::OK) { + msg_->markFailed(cr.error, std::move(cr.note)); + } else { + msg_->markCompleted(); + } + } -/** Call the request handler above and then mark completed or failed. */ -#define CHPLDEF_MESSAGE(name__, x1__, x2__, x3__) \ - void name__::handle(Server* ctx) { \ - ComputedResult r; \ - if (doHandleMessage(ctx, this, p, r)) { \ - this->r = std::move(r.result); \ - this->markCompleted(); \ - } else { \ - this->markFailed(r.error, std::move(r.note)); \ - } \ + void handle() { + if (msg_->isOutbound() || msg_->isDone()) return; + if (msg_->status() == Message::PROGRESSING) CHPLDEF_TODO(); + issueComputationCall(); } -#include "./message-macro-list.h" -#undef CHPLDEF_MESSAGE -opt Message::handle(Server* ctx, Message* msg) { - if (!ctx || !msg || msg->isResponse()) return {}; - if (msg->status() != Message::PENDING) return {}; + void handle(Response* rsp) { + if (!rsp || rsp->id() != msg_->id()) return; + if (rsp->status() == Message::FAILED) CHPLDEF_TODO(); + std::string note; + Result r; + auto error = M::unpack(rsp, r, note); + if (error != Message::OK) { + msg_->markFailed(error, std::move(note)); + } else { + handle(std::move(r)); + } + } - msg->handle(ctx); + void handle(Result r) { + if (msg_->isOutbound() || msg_->isDone()) return; + if (msg_->status() == Message::PROGRESSING) CHPLDEF_TODO(); + issueComputationCall(std::move(r)); + } +}; - CHPL_ASSERT(msg->status() != Message::PENDING); +template +void TemplatedMessage::handle(Server* ctx) { + TemplatedMessageHandler> tmh(ctx, this); + tmh.handle(); +} - if (!msg->isNotification() && msg->status() != Message::PROGRESSING) { - if (auto ret = Message::response(ctx, msg)) { - CHPL_ASSERT(ret->id() == msg->id()); - CHPL_ASSERT(ret->status() == Message::COMPLETED); - return ret; - } - } +template +void TemplatedMessage::handle(Server* ctx, Response* rsp) { + TemplatedMessageHandler> tmh(ctx, this); + tmh.handle(rsp); +} - return {}; +template +void TemplatedMessage::handle(Server* ctx, Result r) { + TemplatedMessageHandler> tmh(ctx, this); + tmh.handle(std::move(r)); } } // end namespace 'chpldef' diff --git a/tools/chpldef/Message.h b/tools/chpldef/Message.h index e0f73ef20d9c..45a2a89323a9 100644 --- a/tools/chpldef/Message.h +++ b/tools/chpldef/Message.h @@ -21,34 +21,40 @@ #ifndef CHPL_TOOLS_CHPLDEF_MESSAGE_H #define CHPL_TOOLS_CHPLDEF_MESSAGE_H -#include "./misc.h" -#include "./protocol-types.h" -#include "./Server.h" - +#include "misc.h" +#include "protocol-types.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/JSON.h" - #include #include #include namespace chpldef { -/** Forward declare some message subclasses. */ -class BaseRequest; -template class Request; class Response; +class Server; +class Message; + +/** These tags are used to do dynamic casts to message types at runtime, + and to parameterize `TemplatedMessage` instances at compile-time. */ +enum class MessageTag { + UNSET = 0, + INVALID = 1, + RESPONSE = 2, + // Expand the message macros. + #define CHPLDEF_MESSAGE(name__, x1__, x2__, x3__) name__ , + #include "./message-macro-list.h" + #undef CHPLDEF_MESSAGE + NUM_MESSAGES +}; -/** Forward declare requests. */ -#define CHPLDEF_MESSAGE(name__, x1__, x2__, x3__) class name__; -#include "./message-macro-list.h" -#undef CHPLDEF_MESSAGE +template class TemplatedMessage; /** Attempts to model a LSP message. A message may be either incoming or - outgoing (most are incoming). -*/ + outgoing (most are incoming). */ class Message { public: + using Tag = MessageTag; /** Error codes are listed in order according to the LSP spec. */ enum Error { @@ -73,21 +79,17 @@ class Message { FAILED /** Handling of this message failed. */ }; - enum Tag { - UNSET = 0, - INVALID = 1, - RESPONSE = 2, - - // Expand the message macros. - #define CHPLDEF_MESSAGE(name__, x1__, x2__, x3__) name__ , - #include "./message-macro-list.h" - #undef CHPLDEF_MESSAGE - NUM_MESSAGES_INNER, - NUM_MESSAGES = (NUM_MESSAGES_INNER - 1) + /** Behavior is a combination of message direction and return value. */ + enum Behavior { + NO_BEHAVIOR, + INCOMING_REQUEST, + INCOMING_NOTIFY, + OUTBOUND_REQUEST, + OUTBOUND_NOTIFY }; private: - Tag tag_ = Message::UNSET; + Tag tag_ = MessageTag::UNSET; JsonValue id_ = nullptr; Error error_ = Message::OK; Status status_ = Message::PENDING; @@ -102,6 +104,7 @@ class Message { note_(std::move(note)) { CHPL_ASSERT(isIdValid(id_)); if (id.kind() == JsonValue::Null) CHPL_ASSERT(isNotification()); + if (error != Message::OK) status_ = Message::FAILED; } inline void markProgressing() { status_ = Message::PROGRESSING; } @@ -118,11 +121,7 @@ class Message { virtual ~Message() = default; /** Create a request given a JSON value. */ - static chpl::owned request(Server* ctx, const JsonValue& j); - - /** Create a response to a handled message. The status of the message - must be COMPLETED or FAILED, or else nothing is returned. */ - static opt response(Server* ctx, const Message* msg); + static chpl::owned create(Server* ctx, JsonValue j); /** The tag for this message. */ inline Tag tag() const { return tag_; } @@ -162,11 +161,26 @@ class Message { /** Returns 'true' if this message is marked 'COMPLETED'. */ inline bool isCompleted() const { return status_ == Message::COMPLETED; } + /** Returns 'true' if this message is marked 'FAILED'. */ + inline bool isFailed() const { return status_ == Message::FAILED; } + + /** Returns 'true' if this message is marked 'COMPLETED' or 'FAILED'. */ + inline bool isDone() const { return isCompleted() || isFailed(); } + /** If 'true', this message is a response. */ - inline bool isResponse() const { return tag_ == Message::RESPONSE; } + inline bool isResponse() const { return tag_ == MessageTag::RESPONSE; } + + /** If 'true', then 'tag' is for a message being sent to the server. */ + static bool isOutbound(Tag tag); /** If 'true', the server is sending this message to the client. */ - bool isOutbound() const; + inline bool isOutbound() const { return isOutbound(tag_); } + + /** If 'true', then 'tag' is for a message being sent to the server. */ + static bool isIncoming(Tag tag); + + /** If 'true', the client is sending this message to the server. */ + inline bool isIncoming() const { return isIncoming(tag_); } /** If 'true', then the Tag 'tag' does not need a response. */ static bool isNotification(Tag tag); @@ -174,6 +188,15 @@ class Message { /** If 'true', then this message does not need a response. */ inline bool isNotification() const { return isNotification(tag_); } + /** Return the behavior of a message tag. */ + static Behavior behavior(Tag tag); + + /** Return the behavior of this message. */ + inline Behavior behavior() const { return behavior(tag_); } + + /** Print a behavior value as a string. */ + static const char* behaviorToString(Behavior b); + /** Returns the expected JSON-RPC method name for this message. */ const char* jsonRpcMethodName() const; @@ -200,8 +223,8 @@ class Message { /** Dynamic cast to other message subclasses, const and non-const. */ #define CHPLDEF_MESSAGE(name__, x1__, x2__, x3__) \ - const class name__* to##name__() const; \ - class name__* to##name__(); + const TemplatedMessage* to##name__() const; \ + TemplatedMessage* to##name__(); #include "./message-macro-list.h" #undef CHPLDEF_MESSAGE @@ -221,7 +244,9 @@ class Message { /** Default visitor body is trivial enough that we define it here. */ #define CHPLDEF_MESSAGE(name__, x1__, x2__, x3__) \ - virtual T visit(const class name__* req) { T ret; return ret; } + virtual T visit(const TemplatedMessage* req) { \ + T ret; return ret; \ + } #include "./message-macro-list.h" #undef CHPLDEF_MESSAGE @@ -232,6 +257,14 @@ class Message { Server* ctx() const; }; + /** Determine a message's behavior. */ + static inline constexpr Message::Behavior + determineBehavior(bool outbound, bool notify) { + return outbound + ? (notify ? Message::OUTBOUND_NOTIFY : Message::OUTBOUND_REQUEST) + : (notify ? Message::INCOMING_NOTIFY : Message::INCOMING_REQUEST); + } + /** Dispatch a visitor using a message as the receiver. */ template inline T accept(Message::Visitor& v) { return v.dispatch(*this); } @@ -240,188 +273,278 @@ class Message { virtual bool defer() const { return false; } /** Pack this message into a JSON value. */ - virtual JsonValue pack() const = 0; + virtual opt pack() const = 0; - /** May handle a message, but does not send or receive. */ - static opt handle(Server* ctx, Message* msg); - - /** Compute results and store them in this request for later use. */ + /** For incoming requests, compute a result using the input parameters. */ virtual void handle(Server* ctx) = 0; + + /** For outbound requests, compute using a response sent by the client. */ + virtual void handle(Server* ctx, Response* rsp) = 0; }; -class BaseRequest : public Message { -protected: - BaseRequest(Message::Tag tag, JsonValue id, Message::Error error, - std::string note) - : Message(tag, std::move(id), error, std::move(note)) {} -public: - virtual JsonValue pack() const override = 0; - virtual void handle(Server* ctx) override = 0; +namespace detail { + +/** Stores computation results for a message. */ +template +struct ComputationOutput { + bool isProgressingCallAgain = false; + Message::Error error = Message::OK; + std::string note; + R result; + + /** For convenience when implementing 'compute' for a request. */ + ComputationOutput(R&& result) + : isProgressingCallAgain(false), + error(Message::OK), + result(result) {} + + ComputationOutput(bool isProgressingCallAgain, Message::Error error, + std::string note, + R result) + : isProgressingCallAgain(isProgressingCallAgain), + error(error), + note(std::move(note)), + result(std::move(result)) {} + + ComputationOutput() = default; }; -/** Most messages are incoming and are modelled as a 'Request'. A request - consists of a set of parameter values, that can be used to compute - a result (it is analogous to a function call). Some requests can be - sent by the server to the client, in which case they are 'sendable', - and should provide a definition for the 'pack()' method. -*/ -template -class Request : public BaseRequest { -public: +/** Message specializations return this in absence of another value. */ +using Empty = EmptyProtocolType; + +template +struct Computation {}; + +/** Computation details for incoming requests. */ +template +struct Computation { + static constexpr auto BEHAVIOR = Message::INCOMING_REQUEST; + using Params = P; + using Result = R; + using FunctionParams = const Params&; + using FunctionResult = ComputationOutput; +}; - /** Stores computation results for this request. */ - struct ComputedResult { - bool isProgressingCallAgain = false; - Message::Error error = Message::OK; - std::string note; - Result result; +/** Computation details for incoming notifications. */ +template +struct Computation { + static constexpr auto BEHAVIOR = Message::INCOMING_NOTIFY; + using Params = P; + using Result = Empty; + using FunctionParams = const Params&; + using FunctionResult = ComputationOutput; +}; + +/** Computation details for outbound requests. */ +template +struct Computation { + static constexpr auto BEHAVIOR = Message::OUTBOUND_REQUEST; + using Params = P; + using Result = R; + using FunctionParams = const Result&; + using FunctionResult = ComputationOutput; +}; + +/** Details for outbound notifications. Most fields are an empty struct + because outbound notifications should never be computed (they are + only ever created then sent off to the client. Any validation must be + done by the entity that created one). */ +template +struct Computation { + static constexpr auto BEHAVIOR = Message::OUTBOUND_NOTIFY; + using Params = P; + using Result = Empty; + using FunctionParams = Empty; + using FunctionResult = ComputationOutput; +}; - /** For convenience when implementing 'compute' for a request. */ - ComputedResult(Result&& r) - : isProgressingCallAgain(false), - error(Message::OK), - result(r) {} +template struct ComputationByTag {}; - ComputedResult() = default; +/** Specialize computation details for each tag. */ +#define CHPLDEF_MESSAGE(name__, outbound__, notify__, x3__) \ + template <> struct ComputationByTag { \ + static constexpr \ + auto B = Message::determineBehavior(outbound__, notify__); \ + using type = Computation; \ }; +#include "message-macro-list.h" +#undef CHPLDEF_MESSAGE + +} // end namespace 'detail' + +template +class TemplatedMessageHandler; +template +class TemplatedMessage : public Message { +public: + using Computation = typename detail::ComputationByTag::type; + static constexpr auto BEHAVIOR = Computation::BEHAVIOR; + using Params = typename Computation::Params; + using Result = typename Computation::Result; + using ComputeParams = typename Computation::FunctionParams; + using ComputeResult = typename Computation::FunctionResult; + using Self = TemplatedMessage; protected: + friend TemplatedMessageHandler>; + const Params p; Result r = {}; - Request(Message::Tag tag, JsonValue id, Message::Error error, - std::string note, - Params params) - : BaseRequest(tag, std::move(id), error, std::move(note)), - p(std::move(params)) {} + TemplatedMessage(Message::Tag tag, JsonValue id, Message::Error error, + std::string note, + Params p) + : Message(tag, std::move(id), error, std::move(note)), + p(std::move(p)) {} - static Message::Error unpack(const JsonValue& j, Params& p, - std::string* note); + static Message::Error unpack(JsonValue j, Params& p, std::string& note); + static Message::Error unpack(JsonValue j, Result& r, std::string& note); + static Message::Error unpack(Response* rsp, Result& r, std::string& note); /** Use in message handlers to return failure. */ - static ComputedResult fail(Error error=Message::ERR_REQUEST_FAILED, - std::string note=std::string()) { + static ComputeResult fail(Error error=Message::ERR_REQUEST_FAILED, + std::string note=std::string()) { return { false, error, std::move(note), {} }; } /** Use in message handlers to delay. */ - static ComputedResult delay() { + static ComputeResult delay() { return { true, Message::OK, {}, {} }; } public: - virtual ~Request() = default; + virtual ~TemplatedMessage() = default; + + /** Create a message using a JSON id and parameters. */ + static chpl::owned create(JsonValue id, Params p); + + /** Create a message using a JSON id, error code and optional note. */ + static chpl::owned createFromJson(JsonValue id, JsonValue j); + + /** Pack this request into a JSON value. The contents of the JSON value + depend on the behavior of this message. There are four modes: + a message can either be a notification or a request, and it can either + be incoming (client-to-server) or outbound (server-to-client). + + A message should only be packed if it is marked 'COMPLETE'. Otherwise, + the contents of the JSON may not be valid. + + For incoming requests, the request result is implicitly wrapped in a + message of type 'Response', and then that Response is packed into + JSON. This is to conform with the LSP protocol (it is as if we are + sending the return value of a function call to the client). - /** Pack the _parameters_ of this request into a JSON value. To pack - the result of a request into a JSON value, either do so manually - or create a Response. This is method is used for sending outbound - requests from server to client. */ - virtual JsonValue pack() const override; + For outgoing requests and notifications, the message parameters are + packed directly into JSON. - /** Compute results and save for later use. */ - virtual void handle(Server* ctx) override = 0; + For incoming notifications, a JSON 'null' value is returned, as there + is nothing to pack. + */ + virtual opt pack() const final; - /** Get the parameters of this request if they were deserialized. */ - const Params* params() const; + /** For incoming messages, compute a result using the input parameters. */ + virtual void handle(Server* ctx) final; - /** If computed, get the result of this request. */ + /** For outbound messages, compute using a response sent by the client. */ + virtual void handle(Server* ctx, Response* r) final; + + /** For outbound messages, compute using results sent by the client. */ + void handle(Server* ctx, Result r); + + /** Get the parameters of this message if they are valid. */ + inline const Params* params() const { + if (status() == Message::FAILED) return nullptr; + return &this->p; + } + + /** Get the result of this message if it was computed. */ inline const Result* result() const { if (status() != Message::COMPLETED) return nullptr; return &this->r; } -}; -/** Expand each LSP message into its own subclass of Request. Each message - 'Foo' has a parameter type and return type, 'FooParams' and 'FooResult', - which can be found in the 'protocol-types.h' header. These names are - internally consistent but may not always match the spec - a small set of - protocol types defined in the LSP spec do not always match this pattern. + /** This function performs the actual computation. */ + static ComputeResult compute(Server* ctx, ComputeParams p); +}; - When in doubt and referring to the spec, use the "JSON-RPC method name" - to look up the message, as that is unambiguous. -*/ -#define CHPLDEF_MESSAGE(name__, outbound__, notification__, rpc__) \ - class name__ : public Request { \ - public: \ - using Params = name__##Params; \ - using Result = name__##Result; \ - using ComputedResult = Request::ComputedResult; \ - private: \ - name__(JsonValue id, Message::Error error, std::string note, \ - Params p) \ - : Request(Message::name__, std::move(id), error, \ - std::move(note), \ - std::move(p)) { \ - static_assert(std::is_base_of::value, \ - "Must be derived from 'ProtocolType'"); \ - static_assert(std::is_base_of::value, \ - "Must be derived from 'ProtocolType'"); \ - } \ - public: \ - virtual ~name__() = default; \ - static chpl::owned \ - create(JsonValue id, const JsonValue& j); \ - virtual void handle(Server* ctx) override; \ - static ComputedResult compute(Server* ctx, const Params& p); \ - }; -#include "./message-macro-list.h" +/** Create aliases for each specialization over a message tag. */ +#define CHPLDEF_MESSAGE(name__, x1__, x2__, x3__) \ + template <> class TemplatedMessage; \ + using name__ = TemplatedMessage; +#include "message-macro-list.h" #undef CHPLDEF_MESSAGE -/** One response is created for every request message. The structure of a - response is simple and rigid when compared to inbound (client-to-server) - requests. Because there is a 1-to-1 relationship between messages and - responses, a message/response pair must share IDs. Responses are always - outbound and cannot be handled by the server (there is no work to do - other than send the response). +/** Deliberately specialize these tags to be empty. */ +template <> +class TemplatedMessage {}; +template <> +class TemplatedMessage {}; +template <> +class TemplatedMessage {}; + +/** The structure of a response is simple and rigid. When a incoming request + is packed into JSON, it is implicitly wrapped in a response. When the + server sends the client an outbound request, it registers the request + and awaits a response. */ class Response : public Message { private: JsonValue data_; +public: Response(JsonValue id, Message::Error error, std::string note, JsonValue data) - : Message(Message::RESPONSE, std::move(id), error, std::move(note)), + : Message(MessageTag::RESPONSE, std::move(id), error, std::move(note)), data_(data) { + CHPL_ASSERT(this->id().kind() != JsonValue::Null); CHPL_ASSERT(isResponse() && isOutbound()); this->markCompleted(); } -public: + virtual ~Response() = default; + /** Create a response given an ID and a result value. */ - static Response create(JsonValue id, JsonValue data=nullptr); + static chpl::owned create(JsonValue id, JsonValue data=nullptr); /** Create a response given an ID, an error code, and optional details. If 'data' is 'nullptr' then it will not be included in the JSON created when this message is packed. Notes will be included even if they are the empty string, as this is required by the protocol. */ - static Response create(JsonValue id, Message::Error error, - std::string note=std::string(), - JsonValue data=nullptr); + static chpl::owned create(JsonValue id, Message::Error error, + std::string note=std::string(), + JsonValue data=nullptr); - virtual ~Response() = default; + /** Create a response by unpacking it from JSON. */ + static chpl::owned create(JsonValue j); /** Get the JSON data this response stores. */ inline const JsonValue& data() const { return data_; } - /** Pack this response into a JSON value. */ - virtual JsonValue pack() const override; + /** Pack this response into a JSON value. Messages have a rigid format, + so the layout of the JSON sould always be the same sans omission + of optional fields (see the LSP spec section on 'Response' messages). */ + virtual opt pack() const override; /** Handling a response does nothing. */ virtual void handle(Server* ctx) override {} + + /** Handling a response does nothing. */ + virtual void handle(Server* ctx, Response* rsp) override {} }; /** Define visitor dispatch now that all subclasses have been defined. */ template T Message::Visitor::dispatch(const Message* req) { switch (req->tag()) { - case RESPONSE: { + case MessageTag::RESPONSE: { return visit(req->toResponse()); } break; #define CHPLDEF_MESSAGE(name__, x1__, x2__, x3__) \ - case name__: { \ - auto casted = static_cast(req); \ - return visit(casted); \ + case MessageTag::name__: { \ + using F##name__ = TemplatedMessage; \ + auto c = static_cast(req); \ + return visit(c); \ } break; #include "message-macro-list.h" #undef CHPLDEF_MESSAGE diff --git a/tools/chpldef/Server.cpp b/tools/chpldef/Server.cpp index 61503f7bf299..97ce580d5522 100644 --- a/tools/chpldef/Server.cpp +++ b/tools/chpldef/Server.cpp @@ -18,18 +18,17 @@ * limitations under the License. */ -#include "./Server.h" - +#include "Server.h" +#include "Message.h" +#include "events.h" +#include "Transport.h" #include "chpl/framework/Context.h" #include "chpl/framework/UniqueString.h" -#include "chpl/framework/query-impl.h" #include "chpl/util/filesystem.h" #include "chpl/util/printf.h" - #include "llvm/ADT/Optional.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/JSON.h" - #include #include @@ -44,7 +43,11 @@ namespace chpldef { -Server::Server() {} +void Server::ErrorHandler::report(chpl::Context* chapel, + const chpl::ErrorBase* err) { + auto p = std::make_pair(err->clone(), ctx_->revision()); + errors_.push_back(std::move(p)); +} void Server::setLogger(Logger&& logger) { this->logger_ = std::move(logger); @@ -67,4 +70,196 @@ void Server::trace(const char* fmt, ...) { VPRINTF_FORWARD_(fmt, logger_.vtrace); } +bool Server::Event::canRun(Server* ctx) const { + return ctx->state() == Server::READY; +} + +Server::Server(Server::Configuration config) + : chapel_(createCompilerContext(config)), + config_(std::move(config)) { + + // Install the server error handler. + auto handler = toOwned(new ErrorHandler(this)); + errorHandler_ = handler.get(); + chapel_.installErrorHandler(std::move(handler)); + + // Open the server log. + Logger logger; + if (!config_.logFile.empty()) { + logger = Logger::createForFile(config_.logFile); + if (!logger.isLogging()) { + std::cerr << "Failed to open log file!" << std::endl; + logger = Logger(); + std::cerr << "Using '" << logger.filePath() << "'" << std::endl; + } + } + + // Configure and install the logger. + logger.setHeader("[chpldef]"); + logger.setLevel(config_.logLevel); + this->setLogger(std::move(logger)); + + // Set a quick alias for the transport. + this->transport_ = config_.transport.get(); + + doRegisterEssentialEvents(); +} + +chpl::Context +Server::createCompilerContext(const Server::Configuration& config) const { + chpl::Context::Configuration chplConfig; + chplConfig.chplHome = config.chplHome; + return chpl::Context(std::move(chplConfig)); +} + +bool Server::shouldGarbageCollect() const { + auto f = config_.garbageCollectionFrequency; + if (f == 1) return true; + if (f == 0) return false; + return (revision_ % f) == 0; +} + +bool Server::shouldPrepareToGarbageCollect() const { + auto f = config_.garbageCollectionFrequency; + if (f == 0) return false; + return (revision_ % f) == (f - 1); +} + +void Server::doRegisterEssentialEvents() { + using namespace events; + registerEvent(chpl::toOwned(new ReadMessage())); + registerEvent(chpl::toOwned(new ResolveModules())); + registerEvent(chpl::toOwned(new PreparePublishDiagnostics())); +} + +void Server::doRunEvents(Event::When when, const Message* msg) { + for (auto& e : events_) { + CHPL_ASSERT(e.get()); + const bool run = (e->mask() & when) || (e->mask() & Event::ALWAYS); + if (!run || !e->canRun(this)) continue; + this->trace("Running event '%s'\n", e->name()); + e->run(this, msg, when); + } +} + +chpl::owned Server::dequeueOneMessage() { + if (messages_.empty()) return nullptr; + auto ret = std::move(messages_.front()); + messages_.pop(); + return ret; +} + +void Server::sendMessage(const Message* msg) { + if (transport_ == nullptr) return; + + if (auto j = msg->pack()) { + if (isLogTrace()) { + auto str = jsonToString(*j); + this->trace("Outgoing JSON is: %s\n", str.c_str()); + } + + auto status = transport_->sendJson(this, *j); + if (status != Transport::OK) { + CHPLDEF_FATAL(this, "Failed to send JSON!"); + } + } +} + +void Server::registerEvent(chpl::owned event) { + if (!event.get()) return; + events_.push_back(std::move(event)); +} + +void Server::enqueue(chpl::owned msg) { + if (!msg.get()) return; + messages_.push(std::move(msg)); +} + +bool Server::handle(chpl::owned msg) { + if (!msg.get()) return false; + bool ret = false; + + // Handle a response to an outbound request. + if (auto rsp = msg->toResponse()) { + CHPL_ASSERT(rsp->isCompleted()); + CHPL_ASSERT(msg->id().kind() != JsonValue::Null); + + auto it = idToOutboundRequest_.find(msg->idToString()); + if (it != idToOutboundRequest_.end()) { + auto& req = it->second; + CHPL_ASSERT(req->behavior() == Message::OUTBOUND_REQUEST); + req->handle(this, rsp); + idToOutboundRequest_.erase(it); + } else { + CHPLDEF_TODO(); + } + + return false; + } + + if (msg->status() == Message::PROGRESSING) CHPLDEF_TODO(); + + switch (msg->behavior()) { + case Message::INCOMING_REQUEST: + case Message::INCOMING_NOTIFY: { + if (msg->status() == Message::FAILED) CHPLDEF_TODO(); + msg->handle(this); + if (msg->status() == Message::FAILED) CHPLDEF_TODO(); + if (!msg->isNotification()) sendMessage(msg.get()); + } break; + + case Message::OUTBOUND_NOTIFY: + case Message::OUTBOUND_REQUEST: { + if (msg->status() == Message::FAILED) CHPLDEF_TODO(); + CHPL_ASSERT(msg->status() == Message::PENDING); + sendMessage(msg.get()); + + // Requests are stored for later evaluation. + if (!msg->isNotification()) { + auto idStr = msg->idToString(); + auto it = idToOutboundRequest_.find(idStr); + if (it != idToOutboundRequest_.end()) CHPLDEF_TODO(); + auto p = std::make_pair(std::move(idStr), std::move(msg)); + idToOutboundRequest_.emplace(std::move(p)); + } + } break; + + default: { + CHPLDEF_IMPOSSIBLE(); + } break; + } + + // Prepare to exit if we received the 'Exit' message. + if (msg->tag() == MessageTag::Exit) { + CHPL_ASSERT(state() == Server::SHUTDOWN); + + // Should not have any messages or pending requests in the queue. + if (!messages_.empty()) CHPLDEF_TODO(); + if (!idToOutboundRequest_.size()) CHPLDEF_TODO(); + ret = true; + } + + return ret; +} + +int Server::run() { + bool running = true; + int ret = 0; + + while (running) { + doRunEvents(Event::LOOP_START, nullptr); + + if (auto msg = dequeueOneMessage()) { + doRunEvents(Event::PRIOR_HANDLE, msg.get()); + const bool isTimeToExit = handle(std::move(msg)); + running = !isTimeToExit; + doRunEvents(Event::AFTER_HANDLE, msg.get()); + } + + logger().flush(); + } + + return ret; +} + } // end namespace 'chpldef' diff --git a/tools/chpldef/Server.h b/tools/chpldef/Server.h index 2927b56c4435..90ce4f0f24c8 100644 --- a/tools/chpldef/Server.h +++ b/tools/chpldef/Server.h @@ -21,65 +21,249 @@ #ifndef CHPL_TOOLS_CHPLDEF_SERVER_H #define CHPL_TOOLS_CHPLDEF_SERVER_H -#include "./Logger.h" -#include "./misc.h" - +#include "Format.h" +#include "Logger.h" +#include "Message.h" +#include "misc.h" +#include "Transport.h" #include "chpl/framework/Context.h" +#include "chpl/framework/ErrorBase.h" #include "chpl/framework/UniqueString.h" - +#include "chpl/uast/AstNode.h" #include "llvm/ADT/Optional.h" #include "llvm/Support/JSON.h" - #include #include #include +#include #include namespace chpldef { -class Initialize; -class Initialized; -class Shutdown; - class Server { public: + static constexpr auto NAME = "chpldef"; + static constexpr auto VERSION = "0.0.0"; + static constexpr int DEFAULT_GC_FREQUENCY = 64; + enum State { - UNINIT, /** Client has not sent 'Initialize' yet. */ - INIT, /** We have responded to 'Initialize'. */ + UNINITIALIZED, /** Client has not sent 'Initialize' yet. */ + SETUP, /** We have responded to 'Initialize'. */ READY, /** Client has sent us 'Initialized'. */ SHUTDOWN /** Client has sent us 'Shutdown'. */ }; - class Configuration {}; + /** This can be used to configure a server when creating it. */ + struct Configuration { + std::string logFile; + Logger::Level logLevel = Logger::OFF; + std::string chplHome; + int garbageCollectionFrequency = DEFAULT_GC_FREQUENCY; + bool warnUnstable = false; + bool enableStandardLibrary = false; + bool compilerDebugTrace = false; + chpl::owned transport = nullptr; + }; + + /** Document synchronization messages will mutate text entries. */ + struct TextEntry { + int64_t version = -1; + int64_t lastRevisionContentsUpdated = -1; + bool isOpen = false; + }; + + using TextRegistry = std::map; + + /** This error handler is installed into the Chapel frontend. */ + class ErrorHandler : public chpl::Context::ErrorHandler { + public: + using Error = chpl::ErrorBase; + using ErrorAndRevision = std::pair, int64_t>; + using ErrorList = std::vector; + private: + ErrorList errors_; + Server* ctx_ = nullptr; + public: + ErrorHandler(Server* ctx) : ctx_(ctx) {} + ~ErrorHandler() = default; + virtual void report(chpl::Context* chapel, const Error* err) override; + inline const ErrorList& errors() const { return errors_; } + inline void clear() { errors_.clear(); } + inline size_t numErrors() { return errors_.size(); } + }; + + /** The server runs an event during each cycle of the event loop. */ + class Event { + public: + enum When { + NEVER = 0, + ALWAYS = 1, /** Before and after message handling. */ + LOOP_START = 2, /** Start of server loop iteration. */ + PRIOR_HANDLE = 4, /** Before a message is handled. */ + AFTER_HANDLE = 8, /** After a message is handled. */ + }; + + private: + const char* name_ = nullptr; + int mask_ = 0; + + public: + Event(const char* name, int mask) : name_(name), mask_(mask) {} + virtual ~Event() = default; + inline const char* name() const { return name_; } + inline int mask() const { return mask_; } + + /** By default, events will not run until server state is 'READY'. */ + virtual bool canRun(Server* ctx) const; + + /** Run the event at the current stage, indicated by 'when'. */ + virtual void run(Server* ctx, const Message* msg, When when) = 0; + }; + + /** Note that below we list the compiler instance before the server + configuration so that the server initializer can use the config + to help initialize the compiler. */ private: - State state_ = UNINIT; + State state_ = UNINITIALIZED; Logger logger_; - chpl::owned chplctx_ = nullptr; - int revision_; + chpl::Context chapel_; + Configuration config_; + TextRegistry textRegistry_; + int64_t revision_ = 0; + ErrorHandler* errorHandler_ = nullptr; + std::queue> messages_; + std::map> idToOutboundRequest_; + std::vector> events_; + Transport* transport_ = nullptr; + + inline bool + isLogLevel(Logger::Level level) const { return logger_.level() == level; } + + chpl::Context createCompilerContext(const Configuration& config) const; + bool shouldGarbageCollect() const; + bool shouldPrepareToGarbageCollect() const; + void doRegisterEssentialEvents(); + void doRunEvents(Event::When when, const Message* msg=nullptr); + chpl::owned dequeueOneMessage(); + void sendMessage(const Message* msg); protected: - friend class chpldef::Shutdown; - friend class chpldef::Initialize; - friend class chpldef::Initialized; + friend chpldef::Initialize; + friend chpldef::Initialized; + friend chpldef::Shutdown; + friend chpldef::DidOpen; inline void setState(State state) { state_ = state; } + inline TextRegistry& mutableTextRegistry() { return textRegistry_; } public: - Server(); + Server(Configuration config); ~Server() = default; + /** Event order matters, and events can be registered more than once. */ + void registerEvent(chpl::owned event); + + /** Enqueue a message to be worked on by the server. */ + virtual void enqueue(chpl::owned msg); + + /** Handling depends on the behavior of the message. There are four modes: + a message can either be a notification or a request, and it can either + be incoming (client-to-server) or outbound (server-to-client). + + For incoming requests, if the parameters of the request are unpacked, + then they are used to compute a result. The state of the message is + updated with the computation results. The message can then be packed + into JSON so that the results can be sent to the client. + + For incoming notifications, if the parameters of the notification + are unpacked, then they are used to compute a state change within + the server. Notifications do not compute a result, and no response + is returned to the client. + + For outbound requests, the server will input parameters to create the + request. The request is then packed into JSON and sent to the + client. The client is expected to send a separate response to the + server with an ID that matches the originating request. When the + response is received, it is given to the originating request. The + result contained in the response is used to compute a state change + within the server. + + Outbound notifications function similiarly to outbound requests, + except that the server does not expect a response from the client + (and none can be sent). + + This method returns 'true' if the handled message was the 'Exit' + message. This indicates that the server should cease execution. + */ + virtual bool handle(chpl::owned msg); + + /** Run the server main loop until exit. Return value is exit code. */ + int run(); + + /** Run the server main loop for 'n' iterations. If 'n' is '0' then the + server loop will run forever. */ + opt run(size_t n); + + /** Get a handle to the current transport layer. */ + inline Transport* transport() { return transport_; } + + inline ErrorHandler* errorHandler() { return errorHandler_; } + inline State state() const { return state_; } - inline int revision() const { return revision_; } - inline const chpl::Context* chplctx() const { return chplctx_.get(); } + + inline int64_t revision() const { return revision_; } + + inline const chpl::Context* chapel() const { return &chapel_; } + + inline const TextRegistry& textRegistry() const { return textRegistry_; } + + inline int garbageCollectionFrequency() const { + return config_.garbageCollectionFrequency; + } void setLogger(Logger&& logger); inline Logger& logger() { return logger_; } - void sleep(int msec); + inline bool isLogMessage() const { return isLogLevel(Logger::MESSAGES); } + inline bool isLogVerbose() const { return isLogLevel(Logger::VERBOSE); } + inline bool isLogTrace() const { return isLogLevel(Logger::TRACE); } CHPLDEF_PFMT(2, 3, void message(const char* fmt, ...)); CHPLDEF_PFMT(2, 3, void verbose(const char* fmt, ...)); CHPLDEF_PFMT(2, 3, void trace(const char* fmt, ...)); + + void sleep(int msec); + + enum WithChapelConfig { + CHPL_NO_MASK = 0, + CHPL_BUMP_REVISION = 1, + }; + + /** Execute code with controlled access to the Chapel context. */ + template + auto withChapel(WithChapelConfig c, F&& f, Ns&&... ns) + -> decltype(f(&chapel_, std::forward(ns)...)) { + if (shouldGarbageCollect()) chapel_.collectGarbage(); + if (c & CHPL_BUMP_REVISION) { + chapel_.advanceToNextRevision(shouldPrepareToGarbageCollect()); + ++revision_; + } + return f(&chapel_, std::forward(ns)...); + } + + /** Execute code with controlled access to the Chapel context. */ + template + auto withChapel(F&& f, Ns&&... ns) + -> decltype(withChapel(CHPL_NO_MASK, f, std::forward(ns)...)) { + return withChapel(CHPL_NO_MASK, f, std::forward(ns)...); + } + + template + std::string fmt(const T& t, Format::Detail dt=Format::DEFAULT) { + Format f(this, dt); + f.write(t); + return f.read(); + } }; } // end namespace 'chpldef' diff --git a/tools/chpldef/Transport.cpp b/tools/chpldef/Transport.cpp index ce8e4cb39d32..cb5083504ce2 100644 --- a/tools/chpldef/Transport.cpp +++ b/tools/chpldef/Transport.cpp @@ -18,8 +18,8 @@ * limitations under the License. */ -#include "./Transport.h" - +#include "Transport.h" +#include "Server.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/JSON.h" #include @@ -27,18 +27,19 @@ namespace chpldef { -// TODO: Will block until it sees a '\n' delimited line. -static bool readLineTrimCarriageReturn(std::istream& is, std::string& out) { - std::getline(is, out); +static Transport::Status +streamReadLineTrimCarriageReturn(Transport* tp, std::string& out) { + auto ret = tp->read(0, out, Transport::READ_UNTIL_NEWLINE); + if (!out.empty() && out.back() == '\n') out.pop_back(); if (!out.empty() && out.back() == '\r') out.pop_back(); - bool ret = !is.bad() && !is.fail(); return ret; } // SEE: 'clangd::JSONTransport::readStandardMessage' for signal handling. -static bool readLength(std::istream& is, int length, std::string& out) { +static bool +streamReadLength(std::istream& is, int64_t length, std::string& out) { out.resize(length); - for (int bytes = length, read; bytes > 0; bytes -= read) { + for (int64_t bytes = length, read; bytes > 0; bytes -= read) { is.read(&out[0], bytes); read = is.gcount(); } @@ -47,18 +48,18 @@ static bool readLength(std::istream& is, int length, std::string& out) { // TODO: Remove the asserts by logging and returning an error/message. // SEE: 'clangd::JSONTransport::readStandardMessage' for robust impl... -bool Transport::readJsonBlocking(Server* ctx, std::istream& is, - JsonValue& out) { - std::string line; +Transport::Status Transport::readJson(Server* ctx, JsonValue& j) { + Status status = Transport::OK; bool err = false; - int length = 0; + std::string line; + int64_t length = 0; // Note the fatal crashes in this function. I've inserted these because // if we fail to read a line in the protocol, it's difficult or // impossible for the server to recover. At any rate, we can worry about // recovering later (and ideally, not reinvent the wheel). - err |= !readLineTrimCarriageReturn(is, line); - if (err) CHPLDEF_FATAL(ctx, "Reading first line of message data"); + status = streamReadLineTrimCarriageReturn(this, line); + if (status) CHPLDEF_FATAL(ctx, "Reading first line of message data"); static constexpr const char* prefix = "Content-Length:"; err |= !(line.rfind(prefix) == 0); @@ -72,35 +73,75 @@ bool Transport::readJsonBlocking(Server* ctx, std::istream& is, err |= length <= 0; if (err) CHPLDEF_FATAL(ctx, "Message length %s is invalid", str.c_str()); - err |= !readLineTrimCarriageReturn(is, line); + status = streamReadLineTrimCarriageReturn(this, line); + if (status) CHPLDEF_FATAL(ctx, "Reading CRLF before payload"); + err |= !line.empty(); if (err) CHPLDEF_FATAL(ctx, "Expected CRLF before payload"); - err |= !readLength(is, length, line); - if (err) CHPLDEF_FATAL(ctx, "Failed to read payload data"); + status = this->read(length, line); + if (status) CHPLDEF_FATAL(ctx, "Failed to read payload data"); - // Create and write out the JSON now. No need to move the string into - // the parse call because the JSON value will always copy. + // Parse and write out the JSON now. if (auto json = llvm::json::parse(line)) { - out = std::move(*json); - return true; + j = std::move(*json); + return OK; } // Failed to parse. - ctx->verbose("Failed to parse JSON object of length: %d\n", length); + ctx->verbose("Failed to parse JSON object of length: %lld\n", length); ctx->trace("String is: %s\n", line.c_str()); - return false; + return ERROR_JSON_PARSE_FAILED; } -// TODO: Is there a way to write to a 'std::ostream' with LLVM? -bool Transport::sendJsonBlocking(Server* ctx, std::ostream& os, - const JsonValue& json) { +// TODO: Is there a way to write the contents without the copies? +Transport::Status Transport::sendJson(Server* ctx, const JsonValue& j) { + std::stringstream ss; const bool pretty = false; - std::string s = jsonToString(json, pretty); - os << "Content-Length: " << s.size() << "\r\n\r\n" << s; - os.flush(); - bool ret = !os.bad() && !os.fail(); + std::string contents = jsonToString(j, pretty); + + ss << "Content-Length: " << contents.size() << "\r\n\r\n" << contents; + + auto ret = this->send(ss.str()); + + if (ret != OK) { + ctx->trace("Failed to send JSON object of length: %zu\n", + contents.size()); + ctx->trace("Payload is: %s\n", contents.c_str()); + } + return ret; } +Transport::Status +TransportStdio::read(int64_t size, std::string& str, Transport::Behavior b) { + if (size < 0) return ERROR_INVALID_SIZE; + + bool readEntireContents = (size == 0); + bool readUntilNewline = (b & READ_UNTIL_NEWLINE); + int64_t bytesToRead = size; + Status ret = OK; + + if (readUntilNewline) { + if (!readEntireContents) CHPLDEF_TODO(); + std::getline(std::cin, str, '\n'); + bool err = std::cin.fail() || std::cin.bad(); + if (err) ret = ERROR; + return ret; + } + + if (readEntireContents) CHPLDEF_TODO(); + bool ok = streamReadLength(std::cin, bytesToRead, str); + if (!ok) ret = ERROR; + + return ret; +} + +Transport::Status +TransportStdio::send(const std::string& str, Transport::Behavior b) { + std::cout << str; + bool err = std::cout.fail() || std::cout.bad(); + return err ? ERROR : OK; +} + } // end namespace 'chpldef' diff --git a/tools/chpldef/Transport.h b/tools/chpldef/Transport.h index 6892cf1eae17..95e82c4dfd88 100644 --- a/tools/chpldef/Transport.h +++ b/tools/chpldef/Transport.h @@ -21,34 +21,59 @@ #ifndef CHPL_TOOLS_CHPLDEF_TRANSPORT_H #define CHPL_TOOLS_CHPLDEF_TRANSPORT_H -#include "./Message.h" -#include "./Server.h" - -#include -#include +#include "misc.h" +#include +#include namespace chpldef { -/** - This class is intended to model or wrap some existing transport layer, - but is currently empty. -*/ +class Server; + +/** This is the base class for various transport layers. */ class Transport { public: - /** Helper which reads a message from a stream in a blocking way. - Returns 'false' if there was an error. If there was not an - error, then the formal 'outJson' will contain the JSON for the - message. */ - static bool - readJsonBlocking(chpldef::Server* ctx, std::istream& is, - JsonValue& out); - - /** Helper which sends JSON to a stream in a blocking way. - Returns 'false' if there was an error. */ - static bool - sendJsonBlocking(chpldef::Server* ctx, std::ostream& os, - const JsonValue& json); + /** Error codes indicate the result of a read or write. */ + enum Status { + OK = 0, + ERROR = 1, + ERROR_JSON_PARSE_FAILED = 2, + ERROR_INVALID_SIZE = 4, + }; + + /** Provides a way to configure each read or write. */ + enum BehaviorTag { + DEFAULT = 0, + READ_UNTIL_NEWLINE = 1 + }; + + /** Behavior is a bitfield composed of 'BehaviorTag'. */ + using Behavior = int; + + virtual ~Transport() = default; + + /** Read up to 'size' bytes into 'str' with the given behaviors 'b'. */ + virtual Status read(int64_t size, std::string& str, Behavior b=0) = 0; + + /** Send the bytes of 'str' with the given behaviors 'b'. */ + virtual Status send(const std::string& str, Behavior b=0) = 0; + + /** Read JSON into the given JSON value 'j'. */ + Status readJson(Server* ctx, JsonValue& j); + + /** Serialize the contents of the JSON 'j' into text and send it. */ + Status sendJson(Server* ctx, const JsonValue& j); +}; + +/** This transport layer performs blocking reads over STDIN and STDOUT. */ +class TransportStdio final : public Transport { +public: + TransportStdio() {} + virtual ~TransportStdio() = default; + virtual Status read(int64_t size, std::string& str, + Behavior b=0) override; + virtual Status send(const std::string& str, + Behavior b=0) override; }; } // end namespace 'chpldef' diff --git a/tools/chpldef/chpldef.cpp b/tools/chpldef/chpldef.cpp index 8d43c2b1c50d..567e72b2dc8e 100644 --- a/tools/chpldef/chpldef.cpp +++ b/tools/chpldef/chpldef.cpp @@ -26,102 +26,49 @@ using namespace chpldef; -int main(int argc, char** argv) { - Server context; - Server* ctx = &context; +static Server::Configuration prepareServerConfig(int argc, char** argv) { + Server::Configuration ret; - cmd::doParseOptions(ctx, argc, argv); + cmd::doParseOptions(argc, argv); - // Configure the logger instance that the context will use. - auto setupLogger = !cmd::logFile.empty() - ? Logger::createForFile(cmd::logFile) - : Logger(); - if (!setupLogger.isLogging()) { - std::cerr << "Failed to open log file!" << std::endl; + if (!cmd::chplHome.empty()) { + ret.chplHome = cmd::chplHome; + } else if (const char* chplHomeEnv = getenv("CHPL_HOME")) { + ret.chplHome = chplHomeEnv; } else { - ctx->setLogger(std::move(setupLogger)); + std::cerr << "No value for '$CHPL_HOME'!" << std::endl; } - // Get the logger and set the verbosity level. - auto& logger = ctx->logger(); - logger.setLevel(cmd::logLevel); - - // Flush every log message immediately to avoid losing info on crash. - logger.setFlushImmediately(true); - - ctx->message("Logging to '%s' with level '%s'\n", - logger.filePath().c_str(), - logger.levelToString()); - - int run = 1; - int ret = 0; - - while (run) { - ctx->message("Server awaiting message...\n"); - - auto json = JsonValue(nullptr); - - // TODO: This operation blocks. We'd like non-blocking IO. There are a - // few ways to accomplish this. Ideally we find some sort of high-level, - // non-blocking "transport" abstraction that we can use. - // - // -- LLVM might offer transport layer APIs - // -- LLVM might offer non-blocking IO - // -- C++ futures might be useful here, but we need to be able to - // control which thread runs the future and its lifetime (to - // make sure that the thread reading e.g., stdin is reliably - // closed...). - // -- We can do it ourselves using C++ threads, CVs, and locks, and - // wrap it up in a neat little function that populates a queue - // of messages for the context. - bool ok = Transport::readJsonBlocking(ctx, std::cin, json); - CHPL_ASSERT(ok); + ret.logFile = cmd::logFile; + ret.logLevel = cmd::logLevel; + ret.garbageCollectionFrequency = cmd::garbageCollectionFrequency; + ret.warnUnstable = cmd::warnUnstable; + ret.enableStandardLibrary = cmd::enableStandardLibrary; + ret.compilerDebugTrace = cmd::compilerDebugTrace; - if (logger.level() == Logger::TRACE) { - ctx->trace("Incoming JSON is %s\n", jsonToString(json).c_str()); - } + // TODO: Configure transport with a flag. + ret.transport = chpl::toOwned(new TransportStdio()); - // Create a message from the incoming JSON... - auto msg = Message::request(ctx, std::move(json)); - CHPL_ASSERT(msg.get()); - - // Parsing the JSON could have failed. - if (msg->status() == Message::FAILED || msg->isResponse()) { - CHPLDEF_TODO(); - } - - CHPL_ASSERT(msg->status() == Message::PENDING); - CHPL_ASSERT(!msg->isOutbound()); - - if (msg->tag() == Message::Exit) { - CHPL_ASSERT(ctx->state() == Server::SHUTDOWN); - run = 0; - } + return ret; +} - // We have an immediate response, so send it. - if (auto optRsp = Message::handle(ctx, msg.get())) { - auto pack = optRsp->pack(); - if (logger.level() == Logger::TRACE) { - auto str = jsonToString(pack); - ctx->trace("Outgoing JSON is %s\n", str.c_str()); - } +int main(int argc, char** argv) { + auto config = prepareServerConfig(argc, argv); + Server server(std::move(config)); + Server* ctx = &server; - ok = Transport::sendJsonBlocking(ctx, std::cout, std::move(pack)); - CHPL_ASSERT(ok); + // Flush every log message immediately to avoid losing info on crash. + auto& log = ctx->logger(); + log.setFlushImmediately(true); - // Response was delayed. - } else { - if (!msg->isNotification()) { - CHPLDEF_FATAL(ctx, "Handler response should not be delayed!"); - } - } + ctx->message("Logging to '%s' with level '%s'\n", + log.filePath().c_str(), + log.levelToString()); - // Flush the log in case something goes wrong. - logger.flush(); - } + int ret = ctx->run(); ctx->message("Server exiting with code '%d'\n", ret); - logger.flush(); + log.flush(); return ret; } diff --git a/tools/chpldef/command-line-flags.cpp b/tools/chpldef/command-line-flags.cpp index f7c6160c4018..638530523523 100644 --- a/tools/chpldef/command-line-flags.cpp +++ b/tools/chpldef/command-line-flags.cpp @@ -20,6 +20,7 @@ #include "./command-line-flags.h" #include "llvm/Support/CommandLine.h" +#include namespace chpldef { namespace cmd { @@ -36,19 +37,35 @@ Flag logLevel("log-level", clEnumValN(Logger::VERBOSE, "verbose", "Messages and details"), clEnumValN(Logger::TRACE, "trace", "Full trace of execution"))); -template -static bool isSameMemoryLocation(const llvm::cl::opt& f1, - const llvm::cl::Option* f2) { - static_assert(std::is_base_of>::value); - auto ptr = static_cast(&f1); - return ptr == f2; -} +Flag chplHome("chpl-home", + llvm::cl::desc("Specify the location of Chapel home"), + llvm::cl::value_desc("A string specifying a path")); + +Flag warnUnstable("warn-unstable", + llvm::cl::desc("Set to have the Chapel compiler emit unstable warnings")); + +Flag garbageCollectionFrequency("gc-frequency", + llvm::cl::init(Server::DEFAULT_GC_FREQUENCY), + llvm::cl::desc("Set the garbage collection frequency"), + llvm::cl::value_desc("An integer specifying a revision interval")); + +Flag enableStandardLibrary("enable-std", + llvm::cl::init(false), + llvm::cl::desc("Set to enable use of the standard library")); + +Flag compilerDebugTrace("debug-trace", + llvm::cl::init(false), + llvm::cl::desc("Set to enable high verbosity query debug tracing")); + +namespace { +static std::set flagAddresses = { + &logFile, &logLevel, &chplHome, &warnUnstable, &garbageCollectionFrequency, + &enableStandardLibrary, + &compilerDebugTrace +}; -// TODO: Get these things in an array so we don't have O(n) code? static bool isChpldefRegisteredFlag(const llvm::cl::Option* f) { - if (isSameMemoryLocation(logFile, f)) return true; - if (isSameMemoryLocation(logLevel, f)) return true; - return false; + return flagAddresses.find(f) != flagAddresses.end(); } static void disableNonChpldefOptions() { @@ -62,7 +79,9 @@ static void disableNonChpldefOptions() { } } -void doParseOptions(Server* ctx, int argc, char** argv) { +} // end anonymous namespace + +void doParseOptions(int argc, char** argv) { disableNonChpldefOptions(); llvm::cl::ParseCommandLineOptions(argc, argv); } diff --git a/tools/chpldef/command-line-flags.h b/tools/chpldef/command-line-flags.h index 70c97cefd432..b5dcae4800c9 100644 --- a/tools/chpldef/command-line-flags.h +++ b/tools/chpldef/command-line-flags.h @@ -34,7 +34,7 @@ namespace cmd { /** Call this in 'main' to parse command-line options. Only the options added by this program will be shown. */ -void doParseOptions(Server* ctx, int argc, char** argv); +void doParseOptions(int argc, char** argv); /** Alias for LLVM's command-line option type. */ template using Flag = llvm::cl::opt; @@ -45,6 +45,21 @@ extern Flag logFile; /** The log level. Defaults to 'Logger::OFF'. */ extern Flag logLevel; +/** The path to 'CHPL_HOME'. Overridden by the 'CHPL_HOME' env var. */ +extern Flag chplHome; + +/** Whether or not unstable warnings should be emitted. */ +extern Flag warnUnstable; + +/** The GC frequency. Defaults to a server-decided value. */ +extern Flag garbageCollectionFrequency; + +/** If the standard library should be enabled. Defaults to 'false'. */ +extern Flag enableStandardLibrary; + +/** Enable debug tracing for the compiler. Defaults to 'false'. */ +extern Flag compilerDebugTrace; + } // end namespace 'cmd' } // end namespace 'chpldef' diff --git a/tools/chpldef/compiler-gadgets.cpp b/tools/chpldef/compiler-gadgets.cpp new file mode 100644 index 000000000000..74ac22528506 --- /dev/null +++ b/tools/chpldef/compiler-gadgets.cpp @@ -0,0 +1,112 @@ +/* + * Copyright 2020-2023 Hewlett Packard Enterprise Development LP + * Copyright 2004-2019 Cray Inc. + * Other additional copyright holders may be indicated within. + * + * The entirety of this work is 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. + */ + +#include "compiler-gadgets.h" + +namespace chpldef { + +const chpl::uast::BuilderResult& +parseFromUri(chpl::Context* chapel, chpl::UniqueString uri) { + using namespace chpl::parsing; + chpl::UniqueString empty; + auto& ret = parseFileToBuilderResultAndCheck(chapel, uri, empty); + return ret; +} + +const chpl::uast::BuilderResult& +parseFromUri(chpl::Context* chapel, const std::string& uri) { + auto ustr = chpl::UniqueString::get(chapel, uri); + return parseFromUri(chapel, ustr); +} + +static bool isSymbolOfInterest(const chpl::uast::AstNode* ast) { + if (ast->isIdentifier()) return true; + if (ast->isDot()) return true; + return false; +} + +// Preorder traversal to collect identifiers in lexical order. +static void doMapLinesInModule(chpl::Context* chapel, + const chpl::uast::BuilderResult& br, + LineToIdsMap& m, + const chpl::uast::AstNode* node) { + for (auto ast : node->children()) { + if (ast->isComment()) continue; + + // Push back the occupied lines. This is usually just one line but can + // be up to 3 in the case of a dot expression. + if (isSymbolOfInterest(ast)) { + auto loc = br.idToLocation(ast->id(), chpl::UniqueString()); + CHPL_ASSERT(!loc.isEmpty()); + for (int i = loc.firstLine(); i <= loc.lastLine(); i++) { + auto& v = m[i]; + v.push_back(ast->id()); + } + } + + bool recurse = !ast->isModule() && !ast->isLeaf(); + if (recurse) { + doMapLinesInModule(chapel, br, m, ast); + } + } +} + +static LineToIdsMap +mapLinesToIdsInModuleImpl(chpl::Context* chapel, chpl::UniqueString uri) { + auto& br = parseFromUri(chapel, uri); + LineToIdsMap ret; + auto it = br.topLevelExpressions(); + for (auto ast : it) doMapLinesInModule(chapel, br, ret, ast); + return ret; +} + +const LineToIdsMap& +mapLinesToIdsInModule(chpl::Context* chapel, chpl::UniqueString uri) { + using namespace chpl; + QUERY_BEGIN(mapLinesToIdsInModule, chapel, uri); + auto ret = mapLinesToIdsInModuleImpl(chapel, uri); + return QUERY_END(ret); +} + +chpl::Location +locationFromUriAndPosition(chpl::Context* chapel, const std::string& uri, + const Position& pos) { + auto ustr = chpl::UniqueString::get(chapel, uri); + int l1 = pos.line + 1; + int c1 = pos.character + 1; + int l2 = pos.line + 1; + int c2 = pos.character + 1; + return chpl::Location(ustr, l1, c1, l2, c2); +} + +const chpl::uast::FnCall* +parentCallIfBaseExpression(chpl::Context* chapel, + const chpl::uast::AstNode* ast) { + if (!ast) return nullptr; + auto p = chpl::parsing::parentAst(chapel, ast); + if (!p) return nullptr; + + if (auto call = p->toFnCall()) + if (auto ce = call->calledExpression()) + if (ce == ast) return call; + return nullptr; +} + +} // end namespace 'chpldef' diff --git a/tools/chpldef/compiler-gadgets.h b/tools/chpldef/compiler-gadgets.h new file mode 100644 index 000000000000..77b68e972a79 --- /dev/null +++ b/tools/chpldef/compiler-gadgets.h @@ -0,0 +1,69 @@ +/* + * Copyright 2020-2023 Hewlett Packard Enterprise Development LP + * Copyright 2004-2019 Cray Inc. + * Other additional copyright holders may be indicated within. + * + * The entirety of this work is 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. + */ + +#ifndef CHPL_TOOLS_CHPLDEF_COMPILER_GADGETS_H +#define CHPL_TOOLS_CHPLDEF_COMPILER_GADGETS_H + +#include "protocol-types.h" +#include "chpl/framework/Context.h" +#include "chpl/framework/UniqueString.h" +#include "chpl/parsing/parsing-queries.h" +#include "chpl/resolution/resolution-queries.h" +#include "chpl/resolution/resolution-types.h" + +// This header contains 'compiler gadgets', functions manipulating the +// Chapel compiler that may be useful for multiple LSP computations. +// They come in two flavors: +// +// -- Those that take 'Server*' as the first argument. Generally, this +// implies that the gadget will log to the log file, as it will +// implement a more substantial operation. +// -- Those that take the 'chpl::Context' only. These are smaller gadgets +// that do not deserve a line in the logger. +// +namespace chpldef { + +using LineToIdsMap = std::map>; + +/** Parse a top level module given a path. */ +const chpl::uast::BuilderResult& +parseFromUri(chpl::Context* chapel, chpl::UniqueString uri); + +/** Parse a top level module given a path. */ +const chpl::uast::BuilderResult& +parseFromUri(chpl::Context* chapel, const std::string& uri); + +/** A compiler query which maps source lines to AST nodes on that line. */ +const LineToIdsMap& +mapLinesToIdsInModule(chpl::Context* chapel, chpl::UniqueString uri); + +/** Given a cursor position and URI, create a Chapel location. */ +chpl::Location +locationFromUriAndPosition(chpl::Context* chapel, const std::string& uri, + const Position& pos); + +/** If 'ast' is the base expression of a call, return the parent call. */ +const chpl::uast::FnCall* +parentCallIfBaseExpression(chpl::Context* chapel, + const chpl::uast::AstNode* ast); + +} // end namespace 'chpldef' + +#endif diff --git a/tools/chpldef/compute-goto-declaration.cpp b/tools/chpldef/compute-goto-declaration.cpp new file mode 100644 index 000000000000..cd4fef1374cc --- /dev/null +++ b/tools/chpldef/compute-goto-declaration.cpp @@ -0,0 +1,211 @@ +/* + * Copyright 2020-2023 Hewlett Packard Enterprise Development LP + * Copyright 2004-2019 Cray Inc. + * Other additional copyright holders may be indicated within. + * + * The entirety of this work is 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. + */ + +#include "compiler-gadgets.h" +#include "./Message.h" +#include "./Server.h" + +namespace chpldef { + +static const chpl::uast::AstNode* +chooseAstClosestToLocation(chpl::Context* chapel, + const chpl::uast::AstNode* astOld, + const chpl::uast::AstNode* astNew, + chpl::Location loc) { + using namespace chpl::parsing; + CHPL_ASSERT(astNew); + CHPL_ASSERT(astOld != astNew); + + // TODO: This can run 'locateAst' on the same AST node O(n) times. + auto locOld = astOld ? locateAst(chapel, astOld) : chpl::Location(); + auto locNew = locateAst(chapel, astNew); + + if (!locNew.contains(loc)) return astOld; + if (!astOld) return astNew; + + // Prefer the location furthest towards the end of the range. + // TODO: Need to compute distance. + auto ret = (locNew > locOld) ? astNew : astOld; + + return ret; +} + +static const chpl::uast::AstNode* +astAtLocation(Server* ctx, chpl::Location loc) { + const auto& m = ctx->withChapel(mapLinesToIdsInModule, loc.path()); + auto it = m.find(loc.firstLine()); + if (it == m.end()) return nullptr; + + ctx->trace("Found '%zu' AST nodes on line '%d'\n", + it->second.size(), + loc.firstLine()); + + const chpl::uast::AstNode* ret = nullptr; + for (auto id : it->second) { + auto last = ret; + ctx->withChapel([&](auto chapel) { + auto ast = chpl::parsing::idToAst(chapel, id); + + if (ctx->isLogTrace()) { + ctx->trace("Considering '%s' and '%s'\n", + ctx->fmt(ret).c_str(), + ctx->fmt(ast).c_str()); + } + + ret = chooseAstClosestToLocation(chapel, ret, ast, loc); + + if (ctx->isLogTrace()) { + ctx->trace("Chose '%s'\n", ctx->fmt(ret).c_str()); + } + + }); + + // TODO: Are we at a fixed point? + if (ret && ret == last) break; + } + + return ret; +} + +static const chpl::uast::Module* +closestModule(chpl::Context* chapel, const chpl::uast::AstNode* ast) { + if (auto mod = ast->toModule()) return mod; + if (auto id = chpl::parsing::idToParentModule(chapel, ast->id())) { + auto mod = chpl::parsing::idToAst(chapel, id); + return mod->toModule(); + } + return nullptr; +} + +static const chpl::resolution::ResolutionResultByPostorderID& +resolveModule(Server* ctx, chpl::ID idMod) { + return ctx->withChapel(chpl::resolution::resolveModule, idMod); +} + +static std::vector +sourceAstToIds(Server* ctx, const chpl::uast::AstNode* ast) { + std::vector ret; + + if (!ast) return ret; + + auto mod = ctx->withChapel(closestModule, ast); + CHPL_ASSERT(mod); + + if (mod == ast) CHPLDEF_TODO(); + + ctx->trace("Target '%s' in containing module '%s'\n", + ctx->fmt(ast).c_str(), + ctx->fmt(mod).c_str()); + + auto& rr = resolveModule(ctx, mod->id()); + auto& re = rr.byAst(ast); + auto parentCall = ctx->withChapel(parentCallIfBaseExpression, ast); + + if (re.isBuiltin()) CHPLDEF_TODO(); + + ctx->trace("Encountered '%zu' errors resolving\n", + ctx->errorHandler()->numErrors()); + + // First do a simple check for target location. + if (auto idTarget = re.toId()) { + ret.push_back(idTarget); + + // It's a call base expression, so we need to pick apart the call. + } else if (parentCall) { + auto& reCall = rr.byAst(parentCall); + + if (auto idTarget = re.toId()) { + ret.push_back(idTarget); + } else { + if (auto tfs = reCall.mostSpecific().only()) { + ret.push_back(tfs->id()); + } else { + CHPLDEF_TODO(); + } + } + + // Else, more work is required... + } else { + CHPLDEF_TODO(); + } + + return ret; +} + +static bool chplLocIsValid(const chpl::Location& loc) { + return !loc.isEmpty() && loc.firstLine() >= 1 && + loc.firstColumn() >= 1 && + loc.lastLine() >= 1 && + loc.lastColumn() >= 1; +} + +static std::variant +computeDeclarationPoints(Server* ctx, const TextDocumentPositionParams& p) { + auto& uri = p.textDocument.uri; + auto& pos = p.position; + auto loc = ctx->withChapel(locationFromUriAndPosition, uri, pos); + + ctx->verbose("Looking for AST at '%s'\n", ctx->fmt(loc).c_str()); + + auto ast = astAtLocation(ctx, loc); + auto ids = sourceAstToIds(ctx, ast); + + ctx->verbose("Found '%zu' target IDs\n", ids.size()); + + // TODO: Use this info to construct a 'LocationLinkArray' instead. + LocationArray ret; + for (auto& id : ids) { + auto loc = ctx->withChapel(chpl::parsing::locateId, id); + + ctx->trace("Found entity at '%s'\n", ctx->fmt(loc).c_str()); + + if (chplLocIsValid(loc)) { + Position start = { static_cast(loc.firstLine()-1), + static_cast(loc.firstColumn()-1) }; + Position end = { static_cast(loc.lastLine()-1), + static_cast(loc.lastColumn()-1) }; + Location out(loc.path().str(), { start, end }); + ret.push_back(std::move(out)); + } + } + + return ret; +} + +template <> +Declaration::ComputeResult +Declaration::compute(Server* ctx, ComputeParams p) { + DeclarationResult ret; + ret.result = computeDeclarationPoints(ctx, p); + return ret; +} + +/** This functionality is turned off in the server capabilities tab, + because Chapel does not distinguish between a definition and a + declaration in the way that some languages such as C/C++ do. */ +template <> +Definition::ComputeResult +Definition::compute(Server* ctx, ComputeParams p) { + DefinitionResult ret; + ret.result = computeDeclarationPoints(ctx, p); + return ret; +} + +} // end namespace 'chpldef' diff --git a/tools/chpldef/compute-lifecycle.cpp b/tools/chpldef/compute-lifecycle.cpp new file mode 100644 index 000000000000..c98e98305583 --- /dev/null +++ b/tools/chpldef/compute-lifecycle.cpp @@ -0,0 +1,133 @@ +/* + * Copyright 2020-2023 Hewlett Packard Enterprise Development LP + * Copyright 2004-2019 Cray Inc. + * Other additional copyright holders may be indicated within. + * + * The entirety of this work is 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. + */ + +#include "./Message.h" +#include "./Server.h" +#include "chpl/parsing/parsing-queries.h" + +namespace { + static constexpr bool UNSUPPORTED_DELIBERATELY = false; + static constexpr bool UNSUPPORTED_TODO = false; + static constexpr bool SUPPORTED = true; +} + +namespace chpldef { + +static TextDocumentSyncOptions +configureTextDocumentSync(Server* ctx) { + TextDocumentSyncOptions ret; + ret.openClose = SUPPORTED; + ret.change = TextDocumentSyncOptions::Full; + ret.willSave = SUPPORTED; + ret.willSaveWaitUntil = SUPPORTED; + ret.save = SaveOptions(); + ret.save->includeText = true; + return ret; +} + +static void +doConfigureStaticCapabilities(Server* ctx, ServerCapabilities& x) { + x.positionEncoding = "utf-16"; + x.textDocumentSync = configureTextDocumentSync(ctx); + x.hoverProvider = UNSUPPORTED_TODO; + x.declarationProvider = SUPPORTED; + x.definitionProvider = UNSUPPORTED_DELIBERATELY; + x.typeDefinitionProvider = UNSUPPORTED_TODO; + x.implementationProvider = UNSUPPORTED_TODO; + x.referencesProvider = UNSUPPORTED_TODO; + x.documentHighlightProvider = UNSUPPORTED_TODO; + x.documentSymbolProvider = UNSUPPORTED_TODO; + x.codeActionProvider = UNSUPPORTED_TODO; + x.colorProvider = UNSUPPORTED_TODO; + x.documentFormattingProvider = UNSUPPORTED_TODO; + x.documentRangeFormattingProvider = UNSUPPORTED_TODO; + x.renameProvider = UNSUPPORTED_TODO; + x.foldingRangeProvider = UNSUPPORTED_TODO; + x.selectionRangeProvider = UNSUPPORTED_TODO; + x.linkEditingRangeProvider = UNSUPPORTED_TODO; + x.callHierarchyProvider = UNSUPPORTED_TODO; + x.monikerProvider = UNSUPPORTED_TODO; + x.typeHierarchyProvider = UNSUPPORTED_TODO; + x.inlineValueProvider = UNSUPPORTED_TODO; + x.inlayHintProvider = UNSUPPORTED_TODO; + x.workspaceSymbolProvider = UNSUPPORTED_TODO; +} + +static ServerInfo configureServerInfo(Server* ctx) { + ServerInfo ret; + ret.name = Server::NAME; + ret.version = Server::VERSION; + return ret; +} + +template <> +Initialize::ComputeResult +Initialize::compute(Server* ctx, ComputeParams p) { + + // Set the log verbosity level if it was requested. + if (auto trace = p.trace) { + auto level = trace->level; + ctx->message("Client requested log level '%s'\n", + Logger::levelToString(level)); + auto &logger = ctx->logger(); + if (level < logger.level()) { + ctx->message("Ignoring as level is '%s'\n", logger.levelToString()); + } else { + ctx->logger().setLevel(level); + } + } + + // Set the server to the 'INIT' state. + CHPL_ASSERT(ctx->state() == Server::UNINITIALIZED); + ctx->setState(Server::SETUP); + + Result ret; + + doConfigureStaticCapabilities(ctx, ret.capabilities); + + ret.serverInfo = configureServerInfo(ctx); + + return ret; +} + +template <> +Initialized::ComputeResult +Initialized::compute(Server* ctx, ComputeParams p) { + CHPL_ASSERT(ctx->state() == Server::SETUP); + ctx->setState(Server::READY); + return {}; +} + +template <> +Shutdown::ComputeResult +Shutdown::compute(Server* ctx, ComputeParams p) { + CHPL_ASSERT(ctx->state() == Server::READY); + ctx->setState(Server::SHUTDOWN); + return {}; +} + +template <> +Exit::ComputeResult +Exit::compute(Server* ctx, ComputeParams p) { + CHPL_ASSERT(ctx->state() == Server::SHUTDOWN); + return {}; +} + +} // end namespace 'chpldef' diff --git a/tools/chpldef/compute-synchronization.cpp b/tools/chpldef/compute-synchronization.cpp new file mode 100644 index 000000000000..1844a3f597d7 --- /dev/null +++ b/tools/chpldef/compute-synchronization.cpp @@ -0,0 +1,77 @@ +/* + * Copyright 2020-2023 Hewlett Packard Enterprise Development LP + * Copyright 2004-2019 Cray Inc. + * Other additional copyright holders may be indicated within. + * + * The entirety of this work is 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. + */ + +#include "Message.h" +#include "Server.h" +#include "chpl/parsing/parsing-queries.h" + +namespace chpldef { + +template<> +DidOpen::ComputeResult DidOpen::compute(Server* ctx, ComputeParams p) { + auto& tdi = p.textDocument; + auto& e = ctx->mutableTextRegistry()[tdi.uri]; + + if (e.isOpen) { + CHPLDEF_TODO(); + return {}; + } + + CHPL_ASSERT(tdi.version > e.version); + + // NOTE: I think we always have to bump the revision here. This is + // because this file may have been implicitly parsed from disk as + // as result of resolving a use/import. The contents are considered + // to have changed and the "truth of the file's contents" are determined + // by the client as long as it has the file open. Cannot implicitly + // read from disk, so have to bump the revision to ensure correctness. + ctx->withChapel(Server::CHPL_BUMP_REVISION, [&](auto chapel) { + chpl::parsing::setFileText(chapel, tdi.uri, tdi.text); + auto& fc = chpl::parsing::fileText(chapel, tdi.uri); + CHPL_ASSERT(!fc.error()); + CHPL_ASSERT(fc.text() == tdi.text); + e.version = tdi.version; + e.lastRevisionContentsUpdated = ctx->revision(); + e.isOpen = true; + }); + + return {}; +} + +template<> +DidChange::ComputeResult DidChange::compute(Server* ctx, ComputeParams p) { + CHPLDEF_TODO(); + return {}; +} + + +template<> +DidSave::ComputeResult DidSave::compute(Server* ctx, ComputeParams p) { + CHPLDEF_TODO(); + return {}; +} + +template<> +DidClose::ComputeResult DidClose::compute(Server* ctx, ComputeParams p) { + CHPLDEF_TODO(); + return {}; +} + +} // end namespace 'chpldef' diff --git a/tools/chpldef/events.cpp b/tools/chpldef/events.cpp new file mode 100644 index 000000000000..ca41e33688ae --- /dev/null +++ b/tools/chpldef/events.cpp @@ -0,0 +1,113 @@ +/* + * Copyright 2020-2023 Hewlett Packard Enterprise Development LP + * Copyright 2004-2019 Cray Inc. + * Other additional copyright holders may be indicated within. + * + * The entirety of this work is 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. + */ + +#include "compiler-gadgets.h" +#include "events.h" +#include "Server.h" +#include "Transport.h" + +namespace chpldef { +namespace events { + +/** Always run this - we need to be reading messages. */ +bool ReadMessage::canRun(Server* ctx) const { return true; } + +void ReadMessage::run(Server* ctx, const Message* msg, When when) { + CHPL_ASSERT(!msg && when == Event::LOOP_START); + + ctx->message("Attempting to read message...\n"); + auto ts = ctx->transport(); + + if (ts == nullptr) { + ctx->message("Giving up because no transport was found!\n"); + return; + } + + JsonValue json(nullptr); + + // TODO: This operation blocks. We'd like non-blocking IO. There are a + // few ways to accomplish this. Ideally we find some sort of high-level, + // non-blocking "transport" abstraction that we can use. + // + // -- LLVM might offer transport layer APIs + // -- LLVM might offer non-blocking IO + // -- C++ futures might be useful here, but we need to be able to + // control which thread runs the future and its lifetime (to + // make sure that the thread reading e.g., stdin is reliably + // closed...). + // -- We can do it ourselves using C++ threads, CVs, and locks, and + // wrap it up in a neat little function that populates a queue + // of messages for the context. + auto status = ts->readJson(ctx, json); + CHPL_ASSERT(status == Transport::OK); + + if (ctx->isLogTrace()) { + ctx->trace("Incoming JSON is %s\n", jsonToString(json).c_str()); + } + + // Create a message from the incoming JSON... + auto work = Message::create(ctx, std::move(json)); + CHPL_ASSERT(work.get()); + + // Add the message to the work queue. + ctx->enqueue(std::move(work)); +} + +static bool resolveModulesForMessageTag(MessageTag tag) { + return tag == MessageTag::DidOpen; +} + +void ResolveModules::run(Server* ctx, const Message* msg, When when) { + if (when == Event::PRIOR_HANDLE) { + CHPL_ASSERT(msg); + resolveModulesAfterHandle_ = resolveModulesForMessageTag(msg->tag()); + return; + } + + CHPL_ASSERT(!msg && when == AFTER_HANDLE); + + if (!resolveModulesAfterHandle_) return; + if (lastRevisionResolved_ == ctx->revision()) return; + + // Commit to resolving. + lastRevisionResolved_ = ctx->revision(); + + for (const auto& p : ctx->textRegistry()) { + const auto& uri = p.first; + const auto& entry = p.second; + if (!entry.isOpen) continue; + ctx->withChapel([&](auto chapel) { + auto& br = parseFromUri(chapel, uri); + for (auto ast : br.topLevelExpressions()) { + if (!ast->isModule()) continue; + auto& rr = chpl::resolution::resolveModule(chapel, ast->id()); + std::ignore = rr; + } + }); + } +} + +void PreparePublishDiagnostics::run(Server* ctx, const Message* msg, + When when) { + std::ignore = ctx; +} + +} // end namespace 'events' +} // end namespace 'chpldef' diff --git a/tools/chpldef/events.h b/tools/chpldef/events.h new file mode 100644 index 000000000000..96587e793bc1 --- /dev/null +++ b/tools/chpldef/events.h @@ -0,0 +1,65 @@ +/* + * Copyright 2020-2023 Hewlett Packard Enterprise Development LP + * Copyright 2004-2019 Cray Inc. + * Other additional copyright holders may be indicated within. + * + * The entirety of this work is 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. + */ + +#ifndef CHPL_TOOLS_CHPLDEF_EVENTS_H +#define CHPL_TOOLS_CHPLDEF_EVENTS_H + +#include "Server.h" + +namespace chpldef { +namespace events { + +/** This event reads a message from the transport layer. */ +class ReadMessage : public Server::Event { + static constexpr auto NAME = "ReadMessage"; + static constexpr auto WHEN = LOOP_START; +public: + ReadMessage() : Event(NAME, WHEN) {} + virtual ~ReadMessage() = default; + virtual bool canRun(Server* ctx) const override; + virtual void run(Server* ctx, const Message* msg, When when) override; +}; + +/** This event ensures modules are resolved. */ +class ResolveModules : public Server::Event { + static constexpr auto NAME = "ResolveModules"; + static constexpr auto WHEN = AFTER_HANDLE; + bool resolveModulesAfterHandle_ = false; + int64_t lastRevisionResolved_ = -1; +public: + ResolveModules() : Event(NAME, WHEN) {} + virtual ~ResolveModules() = default; + virtual void run(Server* ctx, const Message* msg, When when) override; +}; + +/** This event may prepare a 'PublishDiagnostics' message to send. */ +class PreparePublishDiagnostics : public Server::Event { + static constexpr auto NAME = "PreparePublishDiagnostics"; + static constexpr auto WHEN = PRIOR_HANDLE | AFTER_HANDLE; +public: + PreparePublishDiagnostics() : Event(NAME, WHEN) {} + virtual ~PreparePublishDiagnostics() = default; + virtual void run(Server* ctx, const Message* msg, When when) override; +}; + +} // end namespace 'events' +} // end namespace 'chpldef' + +#endif diff --git a/tools/chpldef/message-compute.cpp b/tools/chpldef/message-compute.cpp deleted file mode 100644 index 25b9422c3688..000000000000 --- a/tools/chpldef/message-compute.cpp +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2020-2023 Hewlett Packard Enterprise Development LP - * Copyright 2004-2019 Cray Inc. - * Other additional copyright holders may be indicated within. - * - * The entirety of this work is 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. - */ - -#include "./Message.h" - -/** This is one file where message handlers can be implemented. However, if - a particular message's handler grows to be very large in size (e.g., - perhaps 500+ lines), then we should feel free to move it to a different - source file with a name that matches the message name. */ -namespace chpldef { - -static void -doConfigureStaticCapabilities(Server* ctx, ServerCapabilities& x) { - x.positionEncoding = "utf-16"; - x.hoverProvider = false; - x.declarationProvider = false; - x.definitionProvider = false; - x.typeDefinitionProvider = false; - x.implementationProvider = false; - x.referencesProvider = false; - x.documentHighlightProvider = false; - x.documentSymbolProvider = false; - x.codeActionProvider = false; - x.colorProvider = false; - x.documentFormattingProvider = false; - x.documentRangeFormattingProvider = false; - x.renameProvider = false; - x.foldingRangeProvider = false; - x.selectionRangeProvider = false; - x.linkEditingRangeProvider = false; - x.callHierarchyProvider = false; - x.monikerProvider = false; - x.typeHierarchyProvider = false; - x.inlineValueProvider = false; - x.inlayHintProvider = false; - x.workspaceSymbolProvider = false; -} - -static ServerInfo configureServerInfo(Server* ctx) { - ServerInfo ret; - ret.name = "chpldef"; - ret.version = "0.0.0"; - return ret; -} - -Initialize::ComputedResult -Initialize::compute(Server* ctx, const Params& p) { - - // Set the log verbosity level if it was requested. - if (auto trace = p.trace) { - auto level = trace->level; - ctx->message("Client requested log level: '%s'\n", - Logger::levelToString(level)); - auto &logger = ctx->logger(); - if (level < logger.level()) { - ctx->message("Ignoring as level is '%s'\n", logger.levelToString()); - } else { - ctx->logger().setLevel(level); - } - } - - // Set the server to the 'INIT' state. - CHPL_ASSERT(ctx->state() == Server::UNINIT); - ctx->setState(Server::INIT); - - Result ret; - - doConfigureStaticCapabilities(ctx, ret.capabilities); - - ret.serverInfo = configureServerInfo(ctx); - - return ret; -} - -Initialized::ComputedResult -Initialized::compute(Server* ctx, const Params& p) { - CHPL_ASSERT(ctx->state() == Server::INIT); - ctx->setState(Server::READY); - return {}; -} - -Shutdown::ComputedResult -Shutdown::compute(Server* ctx, const Params& p) { - CHPL_ASSERT(ctx->state() == Server::READY); - ctx->setState(Server::SHUTDOWN); - return {}; -} - -Exit::ComputedResult -Exit::compute(Server* ctx, const Params& p) { - CHPL_ASSERT(ctx->state() == Server::SHUTDOWN); - return {}; -} - -} // end namespace 'chpldef' diff --git a/tools/chpldef/message-macro-list.h b/tools/chpldef/message-macro-list.h index 125390468c8e..1ca8d5055743 100644 --- a/tools/chpldef/message-macro-list.h +++ b/tools/chpldef/message-macro-list.h @@ -31,12 +31,24 @@ CHPLDEF_MESSAGE(Initialized, 0, 1, initialized) CHPLDEF_MESSAGE(Shutdown, 0, 0, shutdown) CHPLDEF_MESSAGE(Exit, 0, 1, exit) +/** The document's content is now managed by the client and the server + MUST NOT try to read the document's content using the document's + URI. Open in this sense means that it is managed by the client. An + open notification must not be sent more than once without a close + notification having been sent before. */ +CHPLDEF_MESSAGE(DidOpen, 0, 1, textDocument/didOpen) +CHPLDEF_MESSAGE(DidChange, 0, 1, textDocument/didChange) +CHPLDEF_MESSAGE(DidSave, 0, 1, textDocument/didSave) +CHPLDEF_MESSAGE(DidClose, 0, 1, textDocument/didClose) + +CHPLDEF_MESSAGE(Declaration, 0, 0, textDocument/declaration) +CHPLDEF_MESSAGE(Definition, 0, 0, textDocument/definition) + /* CHPLDEF_MESSAGE(RegisterCapability, client/registerCapability) CHPLDEF_MESSAGE(UnregisterCapability, client/unregisterCapability) CHPLDEF_MESSAGE(SetTrace, $/setTrace) CHPLDEF_MESSAGE(LogTrace, $/logTrace) - */ // @@ -53,12 +65,8 @@ CHPLDEF_MESSAGE(Progress, $/progress) // /* -CHPLDEF_MESSAGE(DidOpen, textDocument/didOpen) -CHPLDEF_MESSAGE(DidChange, textDocument/didChange) -CHPLDEF_MESSAGE(WillSave, textDocument/willSave) CHPLDEF_MESSAGE(WillSaveWaitUntil, textDocument/willSaveWaitUntil) -CHPLDEF_MESSAGE(DidSave, textDocument/didSave) -CHPLDEF_MESSAGE(DidClose, textDocument/didClose) +CHPLDEF_MESSAGE(WillSave, textDocument/willSave) */ // @@ -77,8 +85,6 @@ CHPLDEF_MESSAGE(NotebookDidClose, notebookDocument/didClose) // /* -CHPLDEF_MESSAGE(GotoDeclaration, textDocument/declaration) -CHPLDEF_MESSAGE(GotoDefinition, textDocument/definition) CHPLDEF_MESSAGE(GotoTypeDefinition, textDocument/typeDefinition) CHPLDEF_MESSAGE(GotoImplementation, textDocument/implementation) CHPLDEF_MESSAGE(FindReferences, textDocument/references) diff --git a/tools/chpldef/misc.cpp b/tools/chpldef/misc.cpp index c41b0e5380fe..62e9f4441c6e 100644 --- a/tools/chpldef/misc.cpp +++ b/tools/chpldef/misc.cpp @@ -59,4 +59,14 @@ bool cast(std::string str, int& out) noexcept { return true; } +bool cast(std::string str, int64_t& out) noexcept { + char* end = nullptr; + const char* cstr = str.c_str(); + errno = 0; + int64_t x = std::strtoll(cstr, &end, 10); + if (errno == ERANGE || end == cstr) return false; + out = x; + return true; +} + } // end namespace 'chpldef' diff --git a/tools/chpldef/misc.h b/tools/chpldef/misc.h index 1abca47ba6b4..d67748608215 100644 --- a/tools/chpldef/misc.h +++ b/tools/chpldef/misc.h @@ -70,14 +70,8 @@ using JsonObject = llvm::json::Object; using JsonPath = llvm::json::Path; using JsonMapper = llvm::json::ObjectMapper; -/** Wrapper around LLVM's optional type. */ -#if LLVM_VERSION_MAJOR >= 16 - #include - static_assert(std::is_same, llvm::Optional>::value); - template using opt = std::optional; -#else - template using opt = llvm::Optional; -#endif +/** Wrapper around Chapel's optional type. */ +template using opt = chpl::optional; template inline opt option(T&& t) { return opt(std::move(t)); } @@ -88,6 +82,9 @@ inline opt option(const T& t) { return opt(t); } /** Cast a string to an integer, return 'false' if there was an error. */ bool cast(std::string str, int& out) noexcept; +/** Cast a string to an integer, return 'false' if there was an error. */ +bool cast(std::string str, int64_t& out) noexcept; + /** Print a JSON value's kind as a string. */ const char* jsonKindToString(const JsonValue& json); diff --git a/tools/chpldef/protocol-types.cpp b/tools/chpldef/protocol-types.cpp index e900dd23508d..0ad89166f44b 100644 --- a/tools/chpldef/protocol-types.cpp +++ b/tools/chpldef/protocol-types.cpp @@ -21,13 +21,43 @@ #include "./protocol-types.h" #include "./misc.h" #include "llvm/Support/JSON.h" +#include +#include + +template +static void doAddField(chpldef::JsonObject& obj, const char* name, + const T& field) { + obj[name] = field; +} + +template +static void doAddField(chpldef::JsonObject& obj, const char* name, + const chpldef::opt& field) { + if (field) obj[name] = field; +} /** Helper to make populating JSON object fields less painful. */ -#define FIELD_(name__) { #name__, name__ } +#define FIELD_(obj__, name__) \ + do { doAddField(obj__, #name__, name__); } while (0) + +/** Helper to make reading JSON object fields less painful. */ +#define MAP_(m__, name__) (m__.map(#name__, name__)) namespace chpldef { -std::string ProtocolType::toString() const { +bool BaseProtocolType::fromJson(const JsonValue& j, JsonPath p) { + std::cerr << "Called 'BaseProtocolTypefromJson'!" << std::endl; + std::abort(); + return false; +} + +JsonValue BaseProtocolType::toJson() const { + std::cerr << "Called 'BaseProtocolType::toJson'!" << std::endl; + std::abort(); + return nullptr; +} + +std::string BaseProtocolType::toString() const { auto ret = jsonToString(toJson()); return ret; } @@ -37,8 +67,7 @@ bool EmptyProtocolType::fromJson(const JsonValue& j, JsonPath p) { } JsonValue EmptyProtocolType::toJson() const { - JsonValue ret(nullptr); - return ret; + return nullptr; } bool ClientInfo::fromJson(const JsonValue& j, JsonPath p) { @@ -49,29 +78,14 @@ bool ClientInfo::fromJson(const JsonValue& j, JsonPath p) { return true; } -JsonValue ClientInfo::toJson() const { - CHPLDEF_IMPOSSIBLE(); - return nullptr; -} - bool ChpldefInit::fromJson(const JsonValue& j, JsonPath p) { return true; } -JsonValue ChpldefInit::toJson() const { - CHPLDEF_IMPOSSIBLE(); - return nullptr; -} - bool ClientCapabilities::fromJson(const JsonValue& j, JsonPath p) { return true; } -JsonValue ClientCapabilities::toJson() const { - CHPLDEF_IMPOSSIBLE(); - return nullptr; -} - bool InitializeParams::fromJson(const JsonValue& j, JsonPath p) { JsonMapper m(j, p); if (!m) return false; @@ -87,21 +101,10 @@ bool InitializeParams::fromJson(const JsonValue& j, JsonPath p) { return true; } -JsonValue InitializeParams::toJson() const { - CHPLDEF_IMPOSSIBLE(); - return nullptr; -} - -bool InitializeResult::fromJson(const JsonValue& j, JsonPath p) { - CHPLDEF_IMPOSSIBLE(); - return true; -} - JsonValue InitializeResult::toJson() const { - JsonObject ret { - FIELD_(capabilities), - FIELD_(serverInfo) - }; + JsonObject ret; + FIELD_(ret, capabilities); + FIELD_(ret, serverInfo); return ret; } @@ -121,75 +124,207 @@ bool TraceLevel::fromJson(const JsonValue& j, JsonPath p) { return false; } -JsonValue TraceLevel::toJson() const { - CHPLDEF_IMPOSSIBLE(); - return nullptr; +bool WorkspaceFolder::fromJson(const JsonValue& j, JsonPath p) { + JsonMapper m(j, p); + return m && MAP_(m, uri) && MAP_(m, name); } -bool WorkspaceFolder::fromJson(const JsonValue& j, JsonPath p) { - CHPLDEF_TODO(); - return true; +JsonValue ServerInfo::toJson() const { + JsonObject ret; + FIELD_(ret, name); + FIELD_(ret, version); + return ret; } -JsonValue WorkspaceFolder::toJson() const { - CHPLDEF_IMPOSSIBLE(); - return nullptr; +JsonValue ServerCapabilities::toJson() const { + JsonObject ret; + FIELD_(ret, positionEncoding); + FIELD_(ret, textDocumentSync); + FIELD_(ret, notebookDocumentSync); + FIELD_(ret, completionProvider); + FIELD_(ret, hoverProvider); + FIELD_(ret, signatureHelpProvider); + FIELD_(ret, declarationProvider); + FIELD_(ret, definitionProvider); + FIELD_(ret, typeDefinitionProvider); + FIELD_(ret, implementationProvider); + FIELD_(ret, referencesProvider); + FIELD_(ret, documentHighlightProvider); + FIELD_(ret, documentSymbolProvider); + FIELD_(ret, codeActionProvider); + FIELD_(ret, codeLensProvider); + FIELD_(ret, documentLinkProvider); + FIELD_(ret, colorProvider); + FIELD_(ret, documentFormattingProvider); + FIELD_(ret, documentRangeFormattingProvider); + FIELD_(ret, documentOnTypeFormattingProvider); + FIELD_(ret, renameProvider); + FIELD_(ret, foldingRangeProvider); + FIELD_(ret, executeCommandProvider); + FIELD_(ret, selectionRangeProvider); + FIELD_(ret, linkEditingRangeProvider); + FIELD_(ret, callHierarchyProvider); + FIELD_(ret, semanticTokensProvider); + FIELD_(ret, monikerProvider); + FIELD_(ret, typeHierarchyProvider); + FIELD_(ret, inlineValueProvider); + FIELD_(ret, inlayHintProvider); + FIELD_(ret, diagnosticProvider); + FIELD_(ret, workspaceSymbolProvider); + FIELD_(ret, workspace); + FIELD_(ret, experimental); + return ret; } -bool ServerInfo::fromJson(const JsonValue& j, JsonPath p) { - CHPLDEF_IMPOSSIBLE(); - return true; +JsonValue TextDocumentSyncOptions::toJson() const { + JsonObject ret; + FIELD_(ret, openClose); + if (change) ret["change"] = static_cast(*change); + return ret; } -JsonValue ServerInfo::toJson() const { - JsonObject ret { FIELD_(name), FIELD_(version) }; +bool TextDocumentItem::fromJson(const JsonValue& j, JsonPath p) { + JsonMapper m(j, p); + return MAP_(m, uri) && MAP_(m, languageId) && MAP_(m, version) && + MAP_(m, text); +} + +bool DidOpenParams::fromJson(const JsonValue& j, JsonPath p) { + JsonMapper m(j, p); + return m && MAP_(m, textDocument); +} + +bool DidSaveParams::fromJson(const JsonValue& j, JsonPath p) { + JsonMapper m(j, p); + return m && MAP_(m, textDocument); +} + +bool DidCloseParams::fromJson(const JsonValue& j, JsonPath p) { + JsonMapper m(j, p); + return m && MAP_(m, textDocument); +} + +bool DidChangeParams::fromJson(const JsonValue& j, JsonPath p) { + JsonMapper m(j, p); + return m && MAP_(m, textDocument); +} + +JsonValue SaveOptions::toJson() const { + JsonObject ret; + FIELD_(ret, includeText); return ret; } -bool ServerCapabilities::fromJson(const JsonValue& j, JsonPath p) { - CHPLDEF_IMPOSSIBLE(); - return true; +bool TextDocumentIdentifier::fromJson(const JsonValue& j, JsonPath p) { + JsonMapper m(j, p); + return m && MAP_(m, uri); } -JsonValue ServerCapabilities::toJson() const { - JsonObject ret { - FIELD_(positionEncoding), - FIELD_(textDocumentSync), - FIELD_(notebookDocumentSync), - FIELD_(completionProvider), - FIELD_(hoverProvider), - FIELD_(signatureHelpProvider), - FIELD_(declarationProvider), - FIELD_(definitionProvider), - FIELD_(typeDefinitionProvider), - FIELD_(implementationProvider), - FIELD_(referencesProvider), - FIELD_(documentHighlightProvider), - FIELD_(documentSymbolProvider), - FIELD_(codeActionProvider), - FIELD_(codeLensProvider), - FIELD_(documentLinkProvider), - FIELD_(colorProvider), - FIELD_(documentFormattingProvider), - FIELD_(documentRangeFormattingProvider), - FIELD_(documentOnTypeFormattingProvider), - FIELD_(renameProvider), - FIELD_(foldingRangeProvider), - FIELD_(executeCommandProvider), - FIELD_(selectionRangeProvider), - FIELD_(linkEditingRangeProvider), - FIELD_(callHierarchyProvider), - FIELD_(semanticTokensProvider), - FIELD_(monikerProvider), - FIELD_(typeHierarchyProvider), - FIELD_(inlineValueProvider), - FIELD_(inlayHintProvider), - FIELD_(diagnosticProvider), - FIELD_(workspaceSymbolProvider), - FIELD_(workspace), - FIELD_(experimental) - }; +bool Position::fromJson(const JsonValue& j, JsonPath p) { + JsonMapper m(j, p); + return m && MAP_(m, line) && MAP_(m, character); +} + +JsonValue Position::toJson() const { + JsonObject ret; + FIELD_(ret, line); + FIELD_(ret, character); + return ret; +} + +bool Position::operator==(const Position& rhs) const { + return line == rhs.line && character == rhs.character; +} + +bool TextDocumentPositionParams::fromJson(const JsonValue& j, JsonPath p) { + JsonMapper m(j, p); + return m && MAP_(m, textDocument) && MAP_(m, position); +} + +Location::Location(const chpl::Location& loc) + : uri(loc.path().c_str()), + range(Range(loc)) {} + +bool Location::fromJson(const JsonValue& j, JsonPath p) { + JsonMapper m(j, p); + return m && MAP_(m, uri) && MAP_(m, range); +} + +JsonValue Location::toJson() const { + JsonObject ret; + FIELD_(ret, uri); + FIELD_(ret, range); return ret; } +bool Location::operator==(const Location& rhs) const { + return uri == rhs.uri && range == rhs.range; +} + +bool LocationLink::fromJson(const JsonValue& j, JsonPath p) { + JsonMapper m(j, p); + return m && MAP_(m, originSelectionRange) && + MAP_(m, targetUri) && + MAP_(m, targetRange) && + MAP_(m, targetSelectionRange); +} + +JsonValue LocationLink::toJson() const { + JsonObject ret; + FIELD_(ret, originSelectionRange); + FIELD_(ret, targetUri); + FIELD_(ret, targetRange); + FIELD_(ret, targetSelectionRange); + return ret; +} + +static Position constructPosition(int chapelLine, int chapelColumn) { + int line = chapelLine - 1; + int character = chapelColumn - 1; + CHPL_ASSERT(line >= 0 && character >= 0); + return Position(line, character); +} + +static Position constructPositionAtStart(const chpl::Location& loc) { + return constructPosition(loc.firstLine(), loc.firstColumn()); +} + +static Position constructPositionAtEnd(const chpl::Location& loc) { + return constructPosition(loc.lastLine(), loc.lastColumn()); +} + +Range::Range(const chpl::Location& loc) + : start(constructPositionAtStart(loc)), + end(constructPositionAtEnd(loc)) {} + +bool Range::fromJson(const JsonValue& j, JsonPath p) { + JsonMapper m(j, p); + return m && MAP_(m, start) && MAP_(m, end); +} + +JsonValue Range::toJson() const { + JsonObject ret; + FIELD_(ret, start); + FIELD_(ret, end); + return ret; +} + +bool Range::operator==(const Range& rhs) const { + return start == rhs.start && end == rhs.end; +} + +JsonValue DeclarationResult::toJson() const { + if (!result) return nullptr; + if (auto v = std::get_if(&*result)) return *v; + if (auto v = std::get_if>(&*result)) return *v; + return nullptr; +} + +JsonValue DefinitionResult::toJson() const { + if (!result) return nullptr; + if (auto v = std::get_if>(&*result)) return *v; + if (auto v = std::get_if>(&*result)) return *v; + return nullptr; +} + } // end namespace 'chpldef' diff --git a/tools/chpldef/protocol-types.h b/tools/chpldef/protocol-types.h index 1fe3f3b45711..bf58783bc8e3 100644 --- a/tools/chpldef/protocol-types.h +++ b/tools/chpldef/protocol-types.h @@ -21,18 +21,11 @@ #ifndef CHPL_TOOLS_CHPLDEF_PROTOCOL_TYPES_H #define CHPL_TOOLS_CHPLDEF_PROTOCOL_TYPES_H -#include "./Logger.h" -#include "./misc.h" -#include - -/** Bunch up some redundant overrides for protocol structs into a macro. */ -#define CHPLDEF_PROTOCOL_TYPE_OVERRIDES() \ - virtual bool fromJson(const JsonValue& j, JsonPath p) override; \ - virtual JsonValue toJson() const override; - -/** Use this to declare protocol types that are empty. */ -#define CHPLDEF_PROTOCOL_EMPTY_TYPE(name__) \ - struct name__ : EmptyProtocolType {} +#include "Logger.h" +#include "misc.h" +#include +#include +#include /** This header contains types which help form the Microsoft language server protocol. The types attempt to follow the specification as faithfully @@ -45,34 +38,57 @@ */ namespace chpldef { +/** Forward declare request params/result types, even if they may not all + be defined (e.g., a notification does not have a result). */ +#define CHPLDEF_MESSAGE(name__, x1__, x2__, x3__) \ + struct name__##Params; \ + struct name__##Result; +#include "./message-macro-list.h" +#undef CHPLDEF_MESSAGE + using OPT_TODO_TYPE = opt; -struct ProtocolType { - virtual bool fromJson(const JsonValue& j, JsonPath p) = 0; - virtual JsonValue toJson() const = 0; +struct BaseProtocolType { + virtual bool fromJson(const JsonValue& j, JsonPath p); + virtual JsonValue toJson() const; /** By default, convert to JSON and then print the JSON. */ virtual std::string toString() const; - virtual ~ProtocolType() = default; + virtual ~BaseProtocolType() = default; }; -struct EmptyProtocolType : ProtocolType { +struct EmptyProtocolType : BaseProtocolType { virtual bool fromJson(const JsonValue& j, JsonPath p) override; virtual JsonValue toJson() const override; virtual ~EmptyProtocolType() = default; }; -/** Information about the client. */ -struct ClientInfo : ProtocolType { - CHPLDEF_PROTOCOL_TYPE_OVERRIDES(); +struct ProtocolTypeSend : BaseProtocolType { + virtual JsonValue toJson() const override = 0; + virtual ~ProtocolTypeSend() = default; +}; +struct ProtocolTypeRecv : BaseProtocolType { + virtual bool fromJson(const JsonValue& j, JsonPath p) override = 0; + virtual ~ProtocolTypeRecv() = default; +}; + +struct ProtocolType : BaseProtocolType { + virtual bool fromJson(const JsonValue& j, JsonPath p) override = 0; + virtual JsonValue toJson() const override = 0; + virtual ~ProtocolType() = default; +}; + +struct ClientInfo : ProtocolTypeRecv { std::string name; opt version; + + virtual bool fromJson(const JsonValue& j, JsonPath p) override; }; /** TODO: Used to store 'chpldef' specific initialization options. */ -struct ChpldefInit : ProtocolType { - CHPLDEF_PROTOCOL_TYPE_OVERRIDES(); +struct ChpldefInit : ProtocolTypeRecv { + virtual bool fromJson(const JsonValue& j, JsonPath p) override; }; /** As defined by the spec, this structure is deeply nested and absolutely @@ -84,27 +100,25 @@ struct ChpldefInit : ProtocolType { TODO: If you add a field here, then adjust the (de)serializer methods. */ -struct ClientCapabilities : ProtocolType { - CHPLDEF_PROTOCOL_TYPE_OVERRIDES(); +struct ClientCapabilities : ProtocolTypeRecv { + virtual bool fromJson(const JsonValue& j, JsonPath p) override; }; -struct WorkspaceFolder : ProtocolType { - CHPLDEF_PROTOCOL_TYPE_OVERRIDES(); - +struct WorkspaceFolder : ProtocolTypeRecv { std::string uri; std::string name; -}; -struct TraceLevel : ProtocolType { - CHPLDEF_PROTOCOL_TYPE_OVERRIDES(); + virtual bool fromJson(const JsonValue& j, JsonPath p) override; +}; +struct TraceLevel : ProtocolTypeRecv { Logger::Level level; -}; -struct InitializeParams : ProtocolType { - CHPLDEF_PROTOCOL_TYPE_OVERRIDES(); + virtual bool fromJson(const JsonValue& j, JsonPath p) override; +}; - opt processId; +struct InitializeParams : ProtocolTypeRecv { + opt processId; opt clientInfo; opt locale; opt rootPath; /** Deprecated -> 'rootUri'. */ @@ -113,19 +127,36 @@ struct InitializeParams : ProtocolType { ClientCapabilities capabilities; opt trace; opt> workspaceFolders; + + virtual bool fromJson(const JsonValue& j, JsonPath p) override; }; -struct TextDocumentSyncOptions : ProtocolType { - CHPLDEF_PROTOCOL_TYPE_OVERRIDES(); +struct SaveOptions : ProtocolTypeSend { + opt includeText; + + virtual JsonValue toJson() const override; +}; + +struct TextDocumentSyncOptions : ProtocolTypeSend { + enum Change { + None = 0, + Full = 1, + Incremental = 2 + }; + opt openClose; + opt change; + opt willSave; + opt willSaveWaitUntil; + opt save; + + virtual JsonValue toJson() const override; }; /** Some of the 'provider' queries have more advanced types we can swap in to configure further -- see 'DeclarationRegistrationOptions'. */ -struct ServerCapabilities : ProtocolType { - CHPLDEF_PROTOCOL_TYPE_OVERRIDES(); - +struct ServerCapabilities : ProtocolTypeSend { opt positionEncoding; - OPT_TODO_TYPE textDocumentSync; + opt textDocumentSync; OPT_TODO_TYPE notebookDocumentSync; OPT_TODO_TYPE completionProvider; opt hoverProvider; @@ -159,41 +190,171 @@ struct ServerCapabilities : ProtocolType { opt workspaceSymbolProvider; OPT_TODO_TYPE workspace; OPT_TODO_TYPE experimental; -}; -struct ServerInfo : ProtocolType { - CHPLDEF_PROTOCOL_TYPE_OVERRIDES(); + virtual JsonValue toJson() const override; +}; +struct ServerInfo : ProtocolTypeSend { std::string name; opt version; -}; -struct InitializeResult : ProtocolType { - CHPLDEF_PROTOCOL_TYPE_OVERRIDES(); + virtual JsonValue toJson() const override; +}; +struct InitializeResult : ProtocolTypeSend { ServerCapabilities capabilities; opt serverInfo; + + virtual JsonValue toJson() const override; +}; + +struct InitializedParams : EmptyProtocolType {}; +struct ShutdownParams : EmptyProtocolType {}; +struct ShutdownResult : EmptyProtocolType {}; +struct ExitParams : EmptyProtocolType {}; + +struct TextDocumentItem : ProtocolTypeRecv { + std::string uri; + std::string languageId; + int64_t version = -1; + std::string text; + + TextDocumentItem() = default; + TextDocumentItem(std::string uri, std::string languageId, int64_t version, + std::string text) + : uri(std::move(uri)), + languageId(std::move(languageId)), + version(version), + text(std::move(text)) {} + virtual bool fromJson(const JsonValue& j, JsonPath p) override; }; -CHPLDEF_PROTOCOL_EMPTY_TYPE(InitializedParams); -CHPLDEF_PROTOCOL_EMPTY_TYPE(InitializedResult); +struct DidOpenParams : ProtocolTypeRecv { + TextDocumentItem textDocument; + + DidOpenParams() = default; + DidOpenParams(TextDocumentItem textDocument) + : textDocument(std::move(textDocument)) {} + virtual bool fromJson(const JsonValue& j, JsonPath p) override; +}; -CHPLDEF_PROTOCOL_EMPTY_TYPE(ShutdownParams); -CHPLDEF_PROTOCOL_EMPTY_TYPE(ShutdownResult); +struct DidChangeParams : ProtocolTypeRecv { + TextDocumentItem textDocument; -CHPLDEF_PROTOCOL_EMPTY_TYPE(ExitParams); -CHPLDEF_PROTOCOL_EMPTY_TYPE(ExitResult); + virtual bool fromJson(const JsonValue& j, JsonPath p) override; +}; + +struct DidSaveParams : ProtocolTypeRecv { + TextDocumentItem textDocument; + + virtual bool fromJson(const JsonValue& j, JsonPath p) override; +}; + +struct DidCloseParams : ProtocolTypeRecv { + TextDocumentItem textDocument; + + virtual bool fromJson(const JsonValue& j, JsonPath p) override; +}; + +struct TextDocumentIdentifier : ProtocolTypeRecv { + std::string uri; + + TextDocumentIdentifier() = default; + TextDocumentIdentifier(std::string uri) : uri(std::move(uri)) {} + virtual bool fromJson(const JsonValue& j, JsonPath p) override; +}; + +struct Position : ProtocolType { + uint64_t line = 0; /** Zero-based position. */ + uint64_t character = 0; /** Zero-based position. */ + + Position() = default; + Position(uint64_t line, uint64_t character) + : line(line), character(character) {} + virtual bool fromJson(const JsonValue& j, JsonPath p) override; + virtual JsonValue toJson() const override; + bool operator==(const Position& rhs) const; + bool operator!=(const Position& rhs) const; + bool operator<(const Position& rhs) const; + bool operator>(const Position& rhs) const; + bool operator<=(const Position& rhs) const; + bool operator>=(const Position& rhs) const; +}; + +struct Range : ProtocolType { + Position start; + Position end; + + Range() = default; + Range(Position start, Position end) + : start(std::move(start)), + end(std::move(end)) {} + Range(const chpl::Location& loc); + virtual bool fromJson(const JsonValue& j, JsonPath p) override; + virtual JsonValue toJson() const override; + bool operator==(const Range& rhs) const; +}; + +struct TextDocumentPositionParams : ProtocolTypeRecv { + TextDocumentIdentifier textDocument; + Position position; + + virtual bool fromJson(const JsonValue& j, JsonPath p) override; +}; + +struct Location : ProtocolType { + std::string uri; + Range range; + + Location(std::string uri, Range range) + : uri(std::move(uri)), + range(std::move(range)) {} + Location() = default; + Location(const chpl::Location& loc); + virtual bool fromJson(const JsonValue& j, JsonPath p) override; + virtual JsonValue toJson() const override; + bool operator==(const Location& rhs) const; + bool operator!=(const Location& rhs) const; +}; + +struct LocationLink : ProtocolType { + opt originSelectionRange; + std::string targetUri; + Range targetRange; + Range targetSelectionRange; + + virtual bool fromJson(const JsonValue& j, JsonPath p) override; + virtual JsonValue toJson() const override; +}; + +using LocationArray = std::vector; +using LocationLinkArray = std::vector; + +struct DeclarationParams : TextDocumentPositionParams {}; +struct DefinitionParams : TextDocumentPositionParams {}; + +struct DeclarationResult : ProtocolTypeSend { + opt> result; + + virtual JsonValue toJson() const override; +}; + +struct DefinitionResult : ProtocolTypeSend { + opt> result; + + virtual JsonValue toJson() const override; +}; /** Instantiate only if 'T' is derived from 'ProtocolType'. */ template -CHPLDEF_ENABLE_IF_DERIVED(T, ProtocolType, bool) +CHPLDEF_ENABLE_IF_DERIVED(T, BaseProtocolType, bool) fromJSON(const JsonValue& j, T& x, JsonPath p) { return x.fromJson(j, p); } /** Instantiate only if 'T' is derived from 'ProtocolType'. */ template -CHPLDEF_ENABLE_IF_DERIVED(T, ProtocolType, JsonValue) +CHPLDEF_ENABLE_IF_DERIVED(T, BaseProtocolType, JsonValue) toJSON(const T& x) { return x.toJson(); } diff --git a/tools/chpldef/test/CMakeLists.txt b/tools/chpldef/test/CMakeLists.txt new file mode 100644 index 000000000000..358fb09adf20 --- /dev/null +++ b/tools/chpldef/test/CMakeLists.txt @@ -0,0 +1,43 @@ +# Copyright 2021-2023 Hewlett Packard Enterprise Development LP +# Other additional copyright holders may be indicated within. +# +# The entirety of this work is 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. + +add_library(chpldef-test-utility OBJECT EXCLUDE_FROM_ALL + TestClient.cpp) +target_link_libraries(chpldef-test-utility chpldef-objects) +set_property(TARGET chpldef-test-utility PROPERTY CXX_STANDARD 17) +target_include_directories(chpldef-test-utility PRIVATE + ${CHPLDEF_INCLUDE_DIR}) + +add_custom_target(chpldef-tests) + +function(chpldef_compile_test target) + add_executable(${target} EXCLUDE_FROM_ALL ${target}.cpp) + set_target_properties(${target} PROPERTIES LINK_DEPENDS_NO_SHARED true + RUNTIME_OUTPUT_DIRECTORY . ) + target_link_libraries(${target} chpldef-objects chpldef-test-utility) + target_link_libraries(${target} chpldef-objects) + target_include_directories(${target} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} + ${CHPLDEF_INCLUDE_DIR}) + add_test(NAME ${target} COMMAND ${target}) + set_tests_properties(${target} PROPERTIES ENVIRONMENT + "CHPL_HOME=${CHPL_HOME}") + add_dependencies(chpldef-tests ${target}) + set_property(TARGET ${target} PROPERTY CXX_STANDARD 17) +endfunction(chpldef_compile_test) + +chpldef_compile_test(test-lifecycle) +chpldef_compile_test(test-declaration) diff --git a/tools/chpldef/test/TestClient.cpp b/tools/chpldef/test/TestClient.cpp new file mode 100644 index 000000000000..93bd6aede197 --- /dev/null +++ b/tools/chpldef/test/TestClient.cpp @@ -0,0 +1,249 @@ +/* + * Copyright 2020-2023 Hewlett Packard Enterprise Development LP + * Copyright 2004-2019 Cray Inc. + * Other additional copyright holders may be indicated within. + * + * The entirety of this work is 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. + */ + +#include "./TestClient.h" +#include + +void TestClient::breakpoint() {} + +Server TestClient::createServerInstance() { + Server::Configuration conf; + conf.transport = nullptr; + return { std::move(conf) }; +} + +chpl::Context TestClient::createChapelCompilerInstance() { + chpl::Context::Configuration conf; + return { std::move(conf) }; +} + +int64_t TestClient::bumpVersionForUri(const std::string& uri) { + auto it = uriToVersion_.find(uri); + if (it != uriToVersion_.end()) return it->second++; + uriToVersion_[uri] = 0; + return 0; +} + +JsonValue TestClient::createUniqueMessageId() { + return messageIdCounter_++; +} + +opt TestClient::sendInitialize() { + Initialize::Params p; + auto msg = Initialize::create(createUniqueMessageId(), std::move(p)); + msg->handle(ctx_); + if (auto r = msg->result()) return *r; + return {}; +} + +void TestClient::sendInitialized() { + Initialized::Params p; + auto msg = Initialized::create(createUniqueMessageId(), std::move(p)); + msg->handle(ctx_); +} + +void TestClient::sendExit() { + CHPLDEF_TODO(); +} + +void TestClient::sendShutdown() { + CHPLDEF_TODO(); +} + +void TestClient::advanceServerToReady() { + if (ctx_->state() == Server::UNINITIALIZED) { + std::ignore = sendInitialize(); + CHPL_ASSERT(ctx_->state() == Server::SETUP); + } + + if (ctx_->state() == Server::SETUP) { + sendInitialized(); + CHPL_ASSERT(ctx_->state() == Server::READY); + } +} + +void TestClient::sendDidOpen(const std::string& uri, + const std::string& text) { + auto ver = bumpVersionForUri(uri); + TextDocumentItem tdi { uri, "chapel", ver, text }; + DidOpen::Params p { std::move(tdi) }; + auto msg = DidOpen::create(nullptr, std::move(p)); + msg->handle(ctx_); +} + +static std::string mentionSymbol(const chpl::uast::AstNode* ast) { + if (auto x = ast->toIdentifier()) return x->name().c_str(); + if (auto x = ast->toDot()) return x->field().c_str(); + CHPLDEF_TODO(); + return {}; +} + +static Location +locationFromAst(chpl::Context* chapel, const chpl::uast::AstNode* ast) { + CHPL_ASSERT(ast); + auto loc = chpl::parsing::locateAst(chapel, ast); + return Location(std::move(loc)); +} + +static void +doCollectMentions(chpl::Context* chapel, + const chpl::uast::AstNode* ast, + std::vector& decls, + std::vector& mentions) { + if (auto decl = ast->toNamedDecl()) { + decls.push_back(decl); + } else if (ast->isIdentifier() || ast->isDot()) { + TestClient::Mention m; + m.tag = ast->tag(); + m.symbol = mentionSymbol(ast); + bool isBaseExpr = parentCallIfBaseExpression(chapel, ast) != nullptr; + m.isCallBaseExpression = isBaseExpr; + m.source = locationFromAst(chapel, ast); + mentions.push_back(std::move(m)); + } + + const bool canRecurse = !ast->isFunction(); + if (!canRecurse) return; + + for (auto child : ast->children()) { + doCollectMentions(chapel, child, decls, mentions); + } +} + +static void +doFixupMentionTargets(chpl::Context* chapel, + std::vector& decls, + std::vector& mentions) { + std::map> nameToDecls; + std::map functionMentionIdx; + + for (auto& nd : decls) { + auto name = nd->name().c_str(); + auto& v = nameToDecls[name]; + v.push_back(nd); + if (!nd->isFunction()) CHPL_ASSERT(v.size() == 1); + } + + for (size_t i = 0; i < mentions.size(); i++) { + auto& m = mentions[i]; + auto& name = m.symbol; + + // Get the declaration(s) with the name 'name'. + auto it = nameToDecls.find(name); + CHPL_ASSERT(it != nameToDecls.end()); + auto& v = it->second; + + const chpl::uast::NamedDecl* nd = nullptr; + + // Calls map linearly in order to simulate overloading. + if (m.isCallBaseExpression) { + auto& idx = functionMentionIdx[name]; + CHPL_ASSERT(v.size() >= 1); + CHPL_ASSERT(idx < v.size()); + nd = v[idx++]; + + // Everything else just maps by name. + } else { + CHPL_ASSERT(v.size() == 1); + nd = v[0]; + } + + CHPL_ASSERT(nd); + m.target = locationFromAst(chapel, nd); + } +} + +std::vector +TestClient::collectMentions(const std::string& uri, + const std::string& text) { + auto chapelContext = createChapelCompilerInstance(); + auto chapel = &chapelContext; + std::vector ret; + std::vector decls; + + // Set the file text and parse the file. + auto ustr = chpl::UniqueString::get(chapel, uri); + chpl::parsing::setFileText(chapel, ustr, text); + auto& br = parseFromUri(chapel, uri); + + for (auto ast : br.topLevelExpressions()) { + doCollectMentions(chapel, ast, decls, ret); + } + + // Fixing up the targets allows tests to write decls out of order. + doFixupMentionTargets(chapel, decls, ret); + + return ret; +} + +std::vector +TestClient::collectLineLengthsInSource(const std::string& text) { + std::vector ret; + uint64_t character = 0; + for (auto c : text) { + if (c == '\n') { + ret.push_back(character); + character = 0; + } else { + character += 1; + } + } + return ret; +} + +opt +TestClient::sendDeclaration(const std::string& uri, Position cursor) { + TextDocumentIdentifier tdi(uri); + Declaration::Params p; + p.textDocument = std::move(tdi); + p.position = std::move(cursor); + + // Create and handle the message directly rather than enqueue it for + // processing by the server main loop. + auto msg = Declaration::create(createUniqueMessageId(), std::move(p)); + msg->handle(ctx_); + + // TODO: This is absurd, we need to flatten 'DeclarationResult'. + if (auto r = msg->result()) { + if (auto& res = r->result) { + if (auto p = std::get_if(&(*res))) { + auto& arr = *p; + CHPL_ASSERT(arr.size() <= 1); + if (arr.size() == 1) return arr[0]; + return {}; + } + } + } + return {}; +} + +std::string TestClient::Mention::toString() const { + std::stringstream ss; + ss << chpl::uast::asttags::tagToString(tag) << " " << symbol << " ("; + ss << source.range.start.line << ":" << source.range.start.character; + ss << "-"; + ss << source.range.end.line << ":" << source.range.end.character; + ss << " -> "; + ss << target.range.start.line << ":" << target.range.start.character; + ss << "-"; + ss << target.range.end.line << ":" << target.range.end.character; + ss << ")"; + return ss.str(); +} diff --git a/tools/chpldef/test/TestClient.h b/tools/chpldef/test/TestClient.h new file mode 100644 index 000000000000..f30cdd535ca2 --- /dev/null +++ b/tools/chpldef/test/TestClient.h @@ -0,0 +1,117 @@ +/* + * Copyright 2020-2023 Hewlett Packard Enterprise Development LP + * Copyright 2004-2019 Cray Inc. + * Other additional copyright holders may be indicated within. + * + * The entirety of this work is 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. + */ + +#ifndef CHPL_TOOLS_CHPLDEF_TESTCLIENT_H +#define CHPL_TOOLS_CHPLDEF_TESTCLIENT_H + +#include "compiler-gadgets.h" +#include "events.h" +#include "Format.h" +#include "Logger.h" +#include "Message.h" +#include "misc.h" +#include "protocol-types.h" +#include "Server.h" +#include "Transport.h" +#include + +using namespace chpldef; + +/** This 'TestClient' simulates a LSP client, but issues direct calls to + server functionality rather than by sending JSON over some sort of + transport layer as a normal client would (also, usually the client + operates as a separate process - here for the sake of unit testing + the client and server operate in the same process). This client should + be used to test most language server queries. +*/ +class TestClient { + Server server_; + Server* ctx_ = &server_; + int64_t messageIdCounter_ = 0; + std::map uriToVersion_; + std::ostream& dbg_ = ctx_->transport() ? std::cerr : std::cout; + + static Server createServerInstance(); + static chpl::Context createChapelCompilerInstance(); + int64_t bumpVersionForUri(const std::string& uri); + +public: + TestClient() : server_(createServerInstance()), ctx_(&server_) {} + ~TestClient() = default; + + /** Get a handle to a stream suitable for printing debug infos. */ + inline std::ostream& dbg() { return dbg_; } + + /** For requests, prepare a new ID number embedded in JSON. */ + JsonValue createUniqueMessageId(); + + /** Send the 'Initialize' message to the server. */ + opt sendInitialize(); + + /** Send the 'Initialized' message to the server. */ + void sendInitialized(); + + /** Send the 'Shutdown' message to the server. */ + void sendShutdown(); + + /** Send the 'Exit' message to the server. */ + void sendExit(); + + /** Complete the initialization handshake. After this call the server will + be in the 'READY' state and can accept any message. */ + void advanceServerToReady(); + + /** Send a 'DidOpen' notification. */ + void sendDidOpen(const std::string& uri, const std::string& text); + + /** A mention represents a text range the client is interested in. */ + struct Mention { + using AstTag = chpl::uast::asttags::AstTag; + AstTag tag; + bool isCallBaseExpression = false; + std::string symbol; + Location source; + Location target; + + std::string toString() const; + }; + + /** Collect locations the client is interested in using a separate + instance of the Chapel compiler. The way mentions are mapped to + target locations depends on the target. Calls are mapped linearly + (where call N of 'foo' maps to overload N of 'foo') in order to + simulate overloading. Every other declaration is mapped by name. + */ + static std::vector + collectMentions(const std::string& uri, const std::string& text); + + /** Collect line lengths within the file using a separate instance + of the Chapel compiler. */ + static std::vector + collectLineLengthsInSource(const std::string& text); + + /** Send a 'Declaration' message. May return a 'Location'. */ + opt sendDeclaration(const std::string& uri, Position cursor); + + /** For testing/debugging purposes. */ + static void breakpoint(); +}; + +#endif diff --git a/tools/chpldef/test/test-declaration.cpp b/tools/chpldef/test/test-declaration.cpp new file mode 100644 index 000000000000..d63bf4be445a --- /dev/null +++ b/tools/chpldef/test/test-declaration.cpp @@ -0,0 +1,112 @@ +/* + * Copyright 2020-2023 Hewlett Packard Enterprise Development LP + * Copyright 2004-2019 Cray Inc. + * Other additional copyright holders may be indicated within. + * + * The entirety of this work is 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. + */ + +#include "./TestClient.h" + +static void testDeclaration(const std::string& uri, const std::string& text) { + TestClient client; + + client.advanceServerToReady(); + + /** Send 'DidOpen' to communicate text open in the editor. */ + client.sendDidOpen(uri, text); + + /** Collect locations we are interested in. */ + auto lineLengths = TestClient::collectLineLengthsInSource(text); + auto mentions = TestClient::collectMentions(uri, text); + + for (auto& m : mentions) { + auto& range = m.source.range; + auto& start = range.start; + auto& end = range.end; + + // TODO: Fuzz characters to the left and the right of the identifier. + if (m.tag == chpl::uast::asttags::Identifier) { + CHPL_ASSERT(start.line == end.line); + const auto line = start.line; + for (uint64_t c = start.character; c <= end.character; c++) { + Position cursor(line, c); + client.dbg() << m.toString(); + client.dbg() << " w/ cursor (" << cursor.line << ":"; + client.dbg() << cursor.character << ")" << std::endl; + + auto targetLoc = client.sendDeclaration(uri, cursor); + CHPL_ASSERT(targetLoc == m.target); + } + } else { + CHPLDEF_TODO(); + } + } +} + +static void test0(void) { + const auto uri = "test0.chpl"; + const auto text = R"""( + class C { + var f = 0; + } + + var x1 = 0; + var x2 = 0.0; + var x3 = new C(); + + x1; + x2; + // x3.f; TODO: Dot targets. // BUG: Dot hugs right. + + x1 = 8; + x2 = x1 * 2; // BUG: Assigned expression not considered. + + proc foo() {} + proc bar(a: owned C?, b: int=0) { a; b; } + + foo(); + bar(x3, x1); + )"""; + + testDeclaration(uri, text); +} + +static void test1(void) { + const auto uri = "test1.chpl"; + const auto text = R"""( + class C {} + + proc foo() {} + proc foo(a: int) {} // BUG: Formal not considered... + proc foo(b: real, c: owned C?) {} + proc foo(d: owned C?, e: int=0) {} + + // TODO: How to map calls to procedures with default values? + // Short of using the resolver itself... + foo(); + foo(8); + foo(16.0, new C()); + foo(new C()); + )"""; + + testDeclaration(uri, text); +} + +int main(int argc, char** argv) { + test0(); + test1(); + return 0; +} diff --git a/tools/chpldef/test/test-lifecycle.cpp b/tools/chpldef/test/test-lifecycle.cpp new file mode 100644 index 000000000000..d7609e50675a --- /dev/null +++ b/tools/chpldef/test/test-lifecycle.cpp @@ -0,0 +1,23 @@ +/* + * Copyright 2020-2023 Hewlett Packard Enterprise Development LP + * Copyright 2004-2019 Cray Inc. + * Other additional copyright holders may be indicated within. + * + * The entirety of this work is 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. + */ + +int main(int argc, char** argv) { + return 0; +} diff --git a/util/cron/common-arkouda.bash b/util/cron/common-arkouda.bash index 68c04e5ad903..b4f9622cee60 100644 --- a/util/cron/common-arkouda.bash +++ b/util/cron/common-arkouda.bash @@ -25,6 +25,10 @@ fi export CHPL_NIGHTLY_TEST_DIRS=studies/arkouda/ export CHPL_TEST_ARKOUDA=true +# Removing regex.compile caused smoke test failures. As a stopgap measure we are +# skipping dependency check. +export ARKOUDA_SKIP_CHECK_DEPS=1 + ARKOUDA_DEP_DIR=$COMMON_DIR/arkouda-deps if [ -d "$ARKOUDA_DEP_DIR" ]; then export ARKOUDA_ARROW_PATH=${ARKOUDA_ARROW_PATH:-$ARKOUDA_DEP_DIR/arrow-install}