parent
4594a63634
commit
0c7f4a4a56
|
@ -4,8 +4,6 @@ on:
|
|||
push:
|
||||
branches:
|
||||
- master
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
Build:
|
||||
|
@ -17,13 +15,20 @@ jobs:
|
|||
dc:
|
||||
- ldc-latest
|
||||
- dmd-latest
|
||||
build: [debug, release]
|
||||
arch:
|
||||
- x86_64
|
||||
libdparse-version: [min, max]
|
||||
include:
|
||||
# windows x86
|
||||
- os: windows-latest
|
||||
arch: x86
|
||||
dc: ldc-latest
|
||||
build: debug
|
||||
libdparse-version: min
|
||||
# old compiler tests
|
||||
- { os: ubuntu-latest, dc: dmd-2.095.1, libdparse-version: min, build: debug, arch: x86_64 }
|
||||
- { os: ubuntu-latest, dc: ldc-1.25.0, libdparse-version: min, build: debug, arch: x86_64 }
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
@ -39,11 +44,27 @@ jobs:
|
|||
|
||||
- name: Build
|
||||
run: |
|
||||
dub build --build=release --config=client --arch=${{ matrix.arch }}
|
||||
dub build --build=release --config=server --arch=${{ matrix.arch }}
|
||||
rdmd ./d-test-utils/test_with_package.d $LIBDPARSE_VERSION libdparse -- dub build --build=${{ matrix.build }} --config=client --arch=${{ matrix.arch }}
|
||||
rdmd ./d-test-utils/test_with_package.d $LIBDPARSE_VERSION libdparse -- dub build --build=${{ matrix.build }} --config=server --arch=${{ matrix.arch }}
|
||||
|
||||
# Tests
|
||||
|
||||
- name: Build DSymbol
|
||||
env:
|
||||
DC: ${{matrix.dc}}
|
||||
LIBDPARSE_VERSION: ${{ matrix.libdparse-version }}
|
||||
run: |
|
||||
cd dsymbol
|
||||
rdmd ../d-test-utils/test_with_package.d $LIBDPARSE_VERSION libdparse -- dub build --build=${{ matrix.build }}
|
||||
|
||||
- name: Test DSymbol
|
||||
env:
|
||||
DC: ${{matrix.dc}}
|
||||
LIBDPARSE_VERSION: ${{ matrix.libdparse-version }}
|
||||
run: |
|
||||
cd dsymbol
|
||||
rdmd ../d-test-utils/test_with_package.d $LIBDPARSE_VERSION libdparse -- dub test
|
||||
|
||||
- name: Linux Tests
|
||||
if: contains(matrix.os, 'ubuntu')
|
||||
run: |
|
||||
|
@ -58,54 +79,3 @@ jobs:
|
|||
working-directory: tests
|
||||
shell: bash
|
||||
continue-on-error: true
|
||||
|
||||
|
||||
# Package Release
|
||||
|
||||
- name: Package the artificats
|
||||
if: github.event_name == 'release' && contains(matrix.dc, 'ldc')
|
||||
shell: pwsh
|
||||
working-directory: bin
|
||||
run: |
|
||||
if ("${{ matrix.os }}" -like 'windows*') {
|
||||
7z a -tzip ..\dcd.zip dcd-client.exe dcd-server.exe
|
||||
} elseif ("${{ matrix.os }}" -like 'macos*') {
|
||||
gtar -cvzf ../dcd.tar.gz dcd-client dcd-server
|
||||
} else {
|
||||
tar -cvzf ../dcd.tar.gz dcd-client dcd-server
|
||||
}
|
||||
|
||||
# Release
|
||||
|
||||
- name: Release Linux
|
||||
if: github.event_name == 'release' && contains(matrix.os, 'ubuntu') && contains(matrix.dc, 'ldc')
|
||||
uses: WebFreak001/upload-asset@v1.0.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
OS: linux
|
||||
with:
|
||||
file: dcd.tar.gz
|
||||
name: dcd-${TAG_RAW}-${OS}-${{ matrix.arch }}.tar.gz
|
||||
mime: application/tar+gzip
|
||||
|
||||
- name: Release Macos
|
||||
if: github.event_name == 'release' && contains(matrix.os, 'macos') && contains(matrix.dc, 'ldc')
|
||||
uses: WebFreak001/upload-asset@v1.0.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
OS: osx
|
||||
with:
|
||||
file: dcd.tar.gz
|
||||
name: dcd-${TAG_RAW}-${OS}-${{ matrix.arch }}.tar.gz
|
||||
mime: application/tar+gzip
|
||||
|
||||
- name: Release Windows
|
||||
if: github.event_name == 'release' && contains(matrix.os, 'windows') && contains(matrix.dc, 'ldc')
|
||||
uses: WebFreak001/upload-asset@v1.0.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
OS: windows
|
||||
with:
|
||||
file: dcd.zip
|
||||
name: dcd-${TAG_RAW}-${OS}-${{ matrix.arch }}.zip
|
||||
mime: application/zip
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
name: Publish Releases
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
Build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
dc:
|
||||
- ldc-latest
|
||||
arch:
|
||||
- x86_64
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup D
|
||||
uses: dlang-community/setup-dlang@v1
|
||||
with:
|
||||
compiler: ${{ matrix.dc }}
|
||||
|
||||
# Build
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
dub build --build=release --config=client --arch=${{ matrix.arch }}
|
||||
dub build --build=release --config=server --arch=${{ matrix.arch }}
|
||||
|
||||
# Package Release
|
||||
|
||||
- name: Package the artificats
|
||||
shell: pwsh
|
||||
working-directory: bin
|
||||
run: |
|
||||
if ("${{ matrix.os }}" -like 'windows*') {
|
||||
7z a -tzip ..\dcd.zip dcd-client.exe dcd-server.exe
|
||||
} elseif ("${{ matrix.os }}" -like 'macos*') {
|
||||
gtar -cvzf ../dcd.tar.gz dcd-client dcd-server
|
||||
} else {
|
||||
tar -cvzf ../dcd.tar.gz dcd-client dcd-server
|
||||
}
|
||||
|
||||
# Release
|
||||
|
||||
- name: Release Linux
|
||||
if: contains(matrix.os, 'ubuntu')
|
||||
uses: WebFreak001/upload-asset@v1.0.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
OS: linux
|
||||
with:
|
||||
file: dcd.tar.gz
|
||||
name: dcd-${TAG_RAW}-${OS}-${{ matrix.arch }}.tar.gz
|
||||
mime: application/tar+gzip
|
||||
|
||||
- name: Release Macos
|
||||
if: contains(matrix.os, 'macos')
|
||||
uses: WebFreak001/upload-asset@v1.0.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
OS: osx
|
||||
with:
|
||||
file: dcd.tar.gz
|
||||
name: dcd-${TAG_RAW}-${OS}-${{ matrix.arch }}.tar.gz
|
||||
mime: application/tar+gzip
|
||||
|
||||
- name: Release Windows
|
||||
if: contains(matrix.os, 'windows')
|
||||
uses: WebFreak001/upload-asset@v1.0.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
OS: windows
|
||||
with:
|
||||
file: dcd.zip
|
||||
name: dcd-${TAG_RAW}-${OS}-${{ matrix.arch }}.zip
|
||||
mime: application/zip
|
|
@ -8,9 +8,6 @@
|
|||
path = libdparse
|
||||
url = https://github.com/dlang-community/libdparse.git
|
||||
branch = master
|
||||
[submodule "dsymbol"]
|
||||
path = dsymbol
|
||||
url = https://github.com/dlang-community/dsymbol.git
|
||||
[submodule "stdx-allocator"]
|
||||
path = stdx-allocator
|
||||
url = https://github.com/dlang-community/stdx-allocator
|
||||
|
|
1
dsymbol
1
dsymbol
|
@ -1 +0,0 @@
|
|||
Subproject commit 46873f01f74844ab0bea0af14643cdd791573505
|
|
@ -0,0 +1,9 @@
|
|||
root = true
|
||||
|
||||
[*.d]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = true
|
|
@ -0,0 +1,32 @@
|
|||
# Compiled Object files
|
||||
*.o
|
||||
*.obj
|
||||
|
||||
# Compiled Dynamic libraries
|
||||
*.so
|
||||
*.dylib
|
||||
*.dll
|
||||
|
||||
# Compiled Static libraries
|
||||
*.a
|
||||
*.lib
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
|
||||
# DUB
|
||||
.dub/
|
||||
docs/
|
||||
docs.json
|
||||
__dummy.html
|
||||
|
||||
# Code coverage
|
||||
*.lst
|
||||
|
||||
# Others
|
||||
build/
|
||||
*.~*
|
||||
*.*~
|
||||
dub.selections.json
|
||||
trace.def
|
||||
.vscode/
|
|
@ -0,0 +1,23 @@
|
|||
Boost Software License - Version 1.0 - August 17th, 2003
|
||||
|
||||
Permission is hereby granted, free of charge, to any person or organization
|
||||
obtaining a copy of the software and accompanying documentation covered by
|
||||
this license (the "Software") to use, reproduce, display, distribute,
|
||||
execute, and transmit the Software, and to prepare derivative works of the
|
||||
Software, and to permit third-parties to whom the Software is furnished to
|
||||
do so, all subject to the following:
|
||||
|
||||
The copyright notices in the Software and this entire statement, including
|
||||
the above license grant, this restriction and the following disclaimer,
|
||||
must be included in all copies of the Software, in whole or in part, and
|
||||
all derivative works of the Software, unless such copies or derivative
|
||||
works are solely in the form of machine-executable object code generated by
|
||||
a source language processor.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
||||
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
||||
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,3 @@
|
|||
# dsymbol
|
||||
|
||||
Symbol lookup support for [libdparse](https://github.com/dlang-community/libdparse)
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"name": "dsymbol",
|
||||
"description": "Symbol lookup support for libdparse",
|
||||
"license": "BSL-1.0",
|
||||
"authors": ["Brian Schott"],
|
||||
"targetPath": "build",
|
||||
"targetType": "library",
|
||||
"dependencies": {
|
||||
"libdparse": ">=0.20.0 <0.21.0",
|
||||
"emsi_containers": "~>0.9.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
project('dsymbol', 'd',
|
||||
meson_version: '>=0.44',
|
||||
license: 'BSL-1.0',
|
||||
version: '0.4.8'
|
||||
)
|
||||
|
||||
project_soversion = '0'
|
||||
|
||||
pkgc = import('pkgconfig')
|
||||
dparse_dep = dependency('dparse', version: '>= 0.9.0', fallback: ['dparse', 'dparse_dep'])
|
||||
dcontainers_dep = dependency('dcontainers', version: '>= 0.8.0', fallback: ['dcontainers', 'dcontainers_dep'])
|
||||
|
||||
#
|
||||
# Sources
|
||||
#
|
||||
dsymbol_src = [
|
||||
'src/dsymbol/builtin/names.d',
|
||||
'src/dsymbol/builtin/symbols.d',
|
||||
'src/dsymbol/cache_entry.d',
|
||||
'src/dsymbol/conversion/first.d',
|
||||
'src/dsymbol/conversion/package.d',
|
||||
'src/dsymbol/conversion/second.d',
|
||||
'src/dsymbol/deferred.d',
|
||||
'src/dsymbol/import_.d',
|
||||
'src/dsymbol/modulecache.d',
|
||||
'src/dsymbol/scope_.d',
|
||||
'src/dsymbol/semantic.d',
|
||||
'src/dsymbol/string_interning.d',
|
||||
'src/dsymbol/symbol.d',
|
||||
'src/dsymbol/tests.d',
|
||||
'src/dsymbol/type_lookup.d',
|
||||
]
|
||||
|
||||
src_dir = include_directories('src/')
|
||||
|
||||
#
|
||||
# Targets
|
||||
#
|
||||
dsymbol_lib = library('dsymbol',
|
||||
[dsymbol_src],
|
||||
include_directories: [src_dir],
|
||||
install: true,
|
||||
version: meson.project_version(),
|
||||
soversion: project_soversion,
|
||||
dependencies: [dparse_dep, dcontainers_dep]
|
||||
)
|
||||
|
||||
pkgc.generate(name: 'dsymbol',
|
||||
libraries: [dsymbol_lib],
|
||||
subdirs: 'd/dsymbol',
|
||||
requires: ['dparse', 'dcontainers'],
|
||||
version: meson.project_version(),
|
||||
description: 'Library for lexing and parsing D source code.'
|
||||
)
|
||||
|
||||
# for use by others which embed this as subproject
|
||||
dsymbol_dep = declare_dependency(
|
||||
link_with: [dsymbol_lib],
|
||||
include_directories: [src_dir],
|
||||
dependencies: [dparse_dep, dcontainers_dep]
|
||||
)
|
||||
|
||||
#
|
||||
# Tests
|
||||
#
|
||||
if meson.get_compiler('d').get_id() == 'llvm'
|
||||
extra_args = ['-main', '-link-defaultlib-shared']
|
||||
else
|
||||
extra_args = ['-main']
|
||||
endif
|
||||
|
||||
dsymbol_test_exe = executable('test_dsymbol',
|
||||
[dsymbol_src],
|
||||
include_directories: [src_dir],
|
||||
dependencies: [dparse_dep, dcontainers_dep],
|
||||
d_unittest: true,
|
||||
link_args: extra_args
|
||||
)
|
||||
test('test_dsymbol', dsymbol_test_exe)
|
||||
|
||||
#
|
||||
# Install
|
||||
#
|
||||
install_subdir('src/dsymbol/', install_dir: 'include/d/dsymbol/')
|
|
@ -0,0 +1,208 @@
|
|||
module dsymbol.builtin.names;
|
||||
|
||||
import dparse.lexer;
|
||||
import dsymbol.string_interning;
|
||||
|
||||
package istring[24] builtinTypeNames;
|
||||
|
||||
/// Constants for buit-in or dummy symbol names
|
||||
istring FUNCTION_SYMBOL_NAME;
|
||||
/// ditto
|
||||
istring FUNCTION_LITERAL_SYMBOL_NAME;
|
||||
/// ditto
|
||||
istring IMPORT_SYMBOL_NAME;
|
||||
/// ditto
|
||||
istring ARRAY_SYMBOL_NAME;
|
||||
/// ditto
|
||||
istring ARRAY_LITERAL_SYMBOL_NAME;
|
||||
/// ditto
|
||||
istring ASSOC_ARRAY_SYMBOL_NAME;
|
||||
/// ditto
|
||||
istring POINTER_SYMBOL_NAME;
|
||||
/// ditto
|
||||
istring PARAMETERS_SYMBOL_NAME;
|
||||
/// ditto
|
||||
istring WITH_SYMBOL_NAME;
|
||||
/// ditto
|
||||
istring CONSTRUCTOR_SYMBOL_NAME;
|
||||
/// ditto
|
||||
istring DESTRUCTOR_SYMBOL_NAME;
|
||||
/// ditto
|
||||
istring ARGPTR_SYMBOL_NAME;
|
||||
/// ditto
|
||||
istring ARGUMENTS_SYMBOL_NAME;
|
||||
/// ditto
|
||||
istring THIS_SYMBOL_NAME;
|
||||
/// ditto
|
||||
istring SUPER_SYMBOL_NAME;
|
||||
/// ditto
|
||||
istring UNITTEST_SYMBOL_NAME;
|
||||
/// ditto
|
||||
istring DOUBLE_LITERAL_SYMBOL_NAME;
|
||||
/// ditto
|
||||
istring FLOAT_LITERAL_SYMBOL_NAME;
|
||||
/// ditto
|
||||
istring IDOUBLE_LITERAL_SYMBOL_NAME;
|
||||
/// ditto
|
||||
istring IFLOAT_LITERAL_SYMBOL_NAME;
|
||||
/// ditto
|
||||
istring INT_LITERAL_SYMBOL_NAME;
|
||||
/// ditto
|
||||
istring LONG_LITERAL_SYMBOL_NAME;
|
||||
/// ditto
|
||||
istring REAL_LITERAL_SYMBOL_NAME;
|
||||
/// ditto
|
||||
istring IREAL_LITERAL_SYMBOL_NAME;
|
||||
/// ditto
|
||||
istring UINT_LITERAL_SYMBOL_NAME;
|
||||
/// ditto
|
||||
istring ULONG_LITERAL_SYMBOL_NAME;
|
||||
/// ditto
|
||||
istring CHAR_LITERAL_SYMBOL_NAME;
|
||||
/// ditto
|
||||
istring DSTRING_LITERAL_SYMBOL_NAME;
|
||||
/// ditto
|
||||
istring STRING_LITERAL_SYMBOL_NAME;
|
||||
/// ditto
|
||||
istring WSTRING_LITERAL_SYMBOL_NAME;
|
||||
/// ditto
|
||||
istring BOOL_VALUE_SYMBOL_NAME;
|
||||
/// ditto
|
||||
istring VOID_SYMBOL_NAME;
|
||||
|
||||
/**
|
||||
* Translates the IDs for built-in types into an interned string.
|
||||
*/
|
||||
istring getBuiltinTypeName(IdType id) nothrow @nogc @safe
|
||||
{
|
||||
switch (id)
|
||||
{
|
||||
case tok!"int": return builtinTypeNames[0];
|
||||
case tok!"uint": return builtinTypeNames[1];
|
||||
case tok!"double": return builtinTypeNames[2];
|
||||
case tok!"idouble": return builtinTypeNames[3];
|
||||
case tok!"float": return builtinTypeNames[4];
|
||||
case tok!"ifloat": return builtinTypeNames[5];
|
||||
case tok!"short": return builtinTypeNames[6];
|
||||
case tok!"ushort": return builtinTypeNames[7];
|
||||
case tok!"long": return builtinTypeNames[8];
|
||||
case tok!"ulong": return builtinTypeNames[9];
|
||||
case tok!"char": return builtinTypeNames[10];
|
||||
case tok!"wchar": return builtinTypeNames[11];
|
||||
case tok!"dchar": return builtinTypeNames[12];
|
||||
case tok!"bool": return builtinTypeNames[13];
|
||||
case tok!"void": return builtinTypeNames[14];
|
||||
case tok!"cent": return builtinTypeNames[15];
|
||||
case tok!"ucent": return builtinTypeNames[16];
|
||||
case tok!"real": return builtinTypeNames[17];
|
||||
case tok!"ireal": return builtinTypeNames[18];
|
||||
case tok!"byte": return builtinTypeNames[19];
|
||||
case tok!"ubyte": return builtinTypeNames[20];
|
||||
case tok!"cdouble": return builtinTypeNames[21];
|
||||
case tok!"cfloat": return builtinTypeNames[22];
|
||||
case tok!"creal": return builtinTypeNames[23];
|
||||
default: assert (false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initializes builtin types and the various properties of builtin types
|
||||
*/
|
||||
static this()
|
||||
{
|
||||
builtinTypeNames[0] = internString("int");
|
||||
builtinTypeNames[1] = internString("uint");
|
||||
builtinTypeNames[2] = internString("double");
|
||||
builtinTypeNames[3] = internString("idouble");
|
||||
builtinTypeNames[4] = internString("float");
|
||||
builtinTypeNames[5] = internString("ifloat");
|
||||
builtinTypeNames[6] = internString("short");
|
||||
builtinTypeNames[7] = internString("ushort");
|
||||
builtinTypeNames[8] = internString("long");
|
||||
builtinTypeNames[9] = internString("ulong");
|
||||
builtinTypeNames[10] = internString("char");
|
||||
builtinTypeNames[11] = internString("wchar");
|
||||
builtinTypeNames[12] = internString("dchar");
|
||||
builtinTypeNames[13] = internString("bool");
|
||||
builtinTypeNames[14] = internString("void");
|
||||
builtinTypeNames[15] = internString("cent");
|
||||
builtinTypeNames[16] = internString("ucent");
|
||||
builtinTypeNames[17] = internString("real");
|
||||
builtinTypeNames[18] = internString("ireal");
|
||||
builtinTypeNames[19] = internString("byte");
|
||||
builtinTypeNames[20] = internString("ubyte");
|
||||
builtinTypeNames[21] = internString("cdouble");
|
||||
builtinTypeNames[22] = internString("cfloat");
|
||||
builtinTypeNames[23] = internString("creal");
|
||||
|
||||
FUNCTION_SYMBOL_NAME = internString("function");
|
||||
FUNCTION_LITERAL_SYMBOL_NAME = internString("*function-literal*");
|
||||
IMPORT_SYMBOL_NAME = internString("import");
|
||||
ARRAY_SYMBOL_NAME = internString("*arr*");
|
||||
ARRAY_LITERAL_SYMBOL_NAME = internString("*arr-literal*");
|
||||
ASSOC_ARRAY_SYMBOL_NAME = internString("*aa*");
|
||||
POINTER_SYMBOL_NAME = internString("*");
|
||||
PARAMETERS_SYMBOL_NAME = internString("*parameters*");
|
||||
WITH_SYMBOL_NAME = internString("with");
|
||||
CONSTRUCTOR_SYMBOL_NAME = internString("*constructor*");
|
||||
DESTRUCTOR_SYMBOL_NAME = internString("~this");
|
||||
ARGPTR_SYMBOL_NAME = internString("_argptr");
|
||||
ARGUMENTS_SYMBOL_NAME = internString("_arguments");
|
||||
THIS_SYMBOL_NAME = internString("this");
|
||||
SUPER_SYMBOL_NAME = internString("super");
|
||||
UNITTEST_SYMBOL_NAME = internString("*unittest*");
|
||||
DOUBLE_LITERAL_SYMBOL_NAME = internString("*double");
|
||||
FLOAT_LITERAL_SYMBOL_NAME = internString("*float");
|
||||
IDOUBLE_LITERAL_SYMBOL_NAME = internString("*idouble");
|
||||
IFLOAT_LITERAL_SYMBOL_NAME = internString("*ifloat");
|
||||
INT_LITERAL_SYMBOL_NAME = internString("*int");
|
||||
LONG_LITERAL_SYMBOL_NAME = internString("*long");
|
||||
REAL_LITERAL_SYMBOL_NAME = internString("*real");
|
||||
IREAL_LITERAL_SYMBOL_NAME = internString("*ireal");
|
||||
UINT_LITERAL_SYMBOL_NAME = internString("*uint");
|
||||
ULONG_LITERAL_SYMBOL_NAME = internString("*ulong");
|
||||
CHAR_LITERAL_SYMBOL_NAME = internString("*char");
|
||||
DSTRING_LITERAL_SYMBOL_NAME = internString("*dstring");
|
||||
STRING_LITERAL_SYMBOL_NAME = internString("*string");
|
||||
WSTRING_LITERAL_SYMBOL_NAME = internString("*wstring");
|
||||
BOOL_VALUE_SYMBOL_NAME = internString("*bool");
|
||||
VOID_SYMBOL_NAME = internString("*void");
|
||||
}
|
||||
|
||||
istring symbolNameToTypeName(istring name)
|
||||
{
|
||||
if (name == DOUBLE_LITERAL_SYMBOL_NAME)
|
||||
return builtinTypeNames[2];
|
||||
if (name == FLOAT_LITERAL_SYMBOL_NAME)
|
||||
return builtinTypeNames[4];
|
||||
if (name == IDOUBLE_LITERAL_SYMBOL_NAME)
|
||||
return builtinTypeNames[3];
|
||||
if (name == IFLOAT_LITERAL_SYMBOL_NAME)
|
||||
return builtinTypeNames[5];
|
||||
if (name == INT_LITERAL_SYMBOL_NAME)
|
||||
return builtinTypeNames[0];
|
||||
if (name == LONG_LITERAL_SYMBOL_NAME)
|
||||
return builtinTypeNames[8];
|
||||
if (name == REAL_LITERAL_SYMBOL_NAME)
|
||||
return builtinTypeNames[17];
|
||||
if (name == IREAL_LITERAL_SYMBOL_NAME)
|
||||
return builtinTypeNames[18];
|
||||
if (name == UINT_LITERAL_SYMBOL_NAME)
|
||||
return builtinTypeNames[1];
|
||||
if (name == ULONG_LITERAL_SYMBOL_NAME)
|
||||
return builtinTypeNames[9];
|
||||
if (name == CHAR_LITERAL_SYMBOL_NAME)
|
||||
return builtinTypeNames[10];
|
||||
if (name == DSTRING_LITERAL_SYMBOL_NAME)
|
||||
return istring("dstring");
|
||||
if (name == STRING_LITERAL_SYMBOL_NAME)
|
||||
return istring("string");
|
||||
if (name == WSTRING_LITERAL_SYMBOL_NAME)
|
||||
return istring("wstring");
|
||||
if (name == BOOL_VALUE_SYMBOL_NAME)
|
||||
return istring("bool");
|
||||
if (name == VOID_SYMBOL_NAME)
|
||||
return istring("void");
|
||||
return name;
|
||||
}
|
|
@ -0,0 +1,310 @@
|
|||
module dsymbol.builtin.symbols;
|
||||
|
||||
import containers.hashset;
|
||||
import containers.ttree;
|
||||
import dparse.rollback_allocator;
|
||||
import dsymbol.builtin.names;
|
||||
import dsymbol.string_interning;
|
||||
import dsymbol.symbol;
|
||||
import std.experimental.allocator.mallocator : Mallocator;
|
||||
|
||||
alias SymbolsAllocator = Mallocator;
|
||||
|
||||
/**
|
||||
* Symbols for the built in types
|
||||
*/
|
||||
TTree!(DSymbol*, SymbolsAllocator, true, "a < b") builtinSymbols;
|
||||
|
||||
/**
|
||||
* Array properties
|
||||
*/
|
||||
TTree!(DSymbol*, SymbolsAllocator, true, "a < b") arraySymbols;
|
||||
|
||||
/**
|
||||
* Associative array properties
|
||||
*/
|
||||
TTree!(DSymbol*, SymbolsAllocator, true, "a < b") assocArraySymbols;
|
||||
|
||||
/**
|
||||
* Struct, enum, union, class, and interface properties
|
||||
*/
|
||||
TTree!(DSymbol*, SymbolsAllocator, true, "a < b") aggregateSymbols;
|
||||
|
||||
/**
|
||||
* Class properties
|
||||
*/
|
||||
TTree!(DSymbol*, SymbolsAllocator, true, "a < b") classSymbols;
|
||||
|
||||
/**
|
||||
* Enum properties
|
||||
*/
|
||||
TTree!(DSymbol*, SymbolsAllocator, true, "a < b") enumSymbols;
|
||||
|
||||
/**
|
||||
* Variadic template parameters properties
|
||||
*/
|
||||
DSymbol* variadicTmpParamSymbol;
|
||||
|
||||
/**
|
||||
* Type template parameters properties (when no colon constraint)
|
||||
*/
|
||||
DSymbol* typeTmpParamSymbol;
|
||||
|
||||
static this()
|
||||
{
|
||||
auto bool_ = makeSymbol(builtinTypeNames[13], CompletionKind.keyword);
|
||||
auto int_ = makeSymbol(builtinTypeNames[0], CompletionKind.keyword);
|
||||
auto long_ = makeSymbol(builtinTypeNames[8], CompletionKind.keyword);
|
||||
auto byte_ = makeSymbol(builtinTypeNames[19], CompletionKind.keyword);
|
||||
auto char_ = makeSymbol(builtinTypeNames[10], CompletionKind.keyword);
|
||||
auto dchar_ = makeSymbol(builtinTypeNames[12], CompletionKind.keyword);
|
||||
auto short_ = makeSymbol(builtinTypeNames[6], CompletionKind.keyword);
|
||||
auto ubyte_ = makeSymbol(builtinTypeNames[20], CompletionKind.keyword);
|
||||
auto uint_ = makeSymbol(builtinTypeNames[1], CompletionKind.keyword);
|
||||
auto ulong_ = makeSymbol(builtinTypeNames[9], CompletionKind.keyword);
|
||||
auto ushort_ = makeSymbol(builtinTypeNames[7], CompletionKind.keyword);
|
||||
auto wchar_ = makeSymbol(builtinTypeNames[11], CompletionKind.keyword);
|
||||
|
||||
auto alignof_ = makeSymbol("alignof", CompletionKind.keyword);
|
||||
auto mangleof_ = makeSymbol("mangleof", CompletionKind.keyword);
|
||||
auto sizeof_ = makeSymbol("sizeof", CompletionKind.keyword);
|
||||
auto stringof_ = makeSymbol("stringof", CompletionKind.keyword);
|
||||
auto init = makeSymbol("init", CompletionKind.keyword);
|
||||
auto min = makeSymbol("min", CompletionKind.keyword);
|
||||
auto max = makeSymbol("max", CompletionKind.keyword);
|
||||
auto dup = makeSymbol("dup", CompletionKind.keyword);
|
||||
auto length = makeSymbol("length", CompletionKind.keyword, ulong_);
|
||||
auto tupleof = makeSymbol("tupleof", CompletionKind.keyword);
|
||||
|
||||
variadicTmpParamSymbol = makeSymbol("variadicTmpParam", CompletionKind.keyword);
|
||||
variadicTmpParamSymbol.addChild(init, false);
|
||||
variadicTmpParamSymbol.addChild(length, false);
|
||||
variadicTmpParamSymbol.addChild(stringof_, false);
|
||||
|
||||
typeTmpParamSymbol = makeSymbol("typeTmpParam", CompletionKind.keyword);
|
||||
typeTmpParamSymbol.addChild(alignof_, false);
|
||||
typeTmpParamSymbol.addChild(init, false);
|
||||
typeTmpParamSymbol.addChild(mangleof_, false);
|
||||
typeTmpParamSymbol.addChild(sizeof_, false);
|
||||
typeTmpParamSymbol.addChild(stringof_, false);
|
||||
|
||||
arraySymbols.insert(alignof_);
|
||||
arraySymbols.insert(dup);
|
||||
arraySymbols.insert(makeSymbol("idup", CompletionKind.keyword));
|
||||
arraySymbols.insert(init);
|
||||
arraySymbols.insert(length);
|
||||
arraySymbols.insert(mangleof_);
|
||||
arraySymbols.insert(makeSymbol("ptr", CompletionKind.keyword));
|
||||
arraySymbols.insert(sizeof_);
|
||||
arraySymbols.insert(stringof_);
|
||||
|
||||
assocArraySymbols.insert(alignof_);
|
||||
assocArraySymbols.insert(makeSymbol("byKey", CompletionKind.keyword));
|
||||
assocArraySymbols.insert(makeSymbol("byValue", CompletionKind.keyword));
|
||||
assocArraySymbols.insert(makeSymbol("clear", CompletionKind.keyword));
|
||||
assocArraySymbols.insert(dup);
|
||||
assocArraySymbols.insert(makeSymbol("get", CompletionKind.keyword));
|
||||
assocArraySymbols.insert(init);
|
||||
assocArraySymbols.insert(makeSymbol("keys", CompletionKind.keyword));
|
||||
assocArraySymbols.insert(length);
|
||||
assocArraySymbols.insert(mangleof_);
|
||||
assocArraySymbols.insert(makeSymbol("rehash", CompletionKind.keyword));
|
||||
assocArraySymbols.insert(sizeof_);
|
||||
assocArraySymbols.insert(stringof_);
|
||||
assocArraySymbols.insert(init);
|
||||
assocArraySymbols.insert(makeSymbol("values", CompletionKind.keyword));
|
||||
|
||||
DSymbol*[12] integralTypeArray;
|
||||
integralTypeArray[0] = bool_;
|
||||
integralTypeArray[1] = int_;
|
||||
integralTypeArray[2] = long_;
|
||||
integralTypeArray[3] = byte_;
|
||||
integralTypeArray[4] = char_;
|
||||
integralTypeArray[5] = dchar_;
|
||||
integralTypeArray[6] = short_;
|
||||
integralTypeArray[7] = ubyte_;
|
||||
integralTypeArray[8] = uint_;
|
||||
integralTypeArray[9] = ulong_;
|
||||
integralTypeArray[10] = ushort_;
|
||||
integralTypeArray[11] = wchar_;
|
||||
|
||||
foreach (s; integralTypeArray)
|
||||
{
|
||||
s.addChild(makeSymbol("init", CompletionKind.keyword, s), false);
|
||||
s.addChild(makeSymbol("min", CompletionKind.keyword, s), false);
|
||||
s.addChild(makeSymbol("max", CompletionKind.keyword, s), false);
|
||||
s.addChild(alignof_, false);
|
||||
s.addChild(sizeof_, false);
|
||||
s.addChild(stringof_, false);
|
||||
s.addChild(mangleof_, false);
|
||||
}
|
||||
|
||||
auto cdouble_ = makeSymbol(builtinTypeNames[21], CompletionKind.keyword);
|
||||
auto cent_ = makeSymbol(builtinTypeNames[15], CompletionKind.keyword);
|
||||
auto cfloat_ = makeSymbol(builtinTypeNames[22], CompletionKind.keyword);
|
||||
auto creal_ = makeSymbol(builtinTypeNames[23], CompletionKind.keyword);
|
||||
auto double_ = makeSymbol(builtinTypeNames[2], CompletionKind.keyword);
|
||||
auto float_ = makeSymbol(builtinTypeNames[4], CompletionKind.keyword);
|
||||
auto idouble_ = makeSymbol(builtinTypeNames[3], CompletionKind.keyword);
|
||||
auto ifloat_ = makeSymbol(builtinTypeNames[5], CompletionKind.keyword);
|
||||
auto ireal_ = makeSymbol(builtinTypeNames[18], CompletionKind.keyword);
|
||||
auto real_ = makeSymbol(builtinTypeNames[17], CompletionKind.keyword);
|
||||
auto ucent_ = makeSymbol(builtinTypeNames[16], CompletionKind.keyword);
|
||||
|
||||
DSymbol*[11] floatTypeArray;
|
||||
floatTypeArray[0] = cdouble_;
|
||||
floatTypeArray[1] = cent_;
|
||||
floatTypeArray[2] = cfloat_;
|
||||
floatTypeArray[3] = creal_;
|
||||
floatTypeArray[4] = double_;
|
||||
floatTypeArray[5] = float_;
|
||||
floatTypeArray[6] = idouble_;
|
||||
floatTypeArray[7] = ifloat_;
|
||||
floatTypeArray[8] = ireal_;
|
||||
floatTypeArray[9] = real_;
|
||||
floatTypeArray[10] = ucent_;
|
||||
|
||||
foreach (s; floatTypeArray)
|
||||
{
|
||||
s.addChild(alignof_, false);
|
||||
s.addChild(makeSymbol("dig", CompletionKind.keyword, s), false);
|
||||
s.addChild(makeSymbol("epsilon", CompletionKind.keyword, s), false);
|
||||
s.addChild(makeSymbol("infinity", CompletionKind.keyword, s), false);
|
||||
s.addChild(makeSymbol("init", CompletionKind.keyword, s), false);
|
||||
s.addChild(mangleof_, false);
|
||||
s.addChild(makeSymbol("mant_dig", CompletionKind.keyword, int_), false);
|
||||
s.addChild(makeSymbol("max", CompletionKind.keyword, s), false);
|
||||
s.addChild(makeSymbol("max_10_exp", CompletionKind.keyword, int_), false);
|
||||
s.addChild(makeSymbol("max_exp", CompletionKind.keyword, int_), false);
|
||||
s.addChild(makeSymbol("min_exp", CompletionKind.keyword, int_), false);
|
||||
s.addChild(makeSymbol("min_10_exp", CompletionKind.keyword, int_), false);
|
||||
s.addChild(makeSymbol("min_normal", CompletionKind.keyword, s), false);
|
||||
s.addChild(makeSymbol("nan", CompletionKind.keyword, s), false);
|
||||
s.addChild(sizeof_, false);
|
||||
s.addChild(stringof_, false);
|
||||
}
|
||||
|
||||
aggregateSymbols.insert(tupleof);
|
||||
aggregateSymbols.insert(mangleof_);
|
||||
aggregateSymbols.insert(alignof_);
|
||||
aggregateSymbols.insert(sizeof_);
|
||||
aggregateSymbols.insert(stringof_);
|
||||
aggregateSymbols.insert(init);
|
||||
|
||||
classSymbols.insert(makeSymbol("classinfo", CompletionKind.variableName));
|
||||
classSymbols.insert(tupleof);
|
||||
classSymbols.insert(makeSymbol("__vptr", CompletionKind.variableName));
|
||||
classSymbols.insert(makeSymbol("__monitor", CompletionKind.variableName));
|
||||
classSymbols.insert(mangleof_);
|
||||
classSymbols.insert(alignof_);
|
||||
classSymbols.insert(sizeof_);
|
||||
classSymbols.insert(stringof_);
|
||||
classSymbols.insert(init);
|
||||
|
||||
enumSymbols.insert(init);
|
||||
enumSymbols.insert(sizeof_);
|
||||
enumSymbols.insert(alignof_);
|
||||
enumSymbols.insert(mangleof_);
|
||||
enumSymbols.insert(stringof_);
|
||||
enumSymbols.insert(min);
|
||||
enumSymbols.insert(max);
|
||||
|
||||
|
||||
ireal_.addChild(makeSymbol("im", CompletionKind.keyword, real_), false);
|
||||
ifloat_.addChild(makeSymbol("im", CompletionKind.keyword, float_), false);
|
||||
idouble_.addChild(makeSymbol("im", CompletionKind.keyword, double_), false);
|
||||
ireal_.addChild(makeSymbol("re", CompletionKind.keyword, real_), false);
|
||||
ifloat_.addChild(makeSymbol("re", CompletionKind.keyword, float_), false);
|
||||
idouble_.addChild(makeSymbol("re", CompletionKind.keyword, double_), false);
|
||||
|
||||
auto void_ = makeSymbol(builtinTypeNames[14], CompletionKind.keyword);
|
||||
|
||||
builtinSymbols.insert(bool_);
|
||||
bool_.type = bool_;
|
||||
builtinSymbols.insert(int_);
|
||||
int_.type = int_;
|
||||
builtinSymbols.insert(long_);
|
||||
long_.type = long_;
|
||||
builtinSymbols.insert(byte_);
|
||||
byte_.type = byte_;
|
||||
builtinSymbols.insert(char_);
|
||||
char_.type = char_;
|
||||
builtinSymbols.insert(dchar_);
|
||||
dchar_.type = dchar_;
|
||||
builtinSymbols.insert(short_);
|
||||
short_.type = short_;
|
||||
builtinSymbols.insert(ubyte_);
|
||||
ubyte_.type = ubyte_;
|
||||
builtinSymbols.insert(uint_);
|
||||
uint_.type = uint_;
|
||||
builtinSymbols.insert(ulong_);
|
||||
ulong_.type = ulong_;
|
||||
builtinSymbols.insert(ushort_);
|
||||
ushort_.type = ushort_;
|
||||
builtinSymbols.insert(wchar_);
|
||||
wchar_.type = wchar_;
|
||||
builtinSymbols.insert(cdouble_);
|
||||
cdouble_.type = cdouble_;
|
||||
builtinSymbols.insert(cent_);
|
||||
cent_.type = cent_;
|
||||
builtinSymbols.insert(cfloat_);
|
||||
cfloat_.type = cfloat_;
|
||||
builtinSymbols.insert(creal_);
|
||||
creal_.type = creal_;
|
||||
builtinSymbols.insert(double_);
|
||||
double_.type = double_;
|
||||
builtinSymbols.insert(float_);
|
||||
float_.type = float_;
|
||||
builtinSymbols.insert(idouble_);
|
||||
idouble_.type = idouble_;
|
||||
builtinSymbols.insert(ifloat_);
|
||||
ifloat_.type = ifloat_;
|
||||
builtinSymbols.insert(ireal_);
|
||||
ireal_.type = ireal_;
|
||||
builtinSymbols.insert(real_);
|
||||
real_.type = real_;
|
||||
builtinSymbols.insert(ucent_);
|
||||
ucent_.type = ucent_;
|
||||
builtinSymbols.insert(void_);
|
||||
void_.type = void_;
|
||||
|
||||
|
||||
foreach (s; ["__DATE__", "__EOF__", "__TIME__", "__TIMESTAMP__", "__VENDOR__",
|
||||
"__VERSION__", "__FUNCTION__", "__PRETTY_FUNCTION__", "__MODULE__",
|
||||
"__FILE__", "__LINE__", "__FILE_FULL_PATH__"])
|
||||
builtinSymbols.insert(makeSymbol(s, CompletionKind.keyword));
|
||||
}
|
||||
|
||||
static ~this()
|
||||
{
|
||||
destroy(builtinSymbols);
|
||||
destroy(arraySymbols);
|
||||
destroy(assocArraySymbols);
|
||||
destroy(aggregateSymbols);
|
||||
destroy(classSymbols);
|
||||
destroy(enumSymbols);
|
||||
|
||||
foreach (sym; symbolsMadeHere[])
|
||||
destroy(*sym);
|
||||
|
||||
destroy(symbolsMadeHere);
|
||||
destroy(rba);
|
||||
}
|
||||
|
||||
private RollbackAllocator rba;
|
||||
private HashSet!(DSymbol*) symbolsMadeHere;
|
||||
|
||||
private DSymbol* makeSymbol(string s, CompletionKind kind, DSymbol* type = null)
|
||||
{
|
||||
auto sym = rba.make!DSymbol(istring(s), kind, type);
|
||||
sym.ownType = false;
|
||||
symbolsMadeHere.insert(sym);
|
||||
return sym;
|
||||
}
|
||||
private DSymbol* makeSymbol(istring s, CompletionKind kind, DSymbol* type = null)
|
||||
{
|
||||
auto sym = rba.make!DSymbol(s, kind, type);
|
||||
sym.ownType = false;
|
||||
symbolsMadeHere.insert(sym);
|
||||
return sym;
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
module dsymbol.cache_entry;
|
||||
|
||||
import dsymbol.symbol;
|
||||
import std.datetime;
|
||||
import containers.openhashset;
|
||||
import containers.unrolledlist;
|
||||
|
||||
/**
|
||||
* Module cache entry
|
||||
*/
|
||||
struct CacheEntry
|
||||
{
|
||||
/// Module root symbol
|
||||
DSymbol* symbol;
|
||||
|
||||
/// Modification time when this file was last cached
|
||||
SysTime modificationTime;
|
||||
|
||||
/// The path to the module
|
||||
istring path;
|
||||
|
||||
/// The modules that this module depends on
|
||||
OpenHashSet!istring dependencies;
|
||||
|
||||
~this()
|
||||
{
|
||||
if (symbol !is null)
|
||||
typeid(DSymbol).destroy(symbol);
|
||||
}
|
||||
|
||||
pure nothrow @nogc @safe:
|
||||
|
||||
ptrdiff_t opCmp(ref const CacheEntry other) const
|
||||
{
|
||||
return path.opCmpFast(other.path);
|
||||
}
|
||||
|
||||
bool opEquals(ref const CacheEntry other) const
|
||||
{
|
||||
return path == other.path;
|
||||
}
|
||||
|
||||
size_t toHash() const
|
||||
{
|
||||
return path.toHash();
|
||||
}
|
||||
|
||||
@disable void opAssign(ref const CacheEntry other);
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,253 @@
|
|||
/**
|
||||
* This file is part of DCD, a development tool for the D programming language.
|
||||
* Copyright (C) 2014 Brian Schott
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
module dsymbol.conversion;
|
||||
|
||||
import dsymbol.cache_entry;
|
||||
import dsymbol.conversion.first;
|
||||
import dsymbol.conversion.second;
|
||||
import dsymbol.modulecache;
|
||||
import dsymbol.scope_;
|
||||
import dsymbol.string_interning;
|
||||
import dsymbol.symbol;
|
||||
import dsymbol.semantic;
|
||||
import dparse.ast;
|
||||
import dparse.lexer;
|
||||
import dparse.parser;
|
||||
import dparse.rollback_allocator;
|
||||
import std.experimental.allocator;
|
||||
|
||||
/**
|
||||
* Used by autocompletion.
|
||||
*/
|
||||
ScopeSymbolPair generateAutocompleteTrees(const(Token)[] tokens,
|
||||
RCIAllocator symbolAllocator, RollbackAllocator* parseAllocator,
|
||||
size_t cursorPosition, ref ModuleCache cache)
|
||||
{
|
||||
Module m = parseModuleForAutocomplete(tokens, internString("stdin"),
|
||||
parseAllocator, cursorPosition);
|
||||
|
||||
scope first = new FirstPass(m, internString("stdin"), symbolAllocator,
|
||||
symbolAllocator, &cache);
|
||||
first.run();
|
||||
|
||||
secondPass(first.rootSymbol, first.moduleScope, cache);
|
||||
auto r = first.rootSymbol.acSymbol;
|
||||
typeid(SemanticSymbol).destroy(first.rootSymbol);
|
||||
return ScopeSymbolPair(r, first.moduleScope);
|
||||
}
|
||||
|
||||
struct ScopeSymbolPair
|
||||
{
|
||||
void destroy()
|
||||
{
|
||||
typeid(DSymbol).destroy(symbol);
|
||||
typeid(Scope).destroy(scope_);
|
||||
}
|
||||
|
||||
DSymbol* symbol;
|
||||
Scope* scope_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by import symbol caching.
|
||||
*
|
||||
* Params:
|
||||
* tokens = the tokens that compose the file
|
||||
* fileName = the name of the file being parsed
|
||||
* parseAllocator = the allocator to use for the AST
|
||||
* Returns: the parsed module
|
||||
*/
|
||||
Module parseModuleSimple(const(Token)[] tokens, string fileName, RollbackAllocator* parseAllocator)
|
||||
{
|
||||
assert (parseAllocator !is null);
|
||||
scope parser = new SimpleParser();
|
||||
parser.fileName = fileName;
|
||||
parser.tokens = tokens;
|
||||
parser.messageFunction = &doesNothing;
|
||||
parser.allocator = parseAllocator;
|
||||
return parser.parseModule();
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
Module parseModuleForAutocomplete(const(Token)[] tokens, string fileName,
|
||||
RollbackAllocator* parseAllocator, size_t cursorPosition)
|
||||
{
|
||||
scope parser = new AutocompleteParser();
|
||||
parser.fileName = fileName;
|
||||
parser.tokens = tokens;
|
||||
parser.messageFunction = &doesNothing;
|
||||
parser.allocator = parseAllocator;
|
||||
parser.cursorPosition = cursorPosition;
|
||||
return parser.parseModule();
|
||||
}
|
||||
|
||||
class AutocompleteParser : Parser
|
||||
{
|
||||
override BlockStatement parseBlockStatement()
|
||||
{
|
||||
if (!currentIs(tok!"{"))
|
||||
return null;
|
||||
if (current.index > cursorPosition)
|
||||
{
|
||||
BlockStatement bs = allocator.make!(BlockStatement);
|
||||
bs.startLocation = current.index;
|
||||
skipBraces();
|
||||
bs.endLocation = tokens[index - 1].index;
|
||||
return bs;
|
||||
}
|
||||
immutable start = current.index;
|
||||
auto b = setBookmark();
|
||||
skipBraces();
|
||||
if (tokens[index - 1].index < cursorPosition)
|
||||
{
|
||||
abandonBookmark(b);
|
||||
BlockStatement bs = allocator.make!BlockStatement();
|
||||
bs.startLocation = start;
|
||||
bs.endLocation = tokens[index - 1].index;
|
||||
return bs;
|
||||
}
|
||||
else
|
||||
{
|
||||
goToBookmark(b);
|
||||
return super.parseBlockStatement();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
size_t cursorPosition;
|
||||
}
|
||||
|
||||
class SimpleParser : Parser
|
||||
{
|
||||
override Unittest parseUnittest()
|
||||
{
|
||||
expect(tok!"unittest");
|
||||
if (currentIs(tok!"{"))
|
||||
skipBraces();
|
||||
return allocator.make!Unittest;
|
||||
}
|
||||
|
||||
override MissingFunctionBody parseMissingFunctionBody()
|
||||
{
|
||||
// Unlike many of the other parsing functions, it is valid and expected
|
||||
// for this one to return `null` on valid code. Returning `null` in
|
||||
// this function means that we are looking at a SpecifiedFunctionBody
|
||||
// or ShortenedFunctionBody.
|
||||
//
|
||||
// The super-class will handle re-trying with the correct parsing
|
||||
// function.
|
||||
|
||||
const bool needDo = skipContracts();
|
||||
if (needDo && moreTokens && (currentIs(tok!"do") || current.text == "body"))
|
||||
return null;
|
||||
if (currentIs(tok!";"))
|
||||
advance();
|
||||
else
|
||||
return null;
|
||||
return allocator.make!MissingFunctionBody;
|
||||
}
|
||||
|
||||
override SpecifiedFunctionBody parseSpecifiedFunctionBody()
|
||||
{
|
||||
if (currentIs(tok!"{"))
|
||||
skipBraces();
|
||||
else
|
||||
{
|
||||
skipContracts();
|
||||
if (currentIs(tok!"do") || (currentIs(tok!"identifier") && current.text == "body"))
|
||||
advance();
|
||||
if (currentIs(tok!"{"))
|
||||
skipBraces();
|
||||
}
|
||||
return allocator.make!SpecifiedFunctionBody;
|
||||
}
|
||||
|
||||
override ShortenedFunctionBody parseShortenedFunctionBody()
|
||||
{
|
||||
skipContracts();
|
||||
if (currentIs(tok!"=>"))
|
||||
{
|
||||
while (!currentIs(tok!";") && moreTokens)
|
||||
{
|
||||
if (currentIs(tok!"{")) // potential function literal
|
||||
skipBraces();
|
||||
else
|
||||
advance();
|
||||
}
|
||||
if (moreTokens)
|
||||
advance();
|
||||
return allocator.make!ShortenedFunctionBody;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip contracts, and return `true` if the type of contract used requires
|
||||
* that the next token is `do`.
|
||||
*/
|
||||
private bool skipContracts()
|
||||
{
|
||||
bool needDo;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (currentIs(tok!"in"))
|
||||
{
|
||||
advance();
|
||||
if (currentIs(tok!"{"))
|
||||
{
|
||||
skipBraces();
|
||||
needDo = true;
|
||||
}
|
||||
if (currentIs(tok!"("))
|
||||
skipParens();
|
||||
}
|
||||
else if (currentIs(tok!"out"))
|
||||
{
|
||||
advance();
|
||||
if (currentIs(tok!"("))
|
||||
{
|
||||
immutable bool asExpr = peekIs(tok!";")
|
||||
|| (peekIs(tok!"identifier")
|
||||
&& index + 2 < tokens.length && tokens[index + 2].type == tok!";");
|
||||
skipParens();
|
||||
if (asExpr)
|
||||
{
|
||||
needDo = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (currentIs(tok!"{"))
|
||||
{
|
||||
skipBraces();
|
||||
needDo = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
return needDo;
|
||||
}
|
||||
}
|
||||
|
||||
void doesNothing(string, size_t, size_t, string, bool) {}
|
|
@ -0,0 +1,527 @@
|
|||
/**
|
||||
* This file is part of DCD, a development tool for the D programming language.
|
||||
* Copyright (C) 2014 Brian Schott
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
module dsymbol.conversion.second;
|
||||
|
||||
import dsymbol.semantic;
|
||||
import dsymbol.string_interning;
|
||||
import dsymbol.symbol;
|
||||
import dsymbol.scope_;
|
||||
import dsymbol.builtin.names;
|
||||
import dsymbol.builtin.symbols;
|
||||
import dsymbol.type_lookup;
|
||||
import dsymbol.deferred;
|
||||
import dsymbol.import_;
|
||||
import dsymbol.modulecache;
|
||||
import std.experimental.allocator;
|
||||
import std.experimental.allocator.gc_allocator : GCAllocator;
|
||||
import std.experimental.logger;
|
||||
import dparse.ast;
|
||||
import dparse.lexer;
|
||||
|
||||
alias SymbolAllocator = GCAllocator; // NOTE using cache.symbolAllocator instead fails when analyzing Phobos master
|
||||
|
||||
void secondPass(SemanticSymbol* currentSymbol, Scope* moduleScope, ref ModuleCache cache)
|
||||
{
|
||||
with (CompletionKind) final switch (currentSymbol.acSymbol.kind)
|
||||
{
|
||||
case className:
|
||||
case interfaceName:
|
||||
resolveInheritance(currentSymbol.acSymbol, currentSymbol.typeLookups,
|
||||
moduleScope, cache);
|
||||
break;
|
||||
case withSymbol:
|
||||
case variableName:
|
||||
case memberVariableName:
|
||||
case functionName:
|
||||
case aliasName:
|
||||
// type may not be null in the case of a renamed import
|
||||
if (currentSymbol.acSymbol.type is null)
|
||||
{
|
||||
resolveType(currentSymbol.acSymbol, currentSymbol.typeLookups,
|
||||
moduleScope, cache);
|
||||
}
|
||||
break;
|
||||
case importSymbol:
|
||||
if (currentSymbol.acSymbol.type is null)
|
||||
resolveImport(currentSymbol.acSymbol, currentSymbol.typeLookups, cache);
|
||||
break;
|
||||
case variadicTmpParam:
|
||||
currentSymbol.acSymbol.type = variadicTmpParamSymbol;
|
||||
break;
|
||||
case typeTmpParam:
|
||||
currentSymbol.acSymbol.type = typeTmpParamSymbol;
|
||||
break;
|
||||
case structName:
|
||||
case unionName:
|
||||
case enumName:
|
||||
case keyword:
|
||||
case enumMember:
|
||||
case packageName:
|
||||
case moduleName:
|
||||
case dummy:
|
||||
case templateName:
|
||||
case mixinTemplateName:
|
||||
break;
|
||||
}
|
||||
|
||||
foreach (child; currentSymbol.children)
|
||||
secondPass(child, moduleScope, cache);
|
||||
|
||||
// Alias this and mixin templates are resolved after child nodes are
|
||||
// resolved so that the correct symbol information will be available.
|
||||
with (CompletionKind) switch (currentSymbol.acSymbol.kind)
|
||||
{
|
||||
case className:
|
||||
case interfaceName:
|
||||
case structName:
|
||||
case unionName:
|
||||
resolveAliasThis(currentSymbol.acSymbol, currentSymbol.typeLookups, moduleScope, cache);
|
||||
resolveMixinTemplates(currentSymbol.acSymbol, currentSymbol.typeLookups,
|
||||
moduleScope, cache);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void resolveImport(DSymbol* acSymbol, ref TypeLookups typeLookups,
|
||||
ref ModuleCache cache)
|
||||
in
|
||||
{
|
||||
assert(acSymbol.kind == CompletionKind.importSymbol);
|
||||
assert(acSymbol.symbolFile !is null);
|
||||
}
|
||||
do
|
||||
{
|
||||
DSymbol* moduleSymbol = cache.cacheModule(acSymbol.symbolFile);
|
||||
if (acSymbol.qualifier == SymbolQualifier.selectiveImport)
|
||||
{
|
||||
if (moduleSymbol is null)
|
||||
{
|
||||
tryAgain:
|
||||
DeferredSymbol* deferred = TypeLookupsAllocator.instance.make!DeferredSymbol(acSymbol);
|
||||
deferred.typeLookups.insert(typeLookups[]);
|
||||
// Get rid of the old references to the lookups, this new deferred
|
||||
// symbol owns them now
|
||||
typeLookups.clear();
|
||||
cache.deferredSymbols.insert(deferred);
|
||||
}
|
||||
else
|
||||
{
|
||||
immutable size_t breadcrumbCount = typeLookups.front.breadcrumbs.length;
|
||||
assert(breadcrumbCount <= 2 && breadcrumbCount > 0, "Malformed selective import");
|
||||
|
||||
istring symbolName = typeLookups.front.breadcrumbs.front;
|
||||
DSymbol* selected = moduleSymbol.getFirstPartNamed(symbolName);
|
||||
if (selected is null)
|
||||
goto tryAgain;
|
||||
acSymbol.type = selected;
|
||||
acSymbol.ownType = false;
|
||||
|
||||
// count of 1 means selective import
|
||||
// count of 2 means a renamed selective import
|
||||
if (breadcrumbCount == 2)
|
||||
{
|
||||
acSymbol.kind = CompletionKind.aliasName;
|
||||
acSymbol.symbolFile = acSymbol.altFile;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (moduleSymbol is null)
|
||||
{
|
||||
DeferredSymbol* deferred = DeferredSymbolsAllocator.instance.make!DeferredSymbol(acSymbol);
|
||||
cache.deferredSymbols.insert(deferred);
|
||||
}
|
||||
else
|
||||
{
|
||||
acSymbol.type = moduleSymbol;
|
||||
acSymbol.ownType = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void resolveTypeFromType(DSymbol* symbol, TypeLookup* lookup, Scope* moduleScope,
|
||||
ref ModuleCache cache, Imports* imports)
|
||||
in
|
||||
{
|
||||
if (imports !is null)
|
||||
foreach (i; imports.opSlice())
|
||||
assert(i.kind == CompletionKind.importSymbol);
|
||||
}
|
||||
do
|
||||
{
|
||||
// The left-most suffix
|
||||
DSymbol* suffix;
|
||||
// The right-most suffix
|
||||
DSymbol* lastSuffix;
|
||||
|
||||
// Create symbols for the type suffixes such as array and
|
||||
// associative array
|
||||
while (!lookup.breadcrumbs.empty)
|
||||
{
|
||||
auto back = lookup.breadcrumbs.back;
|
||||
immutable bool isArr = back == ARRAY_SYMBOL_NAME;
|
||||
immutable bool isAssoc = back == ASSOC_ARRAY_SYMBOL_NAME;
|
||||
immutable bool isFunction = back == FUNCTION_SYMBOL_NAME;
|
||||
if (back == POINTER_SYMBOL_NAME)
|
||||
{
|
||||
lastSuffix.isPointer = true;
|
||||
lookup.breadcrumbs.popBack();
|
||||
continue;
|
||||
}
|
||||
if (!isArr && !isAssoc && !isFunction)
|
||||
break;
|
||||
immutable qualifier = isAssoc ? SymbolQualifier.assocArray :
|
||||
(isFunction ? SymbolQualifier.func : SymbolQualifier.array);
|
||||
lastSuffix = SymbolAllocator.instance.make!DSymbol(back, CompletionKind.dummy, lastSuffix);
|
||||
lastSuffix.qualifier = qualifier;
|
||||
lastSuffix.ownType = true;
|
||||
if (isFunction)
|
||||
{
|
||||
lookup.breadcrumbs.popBack();
|
||||
lastSuffix.callTip = lookup.breadcrumbs.back();
|
||||
}
|
||||
else
|
||||
lastSuffix.addChildren(isArr ? arraySymbols[] : assocArraySymbols[], false);
|
||||
|
||||
if (suffix is null)
|
||||
suffix = lastSuffix;
|
||||
lookup.breadcrumbs.popBack();
|
||||
}
|
||||
|
||||
Imports remainingImports;
|
||||
|
||||
DSymbol* currentSymbol;
|
||||
|
||||
void getSymbolFromImports(Imports* importList, istring name)
|
||||
{
|
||||
foreach (im; importList.opSlice())
|
||||
{
|
||||
assert(im.symbolFile !is null);
|
||||
// Try to find a cached version of the module
|
||||
DSymbol* moduleSymbol = cache.getModuleSymbol(im.symbolFile);
|
||||
// If the module has not been cached yet, store it in the
|
||||
// remaining imports list
|
||||
if (moduleSymbol is null)
|
||||
{
|
||||
remainingImports.insert(im);
|
||||
continue;
|
||||
}
|
||||
// Try to get the symbol from the imported module
|
||||
currentSymbol = moduleSymbol.getFirstPartNamed(name);
|
||||
if (currentSymbol is null)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Follow all the names and try to resolve them
|
||||
size_t i = 0;
|
||||
foreach (part; lookup.breadcrumbs[])
|
||||
{
|
||||
if (i == 0)
|
||||
{
|
||||
if (moduleScope is null)
|
||||
getSymbolFromImports(imports, part);
|
||||
else
|
||||
{
|
||||
auto symbols = moduleScope.getSymbolsByNameAndCursor(part, symbol.location);
|
||||
if (symbols.length > 0)
|
||||
currentSymbol = symbols[0];
|
||||
else
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (currentSymbol.kind == CompletionKind.aliasName)
|
||||
currentSymbol = currentSymbol.type;
|
||||
if (currentSymbol is null)
|
||||
return;
|
||||
if (currentSymbol.kind == CompletionKind.moduleName && currentSymbol.type !is null)
|
||||
currentSymbol = currentSymbol.type;
|
||||
if (currentSymbol is null)
|
||||
return;
|
||||
if (currentSymbol.kind == CompletionKind.importSymbol)
|
||||
currentSymbol = currentSymbol.type;
|
||||
if (currentSymbol is null)
|
||||
return;
|
||||
currentSymbol = currentSymbol.getFirstPartNamed(part);
|
||||
}
|
||||
++i;
|
||||
if (currentSymbol is null)
|
||||
return;
|
||||
}
|
||||
|
||||
if (lastSuffix !is null)
|
||||
{
|
||||
assert(suffix !is null);
|
||||
suffix.type = currentSymbol;
|
||||
suffix.ownType = false;
|
||||
symbol.type = lastSuffix;
|
||||
symbol.ownType = true;
|
||||
if (currentSymbol is null && !remainingImports.empty)
|
||||
{
|
||||
// info("Deferring type resolution for ", symbol.name);
|
||||
auto deferred = DeferredSymbolsAllocator.instance.make!DeferredSymbol(suffix);
|
||||
// TODO: The scope has ownership of the import information
|
||||
deferred.imports.insert(remainingImports[]);
|
||||
deferred.typeLookups.insert(lookup);
|
||||
cache.deferredSymbols.insert(deferred);
|
||||
}
|
||||
}
|
||||
else if (currentSymbol !is null)
|
||||
{
|
||||
symbol.type = currentSymbol;
|
||||
symbol.ownType = false;
|
||||
}
|
||||
else if (!remainingImports.empty)
|
||||
{
|
||||
auto deferred = DeferredSymbolsAllocator.instance.make!DeferredSymbol(symbol);
|
||||
// info("Deferring type resolution for ", symbol.name);
|
||||
// TODO: The scope has ownership of the import information
|
||||
deferred.imports.insert(remainingImports[]);
|
||||
deferred.typeLookups.insert(lookup);
|
||||
cache.deferredSymbols.insert(deferred);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
void resolveInheritance(DSymbol* symbol, ref TypeLookups typeLookups,
|
||||
Scope* moduleScope, ref ModuleCache cache)
|
||||
{
|
||||
import std.algorithm : filter;
|
||||
|
||||
outer: foreach (TypeLookup* lookup; typeLookups[])
|
||||
{
|
||||
if (lookup.kind != TypeLookupKind.inherit)
|
||||
continue;
|
||||
DSymbol* baseClass;
|
||||
assert(lookup.breadcrumbs.length > 0);
|
||||
|
||||
// TODO: Delayed type lookup
|
||||
auto symbolScope = moduleScope.getScopeByCursor(
|
||||
symbol.location + symbol.name.length);
|
||||
auto symbols = moduleScope.getSymbolsByNameAndCursor(lookup.breadcrumbs.front,
|
||||
symbol.location);
|
||||
if (symbols.length == 0)
|
||||
continue;
|
||||
|
||||
baseClass = symbols[0];
|
||||
lookup.breadcrumbs.popFront();
|
||||
foreach (part; lookup.breadcrumbs[])
|
||||
{
|
||||
symbols = baseClass.getPartsByName(part);
|
||||
if (symbols.length == 0)
|
||||
continue outer;
|
||||
baseClass = symbols[0];
|
||||
}
|
||||
|
||||
DSymbol* imp = SymbolAllocator.instance.make!DSymbol(IMPORT_SYMBOL_NAME,
|
||||
CompletionKind.importSymbol, baseClass);
|
||||
symbol.addChild(imp, true);
|
||||
symbolScope.addSymbol(imp, false);
|
||||
if (baseClass.kind == CompletionKind.className)
|
||||
{
|
||||
auto s = SymbolAllocator.instance.make!DSymbol(SUPER_SYMBOL_NAME,
|
||||
CompletionKind.variableName, baseClass);
|
||||
symbolScope.addSymbol(s, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void resolveAliasThis(DSymbol* symbol,
|
||||
ref TypeLookups typeLookups, Scope* moduleScope, ref ModuleCache cache)
|
||||
{
|
||||
import std.algorithm : filter;
|
||||
|
||||
foreach (aliasThis; typeLookups[].filter!(a => a.kind == TypeLookupKind.aliasThis))
|
||||
{
|
||||
assert(aliasThis.breadcrumbs.length > 0);
|
||||
auto parts = symbol.getPartsByName(aliasThis.breadcrumbs.front);
|
||||
if (parts.length == 0 || parts[0].type is null)
|
||||
continue;
|
||||
DSymbol* s = SymbolAllocator.instance.make!DSymbol(IMPORT_SYMBOL_NAME,
|
||||
CompletionKind.importSymbol, parts[0].type);
|
||||
symbol.addChild(s, true);
|
||||
auto symbolScope = moduleScope.getScopeByCursor(s.location);
|
||||
if (symbolScope !is null)
|
||||
symbolScope.addSymbol(s, false);
|
||||
}
|
||||
}
|
||||
|
||||
void resolveMixinTemplates(DSymbol* symbol,
|
||||
ref TypeLookups typeLookups, Scope* moduleScope, ref ModuleCache cache)
|
||||
{
|
||||
import std.algorithm : filter;
|
||||
|
||||
foreach (mix; typeLookups[].filter!(a => a.kind == TypeLookupKind.mixinTemplate))
|
||||
{
|
||||
assert(mix.breadcrumbs.length > 0);
|
||||
auto symbols = moduleScope.getSymbolsByNameAndCursor(mix.breadcrumbs.front,
|
||||
symbol.location);
|
||||
if (symbols.length == 0)
|
||||
continue;
|
||||
auto currentSymbol = symbols[0];
|
||||
mix.breadcrumbs.popFront();
|
||||
foreach (m; mix.breadcrumbs[])
|
||||
{
|
||||
auto s = currentSymbol.getPartsByName(m);
|
||||
if (s.length == 0)
|
||||
{
|
||||
currentSymbol = null;
|
||||
break;
|
||||
}
|
||||
else
|
||||
currentSymbol = s[0];
|
||||
}
|
||||
if (currentSymbol !is null)
|
||||
{
|
||||
auto i = SymbolAllocator.instance.make!DSymbol(IMPORT_SYMBOL_NAME,
|
||||
CompletionKind.importSymbol, currentSymbol);
|
||||
i.ownType = false;
|
||||
symbol.addChild(i, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void resolveType(DSymbol* symbol, ref TypeLookups typeLookups,
|
||||
Scope* moduleScope, ref ModuleCache cache)
|
||||
{
|
||||
|
||||
import std.conv;
|
||||
|
||||
if (typeLookups.length == 0)
|
||||
return;
|
||||
assert(typeLookups.length == 1);
|
||||
auto lookup = typeLookups.front;
|
||||
if (lookup.kind == TypeLookupKind.varOrFunType)
|
||||
resolveTypeFromType(symbol, lookup, moduleScope, cache, null);
|
||||
else if (lookup.kind == TypeLookupKind.initializer)
|
||||
resolveTypeFromInitializer(symbol, lookup, moduleScope, cache);
|
||||
// issue 94
|
||||
else if (lookup.kind == TypeLookupKind.inherit)
|
||||
resolveInheritance(symbol, typeLookups, moduleScope, cache);
|
||||
else
|
||||
assert(false, "How did this happen?");
|
||||
}
|
||||
|
||||
|
||||
void resolveTypeFromInitializer(DSymbol* symbol, TypeLookup* lookup,
|
||||
Scope* moduleScope, ref ModuleCache cache)
|
||||
{
|
||||
if (lookup.breadcrumbs.length == 0)
|
||||
return;
|
||||
DSymbol* currentSymbol = null;
|
||||
size_t i = 0;
|
||||
|
||||
auto crumbs = lookup.breadcrumbs[];
|
||||
foreach (crumb; crumbs)
|
||||
{
|
||||
if (i == 0)
|
||||
{
|
||||
currentSymbol = moduleScope.getFirstSymbolByNameAndCursor(
|
||||
symbolNameToTypeName(crumb), symbol.location);
|
||||
|
||||
if (currentSymbol is null)
|
||||
return;
|
||||
}
|
||||
else
|
||||
if (crumb == ARRAY_LITERAL_SYMBOL_NAME)
|
||||
{
|
||||
auto arr = SymbolAllocator.instance.make!(DSymbol)(ARRAY_LITERAL_SYMBOL_NAME, CompletionKind.dummy, currentSymbol);
|
||||
arr.qualifier = SymbolQualifier.array;
|
||||
currentSymbol = arr;
|
||||
}
|
||||
else if (crumb == ARRAY_SYMBOL_NAME)
|
||||
{
|
||||
typeSwap(currentSymbol);
|
||||
if (currentSymbol is null)
|
||||
return;
|
||||
|
||||
// Index expressions can be an array index or an AA index
|
||||
if (currentSymbol.qualifier == SymbolQualifier.array
|
||||
|| currentSymbol.qualifier == SymbolQualifier.assocArray
|
||||
|| currentSymbol.kind == CompletionKind.aliasName)
|
||||
{
|
||||
if (currentSymbol.type !is null)
|
||||
currentSymbol = currentSymbol.type;
|
||||
else
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto opIndex = currentSymbol.getFirstPartNamed(internString("opIndex"));
|
||||
if (opIndex !is null)
|
||||
currentSymbol = opIndex.type;
|
||||
else
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (crumb == "foreach")
|
||||
{
|
||||
typeSwap(currentSymbol);
|
||||
if (currentSymbol is null)
|
||||
return;
|
||||
if (currentSymbol.qualifier == SymbolQualifier.array
|
||||
|| currentSymbol.qualifier == SymbolQualifier.assocArray)
|
||||
{
|
||||
currentSymbol = currentSymbol.type;
|
||||
break;
|
||||
}
|
||||
auto front = currentSymbol.getFirstPartNamed(internString("front"));
|
||||
if (front !is null)
|
||||
{
|
||||
currentSymbol = front.type;
|
||||
break;
|
||||
}
|
||||
auto opApply = currentSymbol.getFirstPartNamed(internString("opApply"));
|
||||
if (opApply !is null)
|
||||
{
|
||||
currentSymbol = opApply.type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
typeSwap(currentSymbol);
|
||||
if (currentSymbol is null )
|
||||
return;
|
||||
currentSymbol = currentSymbol.getFirstPartNamed(crumb);
|
||||
}
|
||||
++i;
|
||||
if (currentSymbol is null)
|
||||
return;
|
||||
}
|
||||
typeSwap(currentSymbol);
|
||||
symbol.type = currentSymbol;
|
||||
symbol.ownType = false;
|
||||
}
|
||||
|
||||
void typeSwap(ref DSymbol* currentSymbol)
|
||||
{
|
||||
while (currentSymbol !is null && currentSymbol.type !is currentSymbol
|
||||
&& (currentSymbol.kind == CompletionKind.variableName
|
||||
|| currentSymbol.kind == CompletionKind.importSymbol
|
||||
|| currentSymbol.kind == CompletionKind.withSymbol
|
||||
|| currentSymbol.kind == CompletionKind.aliasName))
|
||||
currentSymbol = currentSymbol.type;
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/**
|
||||
* This file is part of DCD, a development tool for the D programming language.
|
||||
* Copyright (C) 2014 Brian Schott
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
module dsymbol.deferred;
|
||||
|
||||
import containers.unrolledlist;
|
||||
import containers.openhashset;
|
||||
import dsymbol.string_interning;
|
||||
import dsymbol.import_;
|
||||
import dsymbol.symbol;
|
||||
import dsymbol.type_lookup;
|
||||
import std.experimental.allocator : dispose;
|
||||
import std.experimental.allocator.mallocator : Mallocator;
|
||||
import dsymbol.semantic : TypeLookups, TypeLookupsAllocator;
|
||||
|
||||
alias ImportsAllocator = Mallocator;
|
||||
alias Imports = UnrolledList!(DSymbol*, ImportsAllocator);
|
||||
|
||||
/**
|
||||
* Contains information for deferred type resolution
|
||||
*/
|
||||
struct DeferredSymbol
|
||||
{
|
||||
~this()
|
||||
{
|
||||
foreach (l; typeLookups[])
|
||||
TypeLookupsAllocator.instance.dispose(l);
|
||||
foreach (i; imports[])
|
||||
ImportsAllocator.instance.dispose(i);
|
||||
}
|
||||
|
||||
bool dependsOn(istring modulePath)
|
||||
{
|
||||
foreach (i; imports[])
|
||||
if (i.symbolFile == modulePath)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// The symbol that needs its type resolved
|
||||
DSymbol* symbol;
|
||||
/// The imports that were in scope for the symbol's declaration'
|
||||
Imports imports;
|
||||
/// The type lookup information
|
||||
TypeLookups typeLookups;
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
* This file is part of DCD, a development tool for the D programming language.
|
||||
* Copyright (C) 2014 Brian Schott
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
module dsymbol.import_;
|
||||
|
||||
/**
|
||||
* Import information
|
||||
*/
|
||||
//struct ImportInformation
|
||||
//{
|
||||
// /// module resolved path
|
||||
// istring modulePath;
|
||||
// /// symbols to import from this module
|
||||
// UnrolledList!(Tuple!(istring, istring), false) importedSymbols;
|
||||
// /// true if the import is public
|
||||
// bool isPublic;
|
||||
//}
|
|
@ -0,0 +1,539 @@
|
|||
/**
|
||||
* This file is part of DCD, a development tool for the D programming language.
|
||||
* Copyright (C) 2014 Brian Schott
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
module dsymbol.modulecache;
|
||||
|
||||
import containers.dynamicarray;
|
||||
import containers.hashset;
|
||||
import containers.ttree;
|
||||
import containers.unrolledlist;
|
||||
import dsymbol.conversion;
|
||||
import dsymbol.conversion.first;
|
||||
import dsymbol.conversion.second;
|
||||
import dsymbol.cache_entry;
|
||||
import dsymbol.scope_;
|
||||
import dsymbol.semantic;
|
||||
import dsymbol.symbol;
|
||||
import dsymbol.string_interning;
|
||||
import dsymbol.deferred;
|
||||
import std.algorithm;
|
||||
import std.experimental.allocator;
|
||||
import std.experimental.allocator.building_blocks.allocator_list;
|
||||
import std.experimental.allocator.building_blocks.region;
|
||||
import std.experimental.allocator.building_blocks.null_allocator;
|
||||
import std.experimental.allocator.mallocator : Mallocator;
|
||||
import std.experimental.allocator.gc_allocator : GCAllocator;
|
||||
import std.conv;
|
||||
import dparse.ast;
|
||||
import std.datetime;
|
||||
import dparse.lexer;
|
||||
import dparse.parser;
|
||||
import std.experimental.logger;
|
||||
import std.file;
|
||||
import std.experimental.lexer;
|
||||
import std.path;
|
||||
|
||||
alias ASTAllocator = AllocatorList!(n => Region!Mallocator(1024 * 128), Mallocator);
|
||||
|
||||
/**
|
||||
* Returns: true if a file exists at the given path.
|
||||
*/
|
||||
bool existanceCheck(A)(A path)
|
||||
{
|
||||
if (path.exists())
|
||||
return true;
|
||||
warning("Cannot cache modules in ", path, " because it does not exist");
|
||||
return false;
|
||||
}
|
||||
|
||||
alias DeferredSymbolsAllocator = GCAllocator; // NOTE using `Mallocator` here fails when analysing Phobos as `free(): invalid pointer`
|
||||
|
||||
/**
|
||||
* Caches pre-parsed module information.
|
||||
*/
|
||||
struct ModuleCache
|
||||
{
|
||||
/// No copying.
|
||||
@disable this(this);
|
||||
|
||||
@disable this();
|
||||
|
||||
this(RCIAllocator symbolAllocator)
|
||||
{
|
||||
this.symbolAllocator = symbolAllocator;
|
||||
}
|
||||
|
||||
~this()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given paths to the list of directories checked for imports.
|
||||
* Performs duplicate checking, so multiple instances of the same path will
|
||||
* not be present.
|
||||
*/
|
||||
void addImportPaths(const string[] paths)
|
||||
{
|
||||
import std.path : baseName;
|
||||
import std.array : array;
|
||||
|
||||
auto newPaths = paths
|
||||
.map!(a => absolutePath(expandTilde(a)))
|
||||
.filter!(a => existanceCheck(a) && !importPaths[].canFind!(b => b.path == a))
|
||||
.map!(a => ImportPath(istring(a)))
|
||||
.array;
|
||||
importPaths.insert(newPaths);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given paths from the list of directories checked for
|
||||
* imports. Corresponding cache entries are removed.
|
||||
*/
|
||||
void removeImportPaths(const string[] paths)
|
||||
{
|
||||
foreach (path; paths[])
|
||||
{
|
||||
if (!importPaths[].canFind!(a => a.path == path))
|
||||
{
|
||||
warning("Cannot remove ", path, " because it is not imported");
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (ref importPath; importPaths[].filter!(a => a.path == path))
|
||||
importPaths.remove(importPath);
|
||||
|
||||
foreach (cacheEntry; cache[])
|
||||
{
|
||||
if (cacheEntry.path.data.startsWith(path))
|
||||
{
|
||||
foreach (deferredSymbol; deferredSymbols[].find!(d => d.symbol.symbolFile.data.startsWith(cacheEntry.path.data)))
|
||||
{
|
||||
deferredSymbols.remove(deferredSymbol);
|
||||
DeferredSymbolsAllocator.instance.dispose(deferredSymbol);
|
||||
}
|
||||
|
||||
cache.remove(cacheEntry);
|
||||
CacheAllocator.instance.dispose(cacheEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the cache from all import paths
|
||||
*/
|
||||
void clear()
|
||||
{
|
||||
foreach (entry; cache[])
|
||||
CacheAllocator.instance.dispose(entry);
|
||||
foreach (symbol; deferredSymbols[])
|
||||
DeferredSymbolsAllocator.instance.dispose(symbol);
|
||||
|
||||
// TODO: This call to deallocateAll is a workaround for issues of
|
||||
// CAllocatorImpl and GCAllocator not interacting well.
|
||||
symbolAllocator.deallocateAll();
|
||||
cache.clear();
|
||||
deferredSymbols.clear();
|
||||
importPaths.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Caches the module at the given location
|
||||
*/
|
||||
DSymbol* cacheModule(string location)
|
||||
{
|
||||
import std.stdio : File;
|
||||
|
||||
assert (location !is null);
|
||||
|
||||
const cachedLocation = istring(location);
|
||||
|
||||
if (recursionGuard.contains(&cachedLocation.data[0]))
|
||||
return null;
|
||||
|
||||
if (!needsReparsing(cachedLocation))
|
||||
return getEntryFor(cachedLocation).symbol;
|
||||
|
||||
recursionGuard.insert(&cachedLocation.data[0]);
|
||||
|
||||
File f = File(cachedLocation);
|
||||
immutable fileSize = cast(size_t) f.size;
|
||||
if (fileSize == 0)
|
||||
return null;
|
||||
|
||||
const(Token)[] tokens;
|
||||
auto parseStringCache = StringCache(fileSize.optimalBucketCount);
|
||||
{
|
||||
ubyte[] source = cast(ubyte[]) Mallocator.instance.allocate(fileSize);
|
||||
scope (exit) Mallocator.instance.deallocate(source);
|
||||
f.rawRead(source);
|
||||
LexerConfig config;
|
||||
config.fileName = cachedLocation;
|
||||
|
||||
// The first three bytes are sliced off here if the file starts with a
|
||||
// Unicode byte order mark. The lexer/parser don't handle them.
|
||||
tokens = getTokensForParser(
|
||||
(source.length >= 3 && source[0 .. 3] == "\xef\xbb\xbf"c)
|
||||
? source[3 .. $] : source,
|
||||
config, &parseStringCache);
|
||||
}
|
||||
|
||||
CacheEntry* newEntry = CacheAllocator.instance.make!CacheEntry();
|
||||
|
||||
scope semanticAllocator = new ASTAllocator();
|
||||
import dparse.rollback_allocator:RollbackAllocator;
|
||||
RollbackAllocator parseAllocator;
|
||||
Module m = parseModuleSimple(tokens[], cachedLocation, &parseAllocator);
|
||||
|
||||
assert (!symbolAllocator.isNull);
|
||||
scope first = new FirstPass(m, cachedLocation, symbolAllocator,
|
||||
semanticAllocator.allocatorObject, &this, newEntry);
|
||||
first.run();
|
||||
|
||||
secondPass(first.rootSymbol, first.moduleScope, this);
|
||||
|
||||
typeid(Scope).destroy(first.moduleScope);
|
||||
symbolsAllocated += first.symbolsAllocated;
|
||||
|
||||
SysTime access;
|
||||
SysTime modification;
|
||||
getTimes(cachedLocation.data, access, modification);
|
||||
|
||||
newEntry.symbol = first.rootSymbol.acSymbol;
|
||||
newEntry.modificationTime = modification;
|
||||
newEntry.path = cachedLocation;
|
||||
|
||||
CacheEntry* oldEntry = getEntryFor(cachedLocation);
|
||||
if (oldEntry !is null)
|
||||
{
|
||||
// Generate update mapping from the old symbol to the new one
|
||||
UpdatePairCollection updatePairs;
|
||||
generateUpdatePairs(oldEntry.symbol, newEntry.symbol, updatePairs);
|
||||
|
||||
// Apply updates to all symbols in modules that depend on this one
|
||||
cache[].filter!(a => a.dependencies.contains(cachedLocation)).each!(
|
||||
upstream => upstream.symbol.updateTypes(updatePairs));
|
||||
|
||||
// Remove the old symbol.
|
||||
cache.remove(oldEntry, entry => CacheAllocator.instance.dispose(entry));
|
||||
}
|
||||
|
||||
cache.insert(newEntry);
|
||||
recursionGuard.remove(&cachedLocation.data[0]);
|
||||
|
||||
resolveDeferredTypes(cachedLocation);
|
||||
|
||||
typeid(SemanticSymbol).destroy(first.rootSymbol);
|
||||
|
||||
return newEntry.symbol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves types for deferred symbols
|
||||
*/
|
||||
void resolveDeferredTypes(istring location)
|
||||
{
|
||||
DeferredSymbols temp;
|
||||
temp.insert(deferredSymbols[]);
|
||||
deferredSymbols.clear();
|
||||
foreach (deferred; temp[])
|
||||
{
|
||||
if (!deferred.imports.empty && !deferred.dependsOn(location))
|
||||
{
|
||||
deferredSymbols.insert(deferred);
|
||||
continue;
|
||||
}
|
||||
assert(deferred.symbol.type is null);
|
||||
if (deferred.symbol.kind == CompletionKind.importSymbol)
|
||||
{
|
||||
resolveImport(deferred.symbol, deferred.typeLookups, this);
|
||||
}
|
||||
else if (!deferred.typeLookups.empty)
|
||||
{
|
||||
// TODO: Is .front the right thing to do here?
|
||||
resolveTypeFromType(deferred.symbol, deferred.typeLookups.front, null,
|
||||
this, &deferred.imports);
|
||||
}
|
||||
DeferredSymbolsAllocator.instance.dispose(deferred);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* moduleName = the name of the module in "a/b/c" form
|
||||
* Returns:
|
||||
* The symbols defined in the given module, or null if the module is
|
||||
* not cached yet.
|
||||
*/
|
||||
DSymbol* getModuleSymbol(istring location)
|
||||
{
|
||||
auto existing = getEntryFor(location);
|
||||
return existing ? existing.symbol : cacheModule(location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* moduleName = the name of the module being imported, in "a/b/c" style
|
||||
* Returns:
|
||||
* The absolute path to the file that contains the module, or null if
|
||||
* not found.
|
||||
*/
|
||||
istring resolveImportLocation(string moduleName)
|
||||
{
|
||||
assert(moduleName !is null, "module name is null");
|
||||
if (isRooted(moduleName))
|
||||
return istring(moduleName);
|
||||
string alternative;
|
||||
foreach (importPath; importPaths[])
|
||||
{
|
||||
auto path = importPath.path;
|
||||
// import path is a filename
|
||||
// first check string if this is a feasable path (no filesystem usage)
|
||||
if (path.stripExtension.endsWith(moduleName)
|
||||
&& path.existsAnd!isFile)
|
||||
{
|
||||
// prefer exact import names above .di/package.d files
|
||||
return istring(path);
|
||||
}
|
||||
// no exact matches and no .di/package.d matches either
|
||||
else if (!alternative.length)
|
||||
{
|
||||
string dotDi = buildPath(path, moduleName) ~ ".di";
|
||||
string dotD = dotDi[0 .. $ - 1];
|
||||
string withoutSuffix = dotDi[0 .. $ - 3];
|
||||
if (existsAnd!isFile(dotD))
|
||||
return istring(dotD); // return early for exactly matching .d files
|
||||
else if (existsAnd!isFile(dotDi))
|
||||
alternative = dotDi;
|
||||
else if (existsAnd!isDir(withoutSuffix))
|
||||
{
|
||||
string packagePath = buildPath(withoutSuffix, "package.di");
|
||||
if (existsAnd!isFile(packagePath[0 .. $ - 1]))
|
||||
alternative = packagePath[0 .. $ - 1];
|
||||
else if (existsAnd!isFile(packagePath))
|
||||
alternative = packagePath;
|
||||
}
|
||||
}
|
||||
// we have a potential .di/package.d file but continue searching for
|
||||
// exact .d file matches to use instead
|
||||
else
|
||||
{
|
||||
string dotD = buildPath(path, moduleName) ~ ".d";
|
||||
if (existsAnd!isFile(dotD))
|
||||
return istring(dotD); // return early for exactly matching .d files
|
||||
}
|
||||
}
|
||||
return alternative.length > 0 ? istring(alternative) : istring(null);
|
||||
}
|
||||
|
||||
auto getImportPaths() const
|
||||
{
|
||||
return importPaths[].map!(a => a.path);
|
||||
}
|
||||
|
||||
auto getAllSymbols()
|
||||
{
|
||||
scanAll();
|
||||
return cache[];
|
||||
}
|
||||
|
||||
RCIAllocator symbolAllocator;
|
||||
|
||||
alias DeferredSymbols = UnrolledList!(DeferredSymbol*, DeferredSymbolsAllocator);
|
||||
DeferredSymbols deferredSymbols;
|
||||
|
||||
/// Count of autocomplete symbols that have been allocated
|
||||
uint symbolsAllocated;
|
||||
|
||||
private:
|
||||
|
||||
CacheEntry* getEntryFor(istring cachedLocation)
|
||||
{
|
||||
CacheEntry dummy;
|
||||
dummy.path = cachedLocation;
|
||||
auto r = cache.equalRange(&dummy);
|
||||
return r.empty ? null : r.front;
|
||||
}
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* mod = the path to the module
|
||||
* Returns:
|
||||
* true if the module needs to be reparsed, false otherwise
|
||||
*/
|
||||
bool needsReparsing(istring mod)
|
||||
{
|
||||
if (!exists(mod.data))
|
||||
return true;
|
||||
CacheEntry e;
|
||||
e.path = mod;
|
||||
auto r = cache.equalRange(&e);
|
||||
if (r.empty)
|
||||
return true;
|
||||
SysTime access;
|
||||
SysTime modification;
|
||||
getTimes(mod.data, access, modification);
|
||||
return r.front.modificationTime != modification;
|
||||
}
|
||||
|
||||
void scanAll()
|
||||
{
|
||||
foreach (ref importPath; importPaths)
|
||||
{
|
||||
if (importPath.scanned)
|
||||
continue;
|
||||
scope(success) importPath.scanned = true;
|
||||
|
||||
if (importPath.path.existsAnd!isFile)
|
||||
{
|
||||
if (importPath.path.baseName.startsWith(".#"))
|
||||
continue;
|
||||
cacheModule(importPath.path);
|
||||
}
|
||||
else
|
||||
{
|
||||
void scanFrom(const string root)
|
||||
{
|
||||
if (exists(buildPath(root, ".no-dcd")))
|
||||
return;
|
||||
|
||||
try foreach (f; dirEntries(root, SpanMode.shallow))
|
||||
{
|
||||
if (f.name.existsAnd!isFile)
|
||||
{
|
||||
if (!f.name.extension.among(".d", ".di") || f.name.baseName.startsWith(".#"))
|
||||
continue;
|
||||
cacheModule(f.name);
|
||||
}
|
||||
else scanFrom(f.name);
|
||||
}
|
||||
catch(FileException) {}
|
||||
}
|
||||
scanFrom(importPath.path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mapping of file paths to their cached symbols.
|
||||
alias CacheAllocator = GCAllocator; // NOTE using `Mallocator` here fails when analysing Phobos as `Segmentation fault (core dumped)`
|
||||
alias Cache = TTree!(CacheEntry*, CacheAllocator);
|
||||
Cache cache;
|
||||
|
||||
HashSet!(immutable(char)*) recursionGuard;
|
||||
|
||||
struct ImportPath
|
||||
{
|
||||
string path;
|
||||
bool scanned;
|
||||
}
|
||||
|
||||
// Listing of paths to check for imports
|
||||
UnrolledList!ImportPath importPaths;
|
||||
}
|
||||
|
||||
/// Wrapper to check some attribute of a path, ignoring errors
|
||||
/// (such as on a broken symlink).
|
||||
private static bool existsAnd(alias fun)(string file)
|
||||
{
|
||||
try
|
||||
return fun(file);
|
||||
catch (FileException e)
|
||||
return false;
|
||||
}
|
||||
|
||||
/// same as getAttributes without throwing
|
||||
/// Returns: true if exists, false otherwise
|
||||
private static bool getFileAttributesFast(R)(R name, uint* attributes)
|
||||
{
|
||||
version (Windows)
|
||||
{
|
||||
import std.internal.cstring : tempCStringW;
|
||||
import core.sys.windows.winnt : INVALID_FILE_ATTRIBUTES;
|
||||
import core.sys.windows.winbase : GetFileAttributesW;
|
||||
|
||||
auto namez = tempCStringW(name);
|
||||
static auto trustedGetFileAttributesW(const(wchar)* namez) @trusted
|
||||
{
|
||||
return GetFileAttributesW(namez);
|
||||
}
|
||||
*attributes = trustedGetFileAttributesW(namez);
|
||||
return *attributes != INVALID_FILE_ATTRIBUTES;
|
||||
}
|
||||
else version (Posix)
|
||||
{
|
||||
import core.sys.posix.sys.stat : stat, stat_t;
|
||||
import std.internal.cstring : tempCString;
|
||||
|
||||
auto namez = tempCString(name);
|
||||
static auto trustedStat(const(char)* namez, out stat_t statbuf) @trusted
|
||||
{
|
||||
return stat(namez, &statbuf);
|
||||
}
|
||||
|
||||
stat_t statbuf;
|
||||
const ret = trustedStat(namez, statbuf) == 0;
|
||||
*attributes = statbuf.st_mode;
|
||||
return ret;
|
||||
}
|
||||
else
|
||||
{
|
||||
static assert(false, "Unimplemented getAttributes check");
|
||||
}
|
||||
}
|
||||
|
||||
private static bool existsAnd(alias fun : isFile)(string file)
|
||||
{
|
||||
uint attributes;
|
||||
if (!getFileAttributesFast(file, &attributes))
|
||||
return false;
|
||||
return attrIsFile(attributes);
|
||||
}
|
||||
|
||||
private static bool existsAnd(alias fun : isDir)(string file)
|
||||
{
|
||||
uint attributes;
|
||||
if (!getFileAttributesFast(file, &attributes))
|
||||
return false;
|
||||
return attrIsDir(attributes);
|
||||
}
|
||||
|
||||
version (Windows)
|
||||
{
|
||||
unittest
|
||||
{
|
||||
assert(existsAnd!isFile(`C:\Windows\regedit.exe`));
|
||||
assert(existsAnd!isDir(`C:\Windows`));
|
||||
assert(!existsAnd!isDir(`C:\Windows\regedit.exe`));
|
||||
assert(!existsAnd!isDir(`C:\SomewhereNonExistant\nonexistant.exe`));
|
||||
assert(!existsAnd!isFile(`C:\SomewhereNonExistant\nonexistant.exe`));
|
||||
assert(!existsAnd!isFile(`C:\Windows`));
|
||||
}
|
||||
}
|
||||
else version (Posix)
|
||||
{
|
||||
unittest
|
||||
{
|
||||
assert(existsAnd!isFile(`/bin/sh`));
|
||||
assert(existsAnd!isDir(`/bin`));
|
||||
assert(!existsAnd!isDir(`/bin/sh`));
|
||||
assert(!existsAnd!isDir(`/nonexistant_dir/__nonexistant`));
|
||||
assert(!existsAnd!isFile(`/nonexistant_dir/__nonexistant`));
|
||||
assert(!existsAnd!isFile(`/bin`));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,257 @@
|
|||
/**
|
||||
* This file is part of DCD, a development tool for the D programming language.
|
||||
* Copyright (C) 2014 Brian Schott
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
module dsymbol.scope_;
|
||||
|
||||
import dsymbol.symbol;
|
||||
import dsymbol.import_;
|
||||
import dsymbol.builtin.names;
|
||||
import containers.ttree;
|
||||
import containers.unrolledlist;
|
||||
import std.algorithm : canFind, any;
|
||||
import std.experimental.logger;
|
||||
import std.experimental.allocator.gc_allocator : GCAllocator;
|
||||
|
||||
/**
|
||||
* Contains symbols and supports lookup of symbols by cursor position.
|
||||
*/
|
||||
struct Scope
|
||||
{
|
||||
@disable this(this);
|
||||
@disable this();
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* begin = the beginning byte index
|
||||
* end = the ending byte index
|
||||
*/
|
||||
this (uint begin, uint end)
|
||||
{
|
||||
this.startLocation = begin;
|
||||
this.endLocation = end;
|
||||
}
|
||||
|
||||
~this()
|
||||
{
|
||||
foreach (child; children[])
|
||||
typeid(Scope).destroy(child);
|
||||
foreach (symbol; _symbols)
|
||||
{
|
||||
if (symbol.owned)
|
||||
typeid(DSymbol).destroy(symbol.ptr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* cursorPosition = the cursor position in bytes
|
||||
* Returns:
|
||||
* the innermost scope that contains the given cursor position
|
||||
*/
|
||||
Scope* getScopeByCursor(size_t cursorPosition) return pure @nogc
|
||||
{
|
||||
if (cursorPosition < startLocation) return null;
|
||||
if (cursorPosition > endLocation) return null;
|
||||
foreach (child; children[])
|
||||
{
|
||||
auto childScope = child.getScopeByCursor(cursorPosition);
|
||||
if (childScope !is null)
|
||||
return childScope;
|
||||
}
|
||||
return cast(typeof(return)) &this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* cursorPosition = the cursor position in bytes
|
||||
* Returns:
|
||||
* all symbols in the scope containing the cursor position, as well as
|
||||
* the symbols in parent scopes of that scope.
|
||||
*/
|
||||
DSymbol*[] getSymbolsInCursorScope(size_t cursorPosition)
|
||||
{
|
||||
import std.array : array;
|
||||
import std.algorithm.iteration : map;
|
||||
|
||||
auto s = getScopeByCursor(cursorPosition);
|
||||
if (s is null)
|
||||
return null;
|
||||
|
||||
UnrolledList!(DSymbol*) retVal;
|
||||
Scope* sc = s;
|
||||
while (sc !is null)
|
||||
{
|
||||
foreach (item; sc._symbols[])
|
||||
{
|
||||
if (item.ptr.kind == CompletionKind.withSymbol)
|
||||
{
|
||||
if (item.ptr.type !is null)
|
||||
foreach (i; item.ptr.type.opSlice())
|
||||
retVal.insert(i);
|
||||
}
|
||||
else if (item.ptr.type !is null && item.ptr.kind == CompletionKind.importSymbol)
|
||||
{
|
||||
if (item.ptr.qualifier != SymbolQualifier.selectiveImport)
|
||||
{
|
||||
foreach (i; item.ptr.type.opSlice())
|
||||
retVal.insert(i);
|
||||
}
|
||||
else
|
||||
retVal.insert(item.ptr.type);
|
||||
}
|
||||
else
|
||||
retVal.insert(item.ptr);
|
||||
}
|
||||
sc = sc.parent;
|
||||
}
|
||||
return array(retVal[]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* name = the symbol name to search for
|
||||
* Returns:
|
||||
* all symbols in this scope or parent scopes with the given name
|
||||
*/
|
||||
inout(DSymbol)*[] getSymbolsByName(istring name) inout
|
||||
{
|
||||
import std.array : array, appender;
|
||||
import std.algorithm.iteration : map;
|
||||
|
||||
DSymbol s = DSymbol(name);
|
||||
auto er = _symbols.equalRange(SymbolOwnership(&s));
|
||||
if (!er.empty)
|
||||
return cast(typeof(return)) array(er.map!(a => a.ptr));
|
||||
|
||||
// Check symbols from "with" statement
|
||||
DSymbol ir2 = DSymbol(WITH_SYMBOL_NAME);
|
||||
auto r2 = _symbols.equalRange(SymbolOwnership(&ir2));
|
||||
if (!r2.empty)
|
||||
{
|
||||
auto app = appender!(DSymbol*[])();
|
||||
foreach (e; r2)
|
||||
{
|
||||
if (e.type is null)
|
||||
continue;
|
||||
foreach (withSymbol; e.type.getPartsByName(s.name))
|
||||
app.put(cast(DSymbol*) withSymbol);
|
||||
}
|
||||
if (app.data.length > 0)
|
||||
return cast(typeof(return)) app.data;
|
||||
}
|
||||
|
||||
if (name != CONSTRUCTOR_SYMBOL_NAME &&
|
||||
name != DESTRUCTOR_SYMBOL_NAME &&
|
||||
name != UNITTEST_SYMBOL_NAME &&
|
||||
name != THIS_SYMBOL_NAME)
|
||||
{
|
||||
// Check imported symbols
|
||||
DSymbol ir = DSymbol(IMPORT_SYMBOL_NAME);
|
||||
|
||||
auto app = appender!(DSymbol*[])();
|
||||
foreach (e; _symbols.equalRange(SymbolOwnership(&ir)))
|
||||
{
|
||||
if (e.type is null)
|
||||
continue;
|
||||
if (e.qualifier == SymbolQualifier.selectiveImport && e.type.name == name)
|
||||
app.put(cast(DSymbol*) e.type);
|
||||
else
|
||||
foreach (importedSymbol; e.type.getPartsByName(s.name))
|
||||
app.put(cast(DSymbol*) importedSymbol);
|
||||
}
|
||||
if (app.data.length > 0)
|
||||
return cast(typeof(return)) app.data;
|
||||
}
|
||||
if (parent is null)
|
||||
return [];
|
||||
return parent.getSymbolsByName(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* name = the symbol name to search for
|
||||
* cursorPosition = the cursor position in bytes
|
||||
* Returns:
|
||||
* all symbols with the given name in the scope containing the cursor
|
||||
* and its parent scopes
|
||||
*/
|
||||
DSymbol*[] getSymbolsByNameAndCursor(istring name, size_t cursorPosition)
|
||||
{
|
||||
auto s = getScopeByCursor(cursorPosition);
|
||||
if (s is null)
|
||||
return [];
|
||||
return s.getSymbolsByName(name);
|
||||
}
|
||||
|
||||
DSymbol* getFirstSymbolByNameAndCursor(istring name, size_t cursorPosition)
|
||||
{
|
||||
auto s = getSymbolsByNameAndCursor(name, cursorPosition);
|
||||
return s.length > 0 ? s[0] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of symbols that are present at global scope
|
||||
*/
|
||||
inout(DSymbol)*[] getSymbolsAtGlobalScope(istring name) inout
|
||||
{
|
||||
if (parent !is null)
|
||||
return parent.getSymbolsAtGlobalScope(name);
|
||||
return getSymbolsByName(name);
|
||||
}
|
||||
|
||||
bool hasSymbolRecursive(const(DSymbol)* symbol) const
|
||||
{
|
||||
return _symbols[].canFind!(a => a == symbol) || children[].any!(a => a.hasSymbolRecursive(symbol));
|
||||
}
|
||||
|
||||
/// The scope that contains this one
|
||||
Scope* parent;
|
||||
|
||||
/// Child scopes
|
||||
alias ChildrenAllocator = GCAllocator; // NOTE using `Mallocator` here fails when analysing Phobos
|
||||
alias Children = UnrolledList!(Scope*, ChildrenAllocator);
|
||||
Children children;
|
||||
|
||||
/// Start location of this scope in bytes
|
||||
uint startLocation;
|
||||
|
||||
/// End location of this scope in bytes
|
||||
uint endLocation;
|
||||
|
||||
auto symbols() @property
|
||||
{
|
||||
return _symbols[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given symbol to this scope.
|
||||
* Params:
|
||||
* symbol = the symbol to add
|
||||
* owns = if true, the symbol's destructor will be called when this
|
||||
* scope's destructor is called.
|
||||
*/
|
||||
void addSymbol(DSymbol* symbol, bool owns)
|
||||
{
|
||||
assert(symbol !is null);
|
||||
_symbols.insert(SymbolOwnership(symbol, owns));
|
||||
}
|
||||
|
||||
private:
|
||||
/// Symbols contained in this scope
|
||||
TTree!(SymbolOwnership, GCAllocator, true, "a.opCmp(b) < 0") _symbols; // NOTE using `Mallocator` here fails when analysing Phobos
|
||||
}
|
|
@ -0,0 +1,156 @@
|
|||
/**
|
||||
* This file is part of DCD, a development tool for the D programming language.
|
||||
* Copyright (C) 2014 Brian Schott
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
module dsymbol.semantic;
|
||||
|
||||
import dsymbol.symbol;
|
||||
import dparse.ast;
|
||||
import dparse.lexer;
|
||||
import containers.unrolledlist;
|
||||
import dsymbol.type_lookup;
|
||||
import std.experimental.allocator.mallocator : Mallocator;
|
||||
import std.experimental.allocator.gc_allocator : GCAllocator;
|
||||
|
||||
enum ResolutionFlags : ubyte
|
||||
{
|
||||
inheritance = 0b0000_0001,
|
||||
type = 0b0000_0010,
|
||||
mixinTemplates = 0b0000_0100,
|
||||
}
|
||||
|
||||
alias TypeLookupsAllocator = GCAllocator; // NOTE using `Mallocator` here fails when analysing Phobos as: `munmap_chunk(): invalid pointer`
|
||||
alias TypeLookups = UnrolledList!(TypeLookup*, TypeLookupsAllocator);
|
||||
|
||||
/**
|
||||
* Intermediate form between DSymbol and the AST classes. Stores enough
|
||||
* information to resolve things like base classes and alias this.
|
||||
*/
|
||||
struct SemanticSymbol
|
||||
{
|
||||
public:
|
||||
|
||||
/// Disable default construction.
|
||||
@disable this();
|
||||
/// Disable copy construction
|
||||
@disable this(this);
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* name = the name
|
||||
*/
|
||||
this(DSymbol* acSymbol)
|
||||
{
|
||||
this.acSymbol = acSymbol;
|
||||
}
|
||||
|
||||
~this()
|
||||
{
|
||||
import std.experimental.allocator : dispose;
|
||||
|
||||
foreach (child; children[])
|
||||
typeid(SemanticSymbol).destroy(child);
|
||||
foreach (lookup; typeLookups[])
|
||||
TypeLookupsAllocator.instance.dispose(lookup);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a child to the children field and updates the acSymbol's parts field
|
||||
*/
|
||||
void addChild(SemanticSymbol* child, bool owns)
|
||||
{
|
||||
children.insert(child);
|
||||
acSymbol.addChild(child.acSymbol, owns);
|
||||
}
|
||||
|
||||
/// Information used to do type resolution, inheritance, mixins, and alias this
|
||||
TypeLookups typeLookups;
|
||||
|
||||
/// Child symbols
|
||||
UnrolledList!(SemanticSymbol*, GCAllocator) children; // NOTE using `Mallocator` here fails when analysing Phobos
|
||||
|
||||
/// Autocompletion symbol
|
||||
DSymbol* acSymbol;
|
||||
|
||||
/// Parent symbol
|
||||
SemanticSymbol* parent;
|
||||
|
||||
/// Protection level for this symobol
|
||||
deprecated("Use acSymbol.protection instead") ref inout(IdType) protection() @property inout
|
||||
{
|
||||
return acSymbol.protection;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of the _argptr variable
|
||||
*/
|
||||
Type argptrType;
|
||||
|
||||
/**
|
||||
* Type of _arguments
|
||||
*/
|
||||
Type argumentsType;
|
||||
|
||||
alias GlobalsAllocator = Mallocator;
|
||||
|
||||
static this()
|
||||
{
|
||||
import dsymbol.string_interning : internString;
|
||||
import std.experimental.allocator : make;
|
||||
|
||||
// TODO: Replace these with DSymbols
|
||||
|
||||
// _argptr has type void*
|
||||
argptrType = GlobalsAllocator.instance.make!Type();
|
||||
argptrType.type2 = GlobalsAllocator.instance.make!Type2();
|
||||
argptrType.type2.builtinType = tok!"void";
|
||||
TypeSuffix argptrTypeSuffix = GlobalsAllocator.instance.make!TypeSuffix();
|
||||
argptrTypeSuffix.star = Token(tok!"*");
|
||||
argptrType.typeSuffixes = cast(TypeSuffix[]) GlobalsAllocator.instance.allocate(TypeSuffix.sizeof);
|
||||
argptrType.typeSuffixes[0] = argptrTypeSuffix;
|
||||
|
||||
// _arguments has type TypeInfo[]
|
||||
argumentsType = GlobalsAllocator.instance.make!Type();
|
||||
argumentsType.type2 = GlobalsAllocator.instance.make!Type2();
|
||||
argumentsType.type2.typeIdentifierPart = GlobalsAllocator.instance.make!TypeIdentifierPart();
|
||||
IdentifierOrTemplateInstance i = GlobalsAllocator.instance.make!IdentifierOrTemplateInstance();
|
||||
i.identifier.text = internString("TypeInfo");
|
||||
i.identifier.type = tok!"identifier";
|
||||
argumentsType.type2.typeIdentifierPart.identifierOrTemplateInstance = i;
|
||||
TypeSuffix argumentsTypeSuffix = GlobalsAllocator.instance.make!TypeSuffix();
|
||||
argumentsTypeSuffix.array = true;
|
||||
argumentsType.typeSuffixes = cast(TypeSuffix[]) GlobalsAllocator.instance.allocate(TypeSuffix.sizeof);
|
||||
argumentsType.typeSuffixes[0] = argumentsTypeSuffix;
|
||||
}
|
||||
|
||||
static ~this()
|
||||
{
|
||||
import std.experimental.allocator : dispose;
|
||||
GlobalsAllocator.instance.dispose(argumentsType.typeSuffixes[0]);
|
||||
GlobalsAllocator.instance.dispose(argumentsType.type2.typeIdentifierPart.identifierOrTemplateInstance);
|
||||
GlobalsAllocator.instance.dispose(argumentsType.type2.typeIdentifierPart);
|
||||
GlobalsAllocator.instance.dispose(argumentsType.type2);
|
||||
GlobalsAllocator.instance.dispose(argptrType.typeSuffixes[0]);
|
||||
GlobalsAllocator.instance.dispose(argptrType.type2);
|
||||
|
||||
GlobalsAllocator.instance.deallocate(argumentsType.typeSuffixes);
|
||||
GlobalsAllocator.instance.deallocate(argptrType.typeSuffixes);
|
||||
|
||||
GlobalsAllocator.instance.dispose(argumentsType);
|
||||
GlobalsAllocator.instance.dispose(argptrType);
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
/**
|
||||
* This file is part of DCD, a development tool for the D programming language.
|
||||
* Copyright (C) 2014 Brian Schott
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
module dsymbol.string_interning;
|
||||
|
||||
import std.traits : Unqual;
|
||||
import dparse.lexer;
|
||||
|
||||
/// Obsolete, use `istring` constructor instead
|
||||
istring internString(string s) nothrow @nogc @safe
|
||||
{
|
||||
return istring(s);
|
||||
}
|
||||
|
||||
static this()
|
||||
{
|
||||
stringCache = StringCache(StringCache.defaultBucketCount);
|
||||
}
|
||||
|
||||
static ~this()
|
||||
{
|
||||
destroy(stringCache);
|
||||
}
|
||||
|
||||
private StringCache stringCache = void;
|
||||
|
||||
struct istring
|
||||
{
|
||||
nothrow @nogc @safe:
|
||||
/// Interns the given string and returns the interned version. Handles empty strings too.
|
||||
this(string s)
|
||||
{
|
||||
if (s.length > 0)
|
||||
_data = stringCache.intern(s);
|
||||
}
|
||||
|
||||
pure:
|
||||
void opAssign(T)(T other) if (is(Unqual!T == istring))
|
||||
{
|
||||
_data = other._data;
|
||||
}
|
||||
|
||||
bool opCast(To : bool)() const
|
||||
{
|
||||
return _data.length > 0;
|
||||
}
|
||||
|
||||
ptrdiff_t opCmpFast(const istring another) const @trusted
|
||||
{
|
||||
// Interned strings can be compared by the pointers.
|
||||
// Identical strings MUST have the same address
|
||||
return (cast(ptrdiff_t) _data.ptr) - (cast(ptrdiff_t) another._data.ptr);
|
||||
}
|
||||
ptrdiff_t opCmp(const string another) const
|
||||
{
|
||||
import std.algorithm.comparison : cmp;
|
||||
// Compare as usual, because another string may come from somewhere else
|
||||
return cmp(_data, another);
|
||||
}
|
||||
|
||||
bool opEquals(const istring another) const @trusted
|
||||
{
|
||||
return _data.ptr is another._data.ptr;
|
||||
}
|
||||
bool opEquals(const string another) const
|
||||
{
|
||||
return _data == another;
|
||||
}
|
||||
|
||||
size_t toHash() const @trusted
|
||||
{
|
||||
return (cast(size_t) _data.ptr) * 27_644_437;
|
||||
}
|
||||
|
||||
string data() const
|
||||
{
|
||||
return _data;
|
||||
}
|
||||
|
||||
alias data this;
|
||||
private string _data;
|
||||
}
|
|
@ -0,0 +1,492 @@
|
|||
/**
|
||||
* This file is part of DCD, a development tool for the D programming language.
|
||||
* Copyright (C) 2014 Brian Schott
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
module dsymbol.symbol;
|
||||
|
||||
import std.array;
|
||||
|
||||
import std.experimental.allocator.mallocator : Mallocator;
|
||||
import std.experimental.allocator.gc_allocator : GCAllocator;
|
||||
import containers.ttree;
|
||||
import containers.unrolledlist;
|
||||
import containers.slist;
|
||||
import containers.hashset;
|
||||
import dparse.lexer;
|
||||
import std.bitmanip;
|
||||
|
||||
import dsymbol.builtin.names;
|
||||
public import dsymbol.string_interning;
|
||||
|
||||
import std.range : isOutputRange;
|
||||
|
||||
/**
|
||||
* Identifies the kind of the item in an identifier completion list
|
||||
*/
|
||||
enum CompletionKind : char
|
||||
{
|
||||
/// Invalid completion kind. This is used internally and will never
|
||||
/// be returned in a completion response.
|
||||
dummy = '?',
|
||||
|
||||
/// Import symbol. This is used internally and will never
|
||||
/// be returned in a completion response.
|
||||
importSymbol = '*',
|
||||
|
||||
/// With symbol. This is used internally and will never
|
||||
/// be returned in a completion response.
|
||||
withSymbol = 'w',
|
||||
|
||||
/// class names
|
||||
className = 'c',
|
||||
|
||||
/// interface names
|
||||
interfaceName = 'i',
|
||||
|
||||
/// structure names
|
||||
structName = 's',
|
||||
|
||||
/// union name
|
||||
unionName = 'u',
|
||||
|
||||
/// variable name
|
||||
variableName = 'v',
|
||||
|
||||
/// member variable
|
||||
memberVariableName = 'm',
|
||||
|
||||
/// keyword, built-in version, scope statement
|
||||
keyword = 'k',
|
||||
|
||||
/// function or method
|
||||
functionName = 'f',
|
||||
|
||||
/// enum name
|
||||
enumName = 'g',
|
||||
|
||||
/// enum member
|
||||
enumMember = 'e',
|
||||
|
||||
/// package name
|
||||
packageName = 'P',
|
||||
|
||||
/// module name
|
||||
moduleName = 'M',
|
||||
|
||||
/// alias name
|
||||
aliasName = 'l',
|
||||
|
||||
/// template name
|
||||
templateName = 't',
|
||||
|
||||
/// mixin template name
|
||||
mixinTemplateName = 'T',
|
||||
|
||||
/// variadic template parameter
|
||||
variadicTmpParam = 'p',
|
||||
|
||||
/// type template parameter when no constraint
|
||||
typeTmpParam = 'h',
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: true if `kind` is something that can be returned to the client
|
||||
*/
|
||||
bool isPublicCompletionKind(CompletionKind kind) pure nothrow @safe @nogc
|
||||
{
|
||||
return kind != CompletionKind.dummy && kind != CompletionKind.importSymbol
|
||||
&& kind != CompletionKind.withSymbol;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Any special information about a variable declaration symbol.
|
||||
*/
|
||||
enum SymbolQualifier : ubyte
|
||||
{
|
||||
/// None
|
||||
none,
|
||||
/// The symbol is an array
|
||||
array,
|
||||
/// The symbol is a associative array
|
||||
assocArray,
|
||||
/// The symbol is a function or delegate pointer
|
||||
func,
|
||||
/// Selective import
|
||||
selectiveImport,
|
||||
}
|
||||
|
||||
/**
|
||||
* Autocompletion symbol
|
||||
*/
|
||||
struct DSymbol
|
||||
{
|
||||
// Copying is disabled
|
||||
@disable this();
|
||||
@disable this(this);
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* name = the symbol's name
|
||||
* kind = the symbol's completion kind
|
||||
* type = the resolved type of the symbol
|
||||
*/
|
||||
this(string name, CompletionKind kind = CompletionKind.dummy, DSymbol* type = null) nothrow @nogc @safe
|
||||
{
|
||||
this.name = istring(name);
|
||||
this.kind = kind;
|
||||
this.type = type;
|
||||
}
|
||||
/// ditto
|
||||
this(istring name, CompletionKind kind = CompletionKind.dummy, DSymbol* type = null) nothrow @nogc @safe
|
||||
{
|
||||
this.name = name;
|
||||
this.kind = kind;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
~this()
|
||||
{
|
||||
foreach (ref part; parts[])
|
||||
{
|
||||
if (part.owned)
|
||||
{
|
||||
assert(part.ptr !is null);
|
||||
typeid(DSymbol).destroy(part.ptr);
|
||||
}
|
||||
else
|
||||
part.ptr = null;
|
||||
}
|
||||
if (ownType)
|
||||
typeid(DSymbol).destroy(type);
|
||||
}
|
||||
|
||||
ptrdiff_t opCmp(ref const DSymbol other) const pure nothrow @nogc @safe
|
||||
{
|
||||
return name.opCmpFast(other.name);
|
||||
}
|
||||
|
||||
bool opEquals(ref const DSymbol other) const pure nothrow @nogc @safe
|
||||
{
|
||||
return name == other.name;
|
||||
}
|
||||
|
||||
size_t toHash() const pure nothrow @nogc @safe
|
||||
{
|
||||
return name.toHash();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all parts whose name matches the given string.
|
||||
*/
|
||||
inout(DSymbol)*[] getPartsByName(istring name) inout
|
||||
{
|
||||
auto app = appender!(DSymbol*[])();
|
||||
HashSet!size_t visited;
|
||||
getParts(name, app, visited);
|
||||
return cast(typeof(return)) app.data;
|
||||
}
|
||||
|
||||
inout(DSymbol)* getFirstPartNamed(this This)(istring name) inout
|
||||
{
|
||||
auto app = appender!(DSymbol*[])();
|
||||
HashSet!size_t visited;
|
||||
getParts(name, app, visited);
|
||||
return app.data.length > 0 ? cast(typeof(return)) app.data[0] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all parts and imported parts. Filters based on the part's name if
|
||||
* the `name` argument is not null. Stores results in `app`.
|
||||
*/
|
||||
void getParts(OR)(istring name, ref OR app, ref HashSet!size_t visited,
|
||||
bool onlyOne = false) inout
|
||||
if (isOutputRange!(OR, DSymbol*))
|
||||
{
|
||||
import std.algorithm.iteration : filter;
|
||||
|
||||
if (&this is null)
|
||||
return;
|
||||
if (visited.contains(cast(size_t) &this))
|
||||
return;
|
||||
visited.insert(cast(size_t) &this);
|
||||
|
||||
if (name is null)
|
||||
{
|
||||
foreach (part; parts[].filter!(a => a.name != IMPORT_SYMBOL_NAME))
|
||||
{
|
||||
app.put(cast(DSymbol*) part);
|
||||
if (onlyOne)
|
||||
return;
|
||||
}
|
||||
DSymbol p = DSymbol(IMPORT_SYMBOL_NAME);
|
||||
foreach (im; parts.equalRange(SymbolOwnership(&p)))
|
||||
{
|
||||
if (im.type !is null && !im.skipOver)
|
||||
{
|
||||
if (im.qualifier == SymbolQualifier.selectiveImport)
|
||||
{
|
||||
app.put(cast(DSymbol*) im.type);
|
||||
if (onlyOne)
|
||||
return;
|
||||
}
|
||||
else
|
||||
im.type.getParts(name, app, visited, onlyOne);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DSymbol s = DSymbol(name);
|
||||
foreach (part; parts.equalRange(SymbolOwnership(&s)))
|
||||
{
|
||||
app.put(cast(DSymbol*) part);
|
||||
if (onlyOne)
|
||||
return;
|
||||
}
|
||||
if (name == CONSTRUCTOR_SYMBOL_NAME ||
|
||||
name == DESTRUCTOR_SYMBOL_NAME ||
|
||||
name == UNITTEST_SYMBOL_NAME ||
|
||||
name == THIS_SYMBOL_NAME)
|
||||
return; // these symbols should not be imported
|
||||
|
||||
DSymbol p = DSymbol(IMPORT_SYMBOL_NAME);
|
||||
foreach (im; parts.equalRange(SymbolOwnership(&p)))
|
||||
{
|
||||
if (im.type !is null && !im.skipOver)
|
||||
{
|
||||
if (im.qualifier == SymbolQualifier.selectiveImport)
|
||||
{
|
||||
if (im.type.name == name)
|
||||
{
|
||||
app.put(cast(DSymbol*) im.type);
|
||||
if (onlyOne)
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
im.type.getParts(name, app, visited, onlyOne);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: a range over this symbol's parts and publicly visible imports
|
||||
*/
|
||||
inout(DSymbol)*[] opSlice(this This)() inout
|
||||
{
|
||||
auto app = appender!(DSymbol*[])();
|
||||
HashSet!size_t visited;
|
||||
getParts!(typeof(app))(istring(null), app, visited);
|
||||
return cast(typeof(return)) app.data;
|
||||
}
|
||||
|
||||
void addChild(DSymbol* symbol, bool owns)
|
||||
{
|
||||
assert(symbol !is null);
|
||||
parts.insert(SymbolOwnership(symbol, owns));
|
||||
}
|
||||
|
||||
void addChildren(R)(R symbols, bool owns)
|
||||
{
|
||||
foreach (symbol; symbols)
|
||||
{
|
||||
assert(symbol !is null);
|
||||
parts.insert(SymbolOwnership(symbol, owns));
|
||||
}
|
||||
}
|
||||
|
||||
void addChildren(DSymbol*[] symbols, bool owns)
|
||||
{
|
||||
foreach (symbol; symbols)
|
||||
{
|
||||
assert(symbol !is null);
|
||||
parts.insert(SymbolOwnership(symbol, owns));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the type field based on the mappings contained in the given
|
||||
* collection.
|
||||
*/
|
||||
void updateTypes(ref UpdatePairCollection collection)
|
||||
{
|
||||
auto r = collection.equalRange(UpdatePair(type, null));
|
||||
if (!r.empty)
|
||||
type = r.front.newSymbol;
|
||||
foreach (part; parts[])
|
||||
part.updateTypes(collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Symbols that compose this symbol, such as enum members, class variables,
|
||||
* methods, parameters, etc.
|
||||
*/
|
||||
alias PartsAllocator = GCAllocator; // NOTE using `Mallocator` here fails when analysing Phobos
|
||||
alias Parts = TTree!(SymbolOwnership, PartsAllocator, true, "a < b");
|
||||
private Parts parts;
|
||||
|
||||
/**
|
||||
* DSymbol's name
|
||||
*/
|
||||
istring name;
|
||||
|
||||
/**
|
||||
* Calltip to display if this is a function
|
||||
*/
|
||||
istring callTip;
|
||||
|
||||
/**
|
||||
* Used for storing information for selective renamed imports
|
||||
*/
|
||||
alias altFile = callTip;
|
||||
|
||||
/**
|
||||
* Module containing the symbol.
|
||||
*/
|
||||
istring symbolFile;
|
||||
|
||||
/**
|
||||
* Documentation for the symbol.
|
||||
*/
|
||||
DocString doc;
|
||||
|
||||
/**
|
||||
* The symbol that represents the type.
|
||||
*/
|
||||
// TODO: assert that the type is not a function
|
||||
DSymbol* type;
|
||||
|
||||
/**
|
||||
* Names of function arguments
|
||||
*/
|
||||
// TODO: remove since we have function arguments
|
||||
UnrolledList!(istring) argNames;
|
||||
|
||||
/**
|
||||
* Function parameter symbols
|
||||
*/
|
||||
DSymbol*[] functionParameters;
|
||||
|
||||
private uint _location;
|
||||
|
||||
/**
|
||||
* DSymbol location
|
||||
*/
|
||||
size_t location() const pure nothrow @nogc @property @safe
|
||||
{
|
||||
return _location;
|
||||
}
|
||||
|
||||
void location(size_t location) pure nothrow @nogc @property @safe
|
||||
{
|
||||
// If the symbol was declared in a file, assert that it has a location
|
||||
// in that file. Built-in symbols don't need a location.
|
||||
assert(symbolFile is null || location < uint.max);
|
||||
_location = cast(uint) location;
|
||||
}
|
||||
|
||||
/**
|
||||
* The kind of symbol
|
||||
*/
|
||||
CompletionKind kind;
|
||||
|
||||
/**
|
||||
* DSymbol qualifier
|
||||
*/
|
||||
SymbolQualifier qualifier;
|
||||
|
||||
/**
|
||||
* If true, this symbol owns its type and will free it on destruction
|
||||
*/
|
||||
// dfmt off
|
||||
mixin(bitfields!(bool, "ownType", 1,
|
||||
bool, "skipOver", 1,
|
||||
bool, "isPointer", 1,
|
||||
ubyte, "", 5));
|
||||
// dfmt on
|
||||
|
||||
/// Protection level for this symbol
|
||||
IdType protection;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* istring with actual content and information if it was ditto
|
||||
*/
|
||||
struct DocString
|
||||
{
|
||||
/// Creates a non-ditto comment.
|
||||
this(istring content)
|
||||
{
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
/// Creates a comment which may have been ditto, but has been resolved.
|
||||
this(istring content, bool ditto)
|
||||
{
|
||||
this.content = content;
|
||||
this.ditto = ditto;
|
||||
}
|
||||
|
||||
alias content this;
|
||||
|
||||
/// Contains the documentation string associated with this symbol, resolves ditto to the previous comment with correct scope.
|
||||
istring content;
|
||||
/// `true` if the documentation was just a "ditto" comment copying from the previous comment.
|
||||
bool ditto;
|
||||
}
|
||||
|
||||
struct UpdatePair
|
||||
{
|
||||
ptrdiff_t opCmp(ref const UpdatePair other) const pure nothrow @nogc @safe
|
||||
{
|
||||
return (cast(ptrdiff_t) other.oldSymbol) - (cast(ptrdiff_t) this.oldSymbol);
|
||||
}
|
||||
|
||||
DSymbol* oldSymbol;
|
||||
DSymbol* newSymbol;
|
||||
}
|
||||
|
||||
alias UpdatePairCollectionAllocator = Mallocator;
|
||||
alias UpdatePairCollection = TTree!(UpdatePair, UpdatePairCollectionAllocator, false, "a < b");
|
||||
|
||||
void generateUpdatePairs(DSymbol* oldSymbol, DSymbol* newSymbol, ref UpdatePairCollection results)
|
||||
{
|
||||
results.insert(UpdatePair(oldSymbol, newSymbol));
|
||||
foreach (part; oldSymbol.parts[])
|
||||
{
|
||||
auto temp = DSymbol(oldSymbol.name);
|
||||
auto r = newSymbol.parts.equalRange(SymbolOwnership(&temp));
|
||||
if (r.empty)
|
||||
continue;
|
||||
generateUpdatePairs(part, r.front, results);
|
||||
}
|
||||
}
|
||||
|
||||
struct SymbolOwnership
|
||||
{
|
||||
ptrdiff_t opCmp(ref const SymbolOwnership other) const @nogc
|
||||
{
|
||||
return this.ptr.opCmp(*other.ptr);
|
||||
}
|
||||
|
||||
DSymbol* ptr;
|
||||
bool owned;
|
||||
alias ptr this;
|
||||
}
|
|
@ -0,0 +1,561 @@
|
|||
module dsymbol.tests;
|
||||
|
||||
import std.experimental.allocator;
|
||||
import dparse.ast, dparse.parser, dparse.lexer, dparse.rollback_allocator;
|
||||
import dsymbol.cache_entry, dsymbol.modulecache, dsymbol.symbol;
|
||||
import dsymbol.conversion, dsymbol.conversion.first, dsymbol.conversion.second;
|
||||
import dsymbol.semantic, dsymbol.string_interning, dsymbol.builtin.names;
|
||||
import std.file, std.path, std.format;
|
||||
import std.stdio : writeln, stdout;
|
||||
|
||||
/**
|
||||
* Parses `source`, caches its symbols and compares the the cache content
|
||||
* with the `results`.
|
||||
*
|
||||
* Params:
|
||||
* source = The source code to test.
|
||||
* results = An array of string array. Each slot represents the variable name
|
||||
* followed by the type strings.
|
||||
*/
|
||||
version (unittest):
|
||||
void expectSymbolsAndTypes(const string source, const string[][] results,
|
||||
string file = __FILE_FULL_PATH__, size_t line = __LINE__)
|
||||
{
|
||||
import core.exception : AssertError;
|
||||
import std.exception : enforce;
|
||||
|
||||
ModuleCache mcache = ModuleCache(theAllocator);
|
||||
auto pair = generateAutocompleteTrees(source, mcache);
|
||||
scope(exit) pair.destroy();
|
||||
|
||||
size_t i;
|
||||
foreach(ss; (*pair.symbol)[])
|
||||
{
|
||||
if (ss.type)
|
||||
{
|
||||
enforce!AssertError(i <= results.length, "not enough results", file, line);
|
||||
enforce!AssertError(results[i].length > 1,
|
||||
"at least one type must be present in a result row", file, line);
|
||||
enforce!AssertError(ss.name == results[i][0],
|
||||
"expected variableName: `%s` but got `%s`".format(results[i][0], ss.name),
|
||||
file, line);
|
||||
|
||||
auto t = cast() ss.type;
|
||||
foreach (immutable j; 1..results[i].length)
|
||||
{
|
||||
enforce!AssertError(t != null, "null symbol", file, line);
|
||||
enforce!AssertError(t.name == results[i][j],
|
||||
"expected typeName: `%s` but got `%s`".format(results[i][j], t.name),
|
||||
file, line);
|
||||
if (t.type is t && t.name.length && t.name[0] != '*')
|
||||
break;
|
||||
t = t.type;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
enforce!AssertError(i == results.length, "too many expected results, %s is left".format(results[i .. $]), file, line);
|
||||
}
|
||||
|
||||
@system unittest
|
||||
{
|
||||
writeln("Running type deduction tests...");
|
||||
q{bool b; int i;}.expectSymbolsAndTypes([["b", "bool"],["i", "int"]]);
|
||||
q{auto b = false;}.expectSymbolsAndTypes([["b", "bool"]]);
|
||||
q{auto b = true;}.expectSymbolsAndTypes([["b", "bool"]]);
|
||||
q{auto b = [0];}.expectSymbolsAndTypes([["b", "*arr-literal*", "int"]]);
|
||||
q{auto b = [[0]];}.expectSymbolsAndTypes([["b", "*arr-literal*", "*arr-literal*", "int"]]);
|
||||
q{auto b = [[[0]]];}.expectSymbolsAndTypes([["b", "*arr-literal*", "*arr-literal*", "*arr-literal*", "int"]]);
|
||||
q{auto b = [];}.expectSymbolsAndTypes([["b", "*arr-literal*", "void"]]);
|
||||
q{auto b = [[]];}.expectSymbolsAndTypes([["b", "*arr-literal*", "*arr-literal*", "void"]]);
|
||||
//q{int* b;}.expectSymbolsAndTypes([["b", "*", "int"]]);
|
||||
//q{int*[] b;}.expectSymbolsAndTypes([["b", "*arr*", "*", "int"]]);
|
||||
|
||||
q{auto b = new class {int i;};}.expectSymbolsAndTypes([["b", "__anonclass1"]]);
|
||||
|
||||
// got a crash before but solving is not yet working ("foo" instead of "__anonclass1");
|
||||
q{class Bar{} auto foo(){return new class Bar{};} auto b = foo();}.expectSymbolsAndTypes([["b", "foo"]]);
|
||||
}
|
||||
|
||||
// this one used to crash, see #125
|
||||
unittest
|
||||
{
|
||||
ModuleCache cache = ModuleCache(theAllocator);
|
||||
auto source = q{ auto a = true ? [42] : []; };
|
||||
auto pair = generateAutocompleteTrees(source, cache);
|
||||
}
|
||||
|
||||
// https://github.com/dlang-community/D-Scanner/issues/749
|
||||
unittest
|
||||
{
|
||||
ModuleCache cache = ModuleCache(theAllocator);
|
||||
auto source = q{ void test() { foo(new class A {});} };
|
||||
auto pair = generateAutocompleteTrees(source, cache);
|
||||
}
|
||||
|
||||
// https://github.com/dlang-community/D-Scanner/issues/738
|
||||
unittest
|
||||
{
|
||||
ModuleCache cache = ModuleCache(theAllocator);
|
||||
auto source = q{ void b() { c = } alias b this; };
|
||||
auto pair = generateAutocompleteTrees(source, cache);
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
ModuleCache cache = ModuleCache(theAllocator);
|
||||
|
||||
writeln("Running function literal tests...");
|
||||
const sources = [
|
||||
q{ int a; auto dg = { }; },
|
||||
q{ void f() { int a; auto dg = { }; } },
|
||||
q{ auto f = (int a) { }; },
|
||||
q{ auto f() { return (int a) { }; } },
|
||||
q{ auto f() { return g((int a) { }); } },
|
||||
q{ void f() { g((int a) { }); } },
|
||||
q{ void f() { auto x = (int a) { }; } },
|
||||
q{ void f() { auto x = g((int a) { }); } },
|
||||
];
|
||||
foreach (src; sources)
|
||||
{
|
||||
auto pair = generateAutocompleteTrees(src, cache);
|
||||
auto a = pair.scope_.getFirstSymbolByNameAndCursor(istring("a"), 35);
|
||||
assert(a, src);
|
||||
assert(a.type, src);
|
||||
assert(a.type.name == "int", src);
|
||||
}
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
ModuleCache cache = ModuleCache(theAllocator);
|
||||
|
||||
writeln("Running struct constructor tests...");
|
||||
auto source = q{ struct A {int a; struct B {bool b;} int c;} };
|
||||
auto pair = generateAutocompleteTrees(source, cache);
|
||||
auto A = pair.symbol.getFirstPartNamed(internString("A"));
|
||||
auto B = A.getFirstPartNamed(internString("B"));
|
||||
auto ACtor = A.getFirstPartNamed(CONSTRUCTOR_SYMBOL_NAME);
|
||||
auto BCtor = B.getFirstPartNamed(CONSTRUCTOR_SYMBOL_NAME);
|
||||
assert(ACtor.callTip == "this(int a, int c)");
|
||||
assert(BCtor.callTip == "this(bool b)");
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
ModuleCache cache = ModuleCache(theAllocator);
|
||||
|
||||
writeln("Running union constructor tests...");
|
||||
auto source = q{ union A {int a; bool b;} };
|
||||
auto pair = generateAutocompleteTrees(source, cache);
|
||||
auto A = pair.symbol.getFirstPartNamed(internString("A"));
|
||||
auto ACtor = A.getFirstPartNamed(CONSTRUCTOR_SYMBOL_NAME);
|
||||
assert(ACtor.callTip == "this(int a, bool b)");
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
ModuleCache cache = ModuleCache(theAllocator);
|
||||
writeln("Running non-importable symbols tests...");
|
||||
auto source = q{
|
||||
class A { this(int a){} }
|
||||
class B : A {}
|
||||
class C { A f; alias f this; }
|
||||
};
|
||||
auto pair = generateAutocompleteTrees(source, cache);
|
||||
auto A = pair.symbol.getFirstPartNamed(internString("A"));
|
||||
auto B = pair.symbol.getFirstPartNamed(internString("B"));
|
||||
auto C = pair.symbol.getFirstPartNamed(internString("C"));
|
||||
assert(A.getFirstPartNamed(CONSTRUCTOR_SYMBOL_NAME) !is null);
|
||||
assert(B.getFirstPartNamed(CONSTRUCTOR_SYMBOL_NAME) is null);
|
||||
assert(C.getFirstPartNamed(CONSTRUCTOR_SYMBOL_NAME) is null);
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
ModuleCache cache = ModuleCache(theAllocator);
|
||||
|
||||
writeln("Running alias this tests...");
|
||||
auto source = q{ struct A {int f;} struct B { A a; alias a this; void fun() { auto var = f; };} };
|
||||
auto pair = generateAutocompleteTrees(source, cache);
|
||||
auto A = pair.symbol.getFirstPartNamed(internString("A"));
|
||||
auto B = pair.symbol.getFirstPartNamed(internString("B"));
|
||||
auto Af = A.getFirstPartNamed(internString("f"));
|
||||
auto fun = B.getFirstPartNamed(internString("fun"));
|
||||
auto var = fun.getFirstPartNamed(internString("var"));
|
||||
assert(Af is pair.scope_.getFirstSymbolByNameAndCursor(internString("f"), var.location));
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
ModuleCache cache = ModuleCache(theAllocator);
|
||||
|
||||
writeln("Running anon struct tests...");
|
||||
auto source = q{ struct A { struct {int a;}} };
|
||||
auto pair = generateAutocompleteTrees(source, cache);
|
||||
auto A = pair.symbol.getFirstPartNamed(internString("A"));
|
||||
assert(A);
|
||||
auto Aa = A.getFirstPartNamed(internString("a"));
|
||||
assert(Aa);
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
ModuleCache cache = ModuleCache(theAllocator);
|
||||
|
||||
writeln("Running anon class tests...");
|
||||
const sources = [
|
||||
q{ auto a = new class Object { int i; }; },
|
||||
q{ auto a = new class Object { int i; void m() { } }; },
|
||||
q{ auto a = g(new class Object { int i; }); },
|
||||
q{ auto a = g(new class Object { int i; void m() { } }); },
|
||||
q{ void f() { new class Object { int i; }; } },
|
||||
q{ void f() { new class Object { int i; void m() { } }; } },
|
||||
q{ void f() { g(new class Object { int i; }); } },
|
||||
q{ void f() { g(new class Object { int i; void m() { } }); } },
|
||||
q{ void f() { auto a = new class Object { int i; }; } },
|
||||
q{ void f() { auto a = new class Object { int i; void m() { } }; } },
|
||||
q{ void f() { auto a = g(new class Object { int i; }); } },
|
||||
q{ void f() { auto a = g(new class Object { int i; void m() { } }); } },
|
||||
];
|
||||
foreach (src; sources)
|
||||
{
|
||||
auto pair = generateAutocompleteTrees(src, cache);
|
||||
auto a = pair.scope_.getFirstSymbolByNameAndCursor(istring("i"), 60);
|
||||
assert(a, src);
|
||||
assert(a.type, src);
|
||||
assert(a.type.name == "int", src);
|
||||
}
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
ModuleCache cache = ModuleCache(theAllocator);
|
||||
|
||||
writeln("Running the deduction from index expr tests...");
|
||||
{
|
||||
auto source = q{struct S{} S[] s; auto b = s[i];};
|
||||
auto pair = generateAutocompleteTrees(source, cache);
|
||||
DSymbol* S = pair.symbol.getFirstPartNamed(internString("S"));
|
||||
DSymbol* b = pair.symbol.getFirstPartNamed(internString("b"));
|
||||
assert(S);
|
||||
assert(b.type is S);
|
||||
}
|
||||
{
|
||||
auto source = q{struct S{} S[1] s; auto b = s[i];};
|
||||
auto pair = generateAutocompleteTrees(source, cache);
|
||||
DSymbol* S = pair.symbol.getFirstPartNamed(internString("S"));
|
||||
DSymbol* b = pair.symbol.getFirstPartNamed(internString("b"));
|
||||
assert(S);
|
||||
assert(b.type is S);
|
||||
}
|
||||
{
|
||||
auto source = q{struct S{} S[][] s; auto b = s[0];};
|
||||
auto pair = generateAutocompleteTrees(source, cache);
|
||||
DSymbol* S = pair.symbol.getFirstPartNamed(internString("S"));
|
||||
DSymbol* b = pair.symbol.getFirstPartNamed(internString("b"));
|
||||
assert(S);
|
||||
assert(b.type.type is S);
|
||||
}
|
||||
{
|
||||
auto source = q{struct S{} S[][][] s; auto b = s[0][0];};
|
||||
auto pair = generateAutocompleteTrees(source, cache);
|
||||
DSymbol* S = pair.symbol.getFirstPartNamed(internString("S"));
|
||||
DSymbol* b = pair.symbol.getFirstPartNamed(internString("b"));
|
||||
assert(S);
|
||||
assert(b.type.name == ARRAY_SYMBOL_NAME);
|
||||
assert(b.type.type is S);
|
||||
}
|
||||
{
|
||||
auto source = q{struct S{} S s; auto b = [s][0];};
|
||||
auto pair = generateAutocompleteTrees(source, cache);
|
||||
DSymbol* S = pair.symbol.getFirstPartNamed(internString("S"));
|
||||
DSymbol* b = pair.symbol.getFirstPartNamed(internString("b"));
|
||||
assert(S);
|
||||
assert(b.type is S);
|
||||
}
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
ModuleCache cache = ModuleCache(theAllocator);
|
||||
|
||||
writeln("Running `super` tests...");
|
||||
auto source = q{ class A {} class B : A {} };
|
||||
auto pair = generateAutocompleteTrees(source, cache);
|
||||
assert(pair.symbol);
|
||||
auto A = pair.symbol.getFirstPartNamed(internString("A"));
|
||||
auto B = pair.symbol.getFirstPartNamed(internString("B"));
|
||||
auto scopeA = (pair.scope_.getScopeByCursor(A.location + A.name.length));
|
||||
auto scopeB = (pair.scope_.getScopeByCursor(B.location + B.name.length));
|
||||
assert(scopeA !is scopeB);
|
||||
|
||||
assert(!scopeA.getSymbolsByName(SUPER_SYMBOL_NAME).length);
|
||||
assert(scopeB.getSymbolsByName(SUPER_SYMBOL_NAME)[0].type is A);
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
ModuleCache cache = ModuleCache(theAllocator);
|
||||
|
||||
writeln("Running the \"access chain with inherited type\" tests...");
|
||||
auto source = q{ class A {} class B : A {} };
|
||||
auto pair = generateAutocompleteTrees(source, cache);
|
||||
assert(pair.symbol);
|
||||
auto A = pair.symbol.getFirstPartNamed(internString("A"));
|
||||
assert(A);
|
||||
auto B = pair.symbol.getFirstPartNamed(internString("B"));
|
||||
assert(B);
|
||||
auto AfromB = B.getFirstPartNamed(internString("A"));
|
||||
assert(AfromB.kind == CompletionKind.aliasName);
|
||||
assert(AfromB.type is A);
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
ModuleCache cache = ModuleCache(theAllocator);
|
||||
|
||||
writeln("Running template type parameters tests...");
|
||||
{
|
||||
auto source = q{ struct Foo(T : int){} struct Bar(T : Foo){} };
|
||||
auto pair = generateAutocompleteTrees(source, "", 0, cache);
|
||||
DSymbol* T1 = pair.symbol.getFirstPartNamed(internString("Foo"));
|
||||
DSymbol* T2 = T1.getFirstPartNamed(internString("T"));
|
||||
assert(T2.type.name == "int");
|
||||
DSymbol* T3 = pair.symbol.getFirstPartNamed(internString("Bar"));
|
||||
DSymbol* T4 = T3.getFirstPartNamed(internString("T"));
|
||||
assert(T4.type);
|
||||
assert(T4.type == T1);
|
||||
}
|
||||
{
|
||||
auto source = q{ struct Foo(T){ }};
|
||||
auto pair = generateAutocompleteTrees(source, "", 0, cache);
|
||||
DSymbol* T1 = pair.symbol.getFirstPartNamed(internString("Foo"));
|
||||
assert(T1);
|
||||
DSymbol* T2 = T1.getFirstPartNamed(internString("T"));
|
||||
assert(T2);
|
||||
assert(T2.kind == CompletionKind.typeTmpParam);
|
||||
}
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
ModuleCache cache = ModuleCache(theAllocator);
|
||||
|
||||
writeln("Running template variadic parameters tests...");
|
||||
auto source = q{ struct Foo(T...){ }};
|
||||
auto pair = generateAutocompleteTrees(source, "", 0, cache);
|
||||
DSymbol* T1 = pair.symbol.getFirstPartNamed(internString("Foo"));
|
||||
assert(T1);
|
||||
DSymbol* T2 = T1.getFirstPartNamed(internString("T"));
|
||||
assert(T2);
|
||||
assert(T2.kind == CompletionKind.variadicTmpParam);
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
writeln("Running public import tests...");
|
||||
|
||||
const dir = buildPath(tempDir(), "dsymbol");
|
||||
const fnameA = buildPath(dir, "a.d");
|
||||
const fnameB = buildPath(dir, "b.d");
|
||||
const fnameC = buildPath(dir, "c.d");
|
||||
const fnameD = buildPath(dir, "d.d");
|
||||
const srcA = q{ int x; int w; };
|
||||
const srcB = q{ float y; private float z; };
|
||||
const srcC = q{ public { import a : x; import b; } import a : w; long t; };
|
||||
const srcD = q{ public import c; };
|
||||
// A simpler diagram:
|
||||
// a = x w
|
||||
// b = y [z]
|
||||
// c = t + (x y) [w]
|
||||
// d = (t x y)
|
||||
|
||||
mkdir(dir);
|
||||
write(fnameA, srcA);
|
||||
write(fnameB, srcB);
|
||||
write(fnameC, srcC);
|
||||
write(fnameD, srcD);
|
||||
scope (exit)
|
||||
{
|
||||
remove(fnameA);
|
||||
remove(fnameB);
|
||||
remove(fnameC);
|
||||
remove(fnameD);
|
||||
rmdir(dir);
|
||||
}
|
||||
|
||||
ModuleCache cache = ModuleCache(theAllocator);
|
||||
cache.addImportPaths([dir]);
|
||||
|
||||
const a = cache.getModuleSymbol(istring(fnameA));
|
||||
const b = cache.getModuleSymbol(istring(fnameB));
|
||||
const c = cache.getModuleSymbol(istring(fnameC));
|
||||
const d = cache.getModuleSymbol(istring(fnameD));
|
||||
const ax = a.getFirstPartNamed(istring("x"));
|
||||
const aw = a.getFirstPartNamed(istring("w"));
|
||||
assert(ax);
|
||||
assert(aw);
|
||||
assert(ax.type && ax.type.name == "int");
|
||||
assert(aw.type && aw.type.name == "int");
|
||||
const by = b.getFirstPartNamed(istring("y"));
|
||||
const bz = b.getFirstPartNamed(istring("z"));
|
||||
assert(by);
|
||||
assert(bz);
|
||||
assert(by.type && by.type.name == "float");
|
||||
assert(bz.type && bz.type.name == "float");
|
||||
const ct = c.getFirstPartNamed(istring("t"));
|
||||
const cw = c.getFirstPartNamed(istring("w"));
|
||||
const cx = c.getFirstPartNamed(istring("x"));
|
||||
const cy = c.getFirstPartNamed(istring("y"));
|
||||
const cz = c.getFirstPartNamed(istring("z"));
|
||||
assert(ct);
|
||||
assert(ct.type && ct.type.name == "long");
|
||||
assert(cw is null); // skipOver is true
|
||||
assert(cx is ax);
|
||||
assert(cy is by);
|
||||
assert(cz is bz); // should not be there, but it is handled by DCD
|
||||
const dt = d.getFirstPartNamed(istring("t"));
|
||||
const dw = d.getFirstPartNamed(istring("w"));
|
||||
const dx = d.getFirstPartNamed(istring("x"));
|
||||
const dy = d.getFirstPartNamed(istring("y"));
|
||||
const dz = d.getFirstPartNamed(istring("z"));
|
||||
assert(dt is ct);
|
||||
assert(dw is null);
|
||||
assert(dx is cx);
|
||||
assert(dy is cy);
|
||||
assert(dz is cz);
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
ModuleCache cache = ModuleCache(theAllocator);
|
||||
|
||||
writeln("Testing protection scopes");
|
||||
auto source = q{version(all) { private: } struct Foo{ }};
|
||||
auto pair = generateAutocompleteTrees(source, "", 0, cache);
|
||||
DSymbol* T1 = pair.symbol.getFirstPartNamed(internString("Foo"));
|
||||
assert(T1);
|
||||
assert(T1.protection != tok!"private");
|
||||
}
|
||||
|
||||
// check for memory leaks on thread termination (in static constructors)
|
||||
version (linux)
|
||||
unittest
|
||||
{
|
||||
import core.memory : GC;
|
||||
import core.thread : Thread;
|
||||
import fs = std.file;
|
||||
import std.array : split;
|
||||
import std.conv : to;
|
||||
|
||||
// get the resident set size
|
||||
static long getRSS()
|
||||
{
|
||||
GC.collect();
|
||||
GC.minimize();
|
||||
// read Linux process statistics
|
||||
const txt = fs.readText("/proc/self/stat");
|
||||
const parts = split(txt);
|
||||
return to!long(parts[23]);
|
||||
}
|
||||
|
||||
const rssBefore = getRSS();
|
||||
// create and destroy a lot of dummy threads
|
||||
foreach (j; 0 .. 50)
|
||||
{
|
||||
Thread[100] arr;
|
||||
foreach (i; 0 .. 100)
|
||||
arr[i] = new Thread({}).start();
|
||||
foreach (i; 0 .. 100)
|
||||
arr[i].join();
|
||||
}
|
||||
const rssAfter = getRSS();
|
||||
// check the process memory increase with some eyeballed threshold
|
||||
assert(rssAfter - rssBefore < 5000);
|
||||
}
|
||||
|
||||
// this is for testing that internString data is always on the same address
|
||||
// since we use this special property for modulecache recursion guard
|
||||
unittest
|
||||
{
|
||||
istring a = internString("foo_bar_baz".idup);
|
||||
istring b = internString("foo_bar_baz".idup);
|
||||
assert(a.data.ptr == b.data.ptr);
|
||||
}
|
||||
|
||||
private StringCache stringCache = void;
|
||||
static this()
|
||||
{
|
||||
stringCache = StringCache(StringCache.defaultBucketCount);
|
||||
}
|
||||
static ~this()
|
||||
{
|
||||
destroy(stringCache);
|
||||
}
|
||||
|
||||
const(Token)[] lex(string source)
|
||||
{
|
||||
return lex(source, null);
|
||||
}
|
||||
|
||||
const(Token)[] lex(string source, string filename)
|
||||
{
|
||||
import dparse.lexer : getTokensForParser;
|
||||
import std.string : representation;
|
||||
LexerConfig config;
|
||||
config.fileName = filename;
|
||||
return getTokensForParser(source.dup.representation, config, &stringCache);
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
auto tokens = lex(q{int a = 9;});
|
||||
foreach(i, t;
|
||||
cast(IdType[]) [tok!"int", tok!"identifier", tok!"=", tok!"intLiteral", tok!";"])
|
||||
{
|
||||
assert(tokens[i] == t);
|
||||
}
|
||||
assert(tokens[1].text == "a", tokens[1].text);
|
||||
assert(tokens[3].text == "9", tokens[3].text);
|
||||
}
|
||||
|
||||
string randomDFilename()
|
||||
{
|
||||
import std.uuid : randomUUID;
|
||||
return "dsymbol_" ~ randomUUID().toString() ~ ".d";
|
||||
}
|
||||
|
||||
ScopeSymbolPair generateAutocompleteTrees(string source, ref ModuleCache cache)
|
||||
{
|
||||
return generateAutocompleteTrees(source, randomDFilename, cache);
|
||||
}
|
||||
|
||||
ScopeSymbolPair generateAutocompleteTrees(string source, string filename, ref ModuleCache cache)
|
||||
{
|
||||
auto tokens = lex(source);
|
||||
RollbackAllocator rba;
|
||||
Module m = parseModule(tokens, filename, &rba);
|
||||
|
||||
scope first = new FirstPass(m, internString(filename),
|
||||
theAllocator, theAllocator, &cache);
|
||||
first.run();
|
||||
|
||||
secondPass(first.rootSymbol, first.moduleScope, cache);
|
||||
auto r = first.rootSymbol.acSymbol;
|
||||
typeid(SemanticSymbol).destroy(first.rootSymbol);
|
||||
return ScopeSymbolPair(r, first.moduleScope);
|
||||
}
|
||||
|
||||
ScopeSymbolPair generateAutocompleteTrees(string source, size_t cursorPosition, ref ModuleCache cache)
|
||||
{
|
||||
return generateAutocompleteTrees(source, null, cache);
|
||||
}
|
||||
|
||||
ScopeSymbolPair generateAutocompleteTrees(string source, string filename, size_t cursorPosition, ref ModuleCache cache)
|
||||
{
|
||||
auto tokens = lex(source);
|
||||
RollbackAllocator rba;
|
||||
return dsymbol.conversion.generateAutocompleteTrees(
|
||||
tokens, theAllocator, &rba, cursorPosition, cache);
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
module dsymbol.type_lookup;
|
||||
|
||||
import dsymbol.string_interning;
|
||||
import containers.unrolledlist;
|
||||
|
||||
/**
|
||||
* The type lookup kind.
|
||||
*/
|
||||
enum TypeLookupKind : ubyte
|
||||
{
|
||||
inherit,
|
||||
aliasThis,
|
||||
initializer,
|
||||
mixinTemplate,
|
||||
varOrFunType,
|
||||
selectiveImport,
|
||||
}
|
||||
|
||||
/**
|
||||
* information used by the symbol resolver to determine types, inheritance,
|
||||
* mixins, and alias this.
|
||||
*/
|
||||
struct TypeLookup
|
||||
{
|
||||
this(TypeLookupKind kind)
|
||||
{
|
||||
this.kind = kind;
|
||||
}
|
||||
|
||||
this(istring name, TypeLookupKind kind)
|
||||
{
|
||||
breadcrumbs.insert(name);
|
||||
this.kind = kind;
|
||||
}
|
||||
|
||||
/// Strings used to resolve the type
|
||||
UnrolledList!istring breadcrumbs;
|
||||
/// The kind of type lookup
|
||||
TypeLookupKind kind;
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
[wrap-git]
|
||||
directory = dcontainers
|
||||
url = https://github.com/dlang-community/containers.git
|
||||
revision = head
|
|
@ -0,0 +1,4 @@
|
|||
[wrap-git]
|
||||
directory = dparse
|
||||
url = https://github.com/dlang-community/libdparse.git
|
||||
revision = head
|
6
dub.json
6
dub.json
|
@ -7,12 +7,12 @@
|
|||
],
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"dsymbol": ">=0.14.0 <0.15.0",
|
||||
":dsymbol": "*",
|
||||
"libdparse": ">=0.20.0 <0.21.0",
|
||||
":common": "*",
|
||||
"emsi_containers": "~>0.8.0"
|
||||
"emsi_containers": "~>0.9.0"
|
||||
},
|
||||
"subPackages": ["common"],
|
||||
"subPackages": ["dsymbol", "common"],
|
||||
"versions": ["built_with_dub"],
|
||||
"configurations": [
|
||||
{
|
||||
|
|
|
@ -6,6 +6,13 @@ NORMAL="\033[0m"
|
|||
IMPORTS=$(pwd)/imports
|
||||
export IMPORTS
|
||||
|
||||
if [ -z "${1:-}" ];
|
||||
then
|
||||
TESTCASES="tc*"
|
||||
else
|
||||
TESTCASES="$1"
|
||||
fi
|
||||
|
||||
fail_count=0
|
||||
pass_count=0
|
||||
client="../bin/dcd-client"
|
||||
|
@ -51,7 +58,7 @@ for socket in unix tcp; do
|
|||
done
|
||||
|
||||
# Run tests
|
||||
for testCase in tc*; do
|
||||
for testCase in $TESTCASES; do
|
||||
cd $testCase
|
||||
|
||||
./run.sh "$tcp"
|
||||
|
|
Loading…
Reference in New Issue