14enum class TokenKind { Identifier,
String, Symbol };
17 TokenKind
kind = TokenKind::Symbol;
32bool identifierStart(
char value) {
return std::isalpha(
static_cast<unsigned char>(
value)) != 0 ||
value ==
'_'; }
34bool identifierPart(
char value) {
return std::isalnum(
static_cast<unsigned char>(
value)) != 0 ||
value ==
'_'; }
36bool validIdentifier(std::string_view
value) {
37 if (
value.empty() || !identifierStart(
value.front()))
return false;
38 return std::all_of(
value.begin() + 1,
value.end(), identifierPart);
41std::vector<Token> tokenize(std::string_view
source) {
42 std::vector<Token> result;
45 const auto advance = [&](
char value,
size_t& currentLine,
size_t& currentColumn) {
54 for (
size_t i = 0; i <
source.size();) {
57 if (std::isspace(
static_cast<unsigned char>(
value)) != 0) {
62 if (
value ==
'/' && next ==
'/') {
69 if (
value ==
'/' && next ==
'*') {
73 while (i <
source.size()) {
87 const size_t tokenOffset = i;
89 if (identifierStart(
value)) {
96 result.push_back({TokenKind::Identifier,
97 std::string(
source.substr(tokenOffset, i - tokenOffset)),
114 while (i <
source.size()) {
139 result.push_back({TokenKind::String, std::move(
text), tokenOffset, {
start, {
line,
column}}});
145 result.push_back({TokenKind::Symbol, std::string(1,
value), tokenOffset, {
start, {
line,
column}}});
150std::string resolveImport(std::string_view importer, std::string_view specifier) {
151 script::ScriptModuleRequest
request{std::string(importer), std::string(specifier)};
152 std::string canonical;
164bool isKeyword(std::string_view
word) {
165 static const std::unordered_set<std::string> words = {
166 "if",
"else",
"switch",
"case",
"default",
"while",
"do",
"for",
167 "foreach",
"break",
"continue",
"return",
"try",
"catch",
"throw",
"yield",
168 "resume",
"await",
"local",
"const",
"enum",
"class",
"function",
"constructor",
169 "static",
"persist",
"async",
"export",
"import",
"from",
"as",
"extends",
170 "in",
"typeof",
"instanceof",
"clone",
"delete",
"this",
"base",
"self",
171 "true",
"false",
"null",
"nullrel"};
172 return words.count(std::string(
word)) != 0;
183 std::unordered_map<std::string, size_t>
exports;
198 std::unordered_map<std::string, Unit>
units;
206 next.canonicalUri = std::move(canonicalUri);
207 next.clientUri = std::move(clientUri);
208 next.source = std::move(
source);
209 next.tokens = tokenize(next.source);
211 const auto&
tokens = next.tokens;
212 for (
size_t i = 0; i <
tokens.size(); ++i) {
213 if (
tokens[i].
kind != TokenKind::Identifier)
continue;
215 const auto remember = [&](
const std::string&
name, uint32_t
type, uint32_t modifiers) {
216 next.nameTypes.try_emplace(
name,
type);
217 next.nameModifiers.try_emplace(
name, modifiers);
219 if ((
word ==
"local" ||
word ==
"function" ||
word ==
"class" ||
word ==
"const" ||
word ==
"persist") &&
220 i + 1 <
tokens.size() &&
tokens[i + 1].kind == TokenKind::Identifier &&
222 next.declarations.try_emplace(
tokens[i + 1].
text, i + 1);
225 else if (
word ==
"function")
227 else if (
word ==
"const")
232 if (
word ==
"export" && i + 2 <
tokens.size() &&
tokens[i + 1].kind == TokenKind::Identifier &&
233 (
tokens[i + 1].text ==
"function" ||
tokens[i + 1].text ==
"class" ||
tokens[i + 1].text ==
"const") &&
234 tokens[i + 2].kind == TokenKind::Identifier) {
235 next.declarations.try_emplace(
tokens[i + 2].
text, i + 2);
236 next.exports[
tokens[i + 2].text] = i + 2;
244 if (
word !=
"import" || i + 1 >=
tokens.size())
continue;
284 next.imports.push_back(
287 impl_->units[next.canonicalUri] = std::move(next);
290void WorkspaceIndex::remove(std::string_view canonicalUri) { impl_->units.erase(std::string(canonicalUri)); }
293 const auto found = impl_->units.find(std::string(canonicalUri));
294 return found == impl_->units.end() ? nullptr : &
found->second;
297std::optional<WorkspaceIndex::Target> WorkspaceIndex::targetAt(
const Unit&
current, Position
position)
const {
299 for (
size_t i = 0; i <
current.tokens.size(); ++i) {
305 if (tokenIndex ==
current.tokens.size())
return std::nullopt;
306 const std::string&
name =
current.tokens[tokenIndex].text;
308 if (!
binding.namespaceImport || tokenIndex < 2)
continue;
309 if (
current.tokens[tokenIndex - 2].kind == TokenKind::Identifier &&
314 if (tokenIndex ==
binding.importedToken)
328std::optional<Location> WorkspaceIndex::targetDefinition(
const Target&
target)
const {
329 const Unit* defining =
unit(
target.definingUri);
330 if (defining ==
nullptr)
return std::nullopt;
331 const auto exported = defining->exports.find(
target.name);
332 const auto declared = defining->declarations.find(
target.name);
333 if (exported != defining->exports.end())
334 return Location{defining->clientUri, defining->tokens[exported->second].range};
335 if (declared != defining->declarations.end())
336 return Location{defining->clientUri, defining->tokens[declared->second].range};
340std::vector<Location> WorkspaceIndex::targetReferences(
const Target&
target,
bool includeDeclaration)
const {
341 std::vector<Location> result;
342 const auto appendNamed = [&](
const Unit&
source, std::string_view
name,
bool declaration) {
343 std::unordered_set<size_t> declarationTokens;
344 for (
const auto& [_,
token] :
source.declarations) declarationTokens.insert(
token);
345 for (
size_t i = 0; i <
source.tokens.size(); ++i) {
346 if (
source.tokens[i].kind != TokenKind::Identifier ||
source.tokens[i].text !=
name)
continue;
347 const bool isDeclaration = declarationTokens.count(i) != 0;
348 if (!declaration && isDeclaration)
continue;
349 if (!includeDeclaration && isDeclaration)
continue;
350 result.push_back({
source.clientUri,
source.tokens[i].range});
357 if (
const Unit* defining =
unit(
target.definingUri)) appendNamed(*defining,
target.name,
true);
362 for (
size_t i = 0; i + 2 <
source.tokens.size(); ++i) {
363 if (
source.tokens[i].kind != TokenKind::Identifier ||
source.tokens[i].text !=
binding.local ||
364 source.tokens[i + 1].text !=
"." ||
source.tokens[i + 2].kind != TokenKind::Identifier ||
367 result.push_back({
source.clientUri,
source.tokens[i + 2].range});
374 for (
size_t i = 0; i <
source.tokens.size(); ++i) {
375 if (
source.tokens[i].kind != TokenKind::Identifier ||
source.tokens[i].text !=
binding.local)
377 result.push_back({
source.clientUri,
source.tokens[i].range});
382 std::sort(result.begin(), result.end(), [](
const Location&
a,
const Location&
b) {
383 if (a.uri != b.uri) return a.uri < b.uri;
384 if (a.range.start.line != b.range.start.line) return a.range.start.line < b.range.start.line;
385 return a.range.start.character < b.range.start.character;
387 result.erase(std::unique(result.begin(), result.end(),
388 [](
const Location&
a,
const Location&
b) {
389 return a.uri == b.uri && a.range.start.line == b.range.start.line &&
390 a.range.start.character == b.range.start.character;
396std::vector<SemanticToken> WorkspaceIndex::semanticTokens(std::string_view canonicalUri)
const {
398 if (
current ==
nullptr)
return {};
400 std::vector<SemanticToken> result;
401 for (
size_t i = 0; i <
tokens.size(); ++i) {
402 if (
tokens[i].
kind != TokenKind::Identifier)
continue;
404 if (
name ==
"true" ||
name ==
"false" ||
name ==
"null" ||
name ==
"nullrel" ||
name ==
"this" ||
406 result.push_back({
tokens[i].range.start,
name.size(), SemanticTypes::Keyword, 0,
name});
409 if (isKeyword(
name))
continue;
410 const Token* prev = i > 0 ? &
tokens[i - 1] :
nullptr;
411 const Token* next = i + 1 <
tokens.size() ? &
tokens[i + 1] :
nullptr;
412 const bool called = next && next->text ==
"(";
413 const bool member = prev && prev->text ==
".";
418 token.type = SemanticTypes::Variable;
421 const std::string prevText = prev && prev->kind == TokenKind::Identifier ? prev->text : std::string{};
422 if (prevText ==
"class" || prevText ==
"extends" || (prev && prev->text ==
":")) {
423 token.type = SemanticTypes::Class;
424 token.modifiers = prevText ==
"class" ? SemanticMods::Declaration : 0;
425 }
else if (prevText ==
"function") {
426 token.type = SemanticTypes::Function;
427 token.modifiers = SemanticMods::Declaration;
428 }
else if (prevText ==
"local" || prevText ==
"persist") {
429 token.type = SemanticTypes::Variable;
430 token.modifiers = SemanticMods::Declaration;
431 }
else if (prevText ==
"const") {
432 token.type = SemanticTypes::Variable;
433 token.modifiers = SemanticMods::Declaration | SemanticMods::Readonly;
434 }
else if (
member && called) {
435 token.type = (!
name.empty() && std::isupper(
static_cast<unsigned char>(
name.front())) != 0)
436 ? SemanticTypes::Class
437 : SemanticTypes::Method;
439 token.type = SemanticTypes::Property;
443 token.type = SemanticTypes::Class;
444 else if (!
name.empty() && std::isupper(
static_cast<unsigned char>(
name.front())) != 0)
445 token.type = SemanticTypes::Class;
447 token.type = SemanticTypes::Function;
451 const auto mods =
current->nameModifiers.find(
name);
452 if (mods !=
current->nameModifiers.end())
token.modifiers = mods->second;
454 result.push_back(std::move(
token));
459std::optional<Location> WorkspaceIndex::definition(std::string_view canonicalUri,
Position position)
const {
461 if (
current ==
nullptr)
return std::nullopt;
463 return target ? targetDefinition(*
target) : std::nullopt;
466std::vector<Location> WorkspaceIndex::references(std::string_view canonicalUri,
Position position,
467 bool includeDeclaration)
const {
469 if (
current ==
nullptr)
return {};
471 return target ? targetReferences(*
target, includeDeclaration) : std::vector<Location>{};
474std::optional<std::vector<TextEdit>> WorkspaceIndex::rename(std::string_view canonicalUri,
Position position,
475 std::string_view newName)
const {
476 if (!validIdentifier(newName))
return std::nullopt;
478 if (
current ==
nullptr)
return std::nullopt;
480 if (!
target || !targetDefinition(*
target))
return std::nullopt;
481 std::vector<TextEdit> edits;
482 for (
const Location& location : targetReferences(*
target,
true)) edits.push_back({location, std::string(newName)});
483 return edits.empty() ? std::nullopt : std::optional<std::vector<TextEdit>>(std::move(edits));
eve::action::ActionVfxBinding binding