Adds support for astypedef files, so we can register the interface as application interface, even when it uses functions that are not supported by angelscript itself.

This commit is contained in:
Deukhoofd 2021-10-23 14:03:31 +02:00
parent fac54acbc9
commit 220c6d0080
Signed by: Deukhoofd
GPG Key ID: F63E044490819F6F
16 changed files with 7372 additions and 177 deletions

View File

@ -0,0 +1,48 @@
{
"brackets": [
[
"(",
")"
],
[
"[",
"]"
],
[
"{",
"}"
]
],
"autoClosingPairs": [
[
"(",
")"
],
[
"[",
"]"
],
[
"{",
"}"
]
],
"surroundingPairs": [
[
"(",
")"
],
[
"[",
"]"
],
[
"{",
"}"
]
],
"indentationRules": {
"increaseIndentPattern": "^((?!\\/\\/).)*(\\{[^}\"'`]*|\\([^)\"'`]*|\\[[^\\]\"'`]*)$",
"decreaseIndentPattern": "^((?!.*?\\/\\*).*\\*/)?\\s*[\\)\\}\\]].*$"
}
}

View File

@ -37,7 +37,10 @@ export function activate(context: ExtensionContext) {
// Options to control the language client
let clientOptions: LanguageClientOptions = {
documentSelector: [{ scheme: 'file', language: 'Angelscript' }],
documentSelector: [
{ scheme: 'file', language: 'Angelscript' },
{ scheme: 'file', language: 'AngelscriptTypeDefinition' }
],
synchronize: {
fileEvents: workspace.createFileSystemWatcher('**/.clientrc'),
}

View File

@ -4,7 +4,7 @@
"author": "Deukhoofd",
"publisher": "Deukhoofd",
"license": "MIT",
"version": "1.0.1",
"version": "1.1.0",
"repository": {
"type": "git",
"url": "https://git.p-epsilon.com/Deukhoofd/AngelscriptLanguageServer"
@ -17,7 +17,8 @@
"vscode": "^1.43.0"
},
"activationEvents": [
"onLanguage:Angelscript"
"onLanguage:Angelscript",
"onLanguage:AngelscriptTypeDefinition"
],
"main": "./client/out/extension",
"contributes": {
@ -45,6 +46,13 @@
"*.as"
],
"configuration": "./language-configuration.json"
},
{
"id": "AngelscriptTypeDefinition",
"filenamePatterns": [
"*.astypedef"
],
"configuration": "./astypedef-configuration.json"
}
],
"grammars": [
@ -52,6 +60,11 @@
"language": "Angelscript",
"scopeName": "source.angelscript",
"path": "./syntaxes/as.tmGrammar"
},
{
"language": "AngelscriptTypeDefinition",
"scopeName": "source.angelscript",
"path": "./syntaxes/astypedef.tmGrammar"
}
]
},

View File

@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 3.17)
cmake_policy(SET CMP0042 NEW)
project(aslsp-native)
option(BUILD_TESTS "Whether or not to build the test executable" ON)
set (CMAKE_CXX_STANDARD 20)
set(BUILD_SHARED_LIBS OFF)
@ -25,3 +26,12 @@ target_include_directories(${PROJECT_NAME} PRIVATE ${NODE_ADDON_API_DIR})
# define NPI_VERSION
add_definitions(-DNAPI_VERSION=6)
if (BUILD_TESTS)
file(GLOB_RECURSE TEST_FILES "tests/*.cpp" "tests/*.hpp" "extern/doctest.hpp")
add_executable(aslsp-test ${TEST_FILES} ${SOURCE_FILES} ${AS_FILES})
target_link_libraries(aslsp-test -lpthread)
target_compile_definitions(aslsp-test PRIVATE TESTS_BUILD)
endif(BUILD_TESTS)
ADD_DEFINITIONS(-D AS_USE_ACCESSORS=1)

View File

@ -222,13 +222,13 @@ enum asEObjTypeFlags
enum asEBehaviours
{
// Value object memory management
asBEHAVE_CONSTRUCT,
asBEHAVE_LIST_CONSTRUCT,
asBEHAVE_DESTRUCT,
asBEHAVE_CONSTRUCT = 0,
asBEHAVE_LIST_CONSTRUCT = 1,
asBEHAVE_DESTRUCT = 2,
// Reference object memory management
asBEHAVE_FACTORY,
asBEHAVE_LIST_FACTORY,
asBEHAVE_FACTORY = 3,
asBEHAVE_LIST_FACTORY = 4,
asBEHAVE_ADDREF,
asBEHAVE_RELEASE,
asBEHAVE_GET_WEAKREF_FLAG,

5965
server/src/extern/doctest.hpp vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -53,7 +53,7 @@ function registerFiles(directoryName: string) {
if (f.isDirectory()) {
registerFiles(directoryName + path.sep + file)
} else {
if (path.extname(file) == ".as") {
if (path.extname(file) == ".as" || path.extname(file) == ".astypedef") {
const filepath = directoryName + path.sep + file;
var uri = "file://" + filepath;
if (!allFiles.get(uri)) {
@ -95,14 +95,15 @@ connection.onInitialize((params: InitializeParams) => {
return result;
});
let isInitialized =false;
connection.onInitialized(() => {
if (hasConfigurationCapability) {
// Register for all configuration changes.
connection.client.register(DidChangeConfigurationNotification.type, undefined);
}
if (hasWorkspaceFolderCapability) {
connection.workspace.
connection.workspace.onDidChangeWorkspaceFolders(_event => {
connection.workspace.connection.workspace.onDidChangeWorkspaceFolders(_event => {
connection.console.log('Workspace folder change event received.');
});
}
@ -113,7 +114,9 @@ connection.onInitialized(() => {
});
database.reset();
allFiles.forEach(loadScript);
validateBuild();
validateBuild().then(() => {
isInitialized = true;
});
})
});
@ -124,16 +127,18 @@ connection.onDidChangeConfiguration(change => {
// The content of a text document has changed. This event is emitted
// when the text document first opened or when its content has changed.
documents.onDidChangeContent(change => {
if (change.document.languageId != "Angelscript")
if (!isInitialized){
return;
}
if (change.document.languageId != "Angelscript" && change.document.languageId != "AngelscriptTypeDefinition")
return;
var current = allFiles.get(change.document.uri);
if (current != null && current != change.document){
if (current.version == 0){
if (current != null && current != change.document) {
if (current.version == 0) {
allFiles.set(change.document.uri, change.document);
return;
}
}
else if (current == null){
} else if (current == null) {
allFiles.set(change.document.uri, change.document);
}
@ -154,22 +159,26 @@ function convertSeverity(type: NativeWrapper.MessageType): DiagnosticSeverity {
}
function loadScript(textDocument: TextDocument): void {
if (textDocument.languageId != "Angelscript")
if (textDocument.languageId != "Angelscript" && textDocument.languageId != "AngelscriptTypeDefinition")
return;
if (!fs.existsSync(textDocument.uri.substr(7))){
if (!fs.existsSync(textDocument.uri.substr(7))) {
allFiles.delete(textDocument.uri);
return;
}
let text = textDocument.getText();
if (path.extname(textDocument.uri) == ".astypedef") {
database.loadTypeDef(textDocument.uri, text);
} else {
database.loadScript(textDocument.uri, text);
}
}
async function validateBuild(): Promise<void> {
console.log("Building");
var r = database.build();
if (r < -1) {
if (r <= -1) {
console.log(r.toString());
}
console.log("Building");
var messages = database.messages();
let diagnostics: Map<string, Diagnostic[]> = new Map<string, Diagnostic[]>();
@ -178,11 +187,11 @@ async function validateBuild(): Promise<void> {
let msg = messages[i];
if (msg.type == NativeWrapper.MessageType.Information)
continue;
if (msg.section == ""){
if (msg.type == NativeWrapper.MessageType.Error){
console.log('[ ' + msg.section + ' ] ' + msg.message);
if (msg.section == "") {
if (msg.type == NativeWrapper.MessageType.Error) {
connection.window.showErrorMessage(msg.message);
}
else if (msg.type == NativeWrapper.MessageType.Warning){
} else if (msg.type == NativeWrapper.MessageType.Warning) {
connection.window.showWarningMessage(msg.message);
}
continue;
@ -203,15 +212,14 @@ async function validateBuild(): Promise<void> {
message: msg.message,
source: 'Angelscript'
};
if (msg.type == NativeWrapper.MessageType.Error){
if (msg.type == NativeWrapper.MessageType.Error) {
connection.console.error(`[${msg.section}:${msg.row - 1},${msg.col - 1}] ${msg.message}`)
}
var diag = diagnostics.get(msg.section);
if (diag) {
diag.push(diagnostic)
}
else {
} else {
diagnostics.set(msg.section, [diagnostic]);
}
}
@ -219,10 +227,11 @@ async function validateBuild(): Promise<void> {
documents.all().forEach(element => {
let v = diagnostics.get(element.uri);
if (v)
connection.sendDiagnostics({ uri: element.uri, diagnostics: v });
connection.sendDiagnostics({uri: element.uri, diagnostics: v});
else
connection.sendDiagnostics({ uri: element.uri, diagnostics: [] });
connection.sendDiagnostics({uri: element.uri, diagnostics: []});
});
console.log("Finished building");
// Send the computed diagnostics to VSCode.
}

View File

@ -0,0 +1,215 @@
#include "Parser.hpp"
#include <iostream>
#include <regex>
#include <unordered_set>
#include "TypeDefResult.hpp"
#include "angelscript.h"
namespace ASTypeDefParser {
std::string trim(const std::string& str) {
size_t first = str.find_first_not_of(" \r\n\t");
if (std::string::npos == first) {
return str;
}
size_t last = str.find_last_not_of(" \r\n\t");
return str.substr(first, (last - first + 1));
}
static std::string ReadUpTo(const std::string& data, size_t& index, const std::unordered_set<char>& chars,
char& lastChar) {
std::stringstream s;
while (index < data.size()) {
lastChar = data.at(index);
if (lastChar == ' ' || lastChar == '\t' || lastChar == '\n' || lastChar == '\r') {
index++;
continue;
} else {
break;
}
}
while (index < data.size()) {
lastChar = data.at(index);
if (chars.contains(lastChar)) {
break;
}
s << lastChar;
index++;
}
return s.str();
}
#define Assert(expression) \
if (!(expression)) { \
throw std::logic_error("Failed assertion: " #expression); \
}
std::regex templateTypeCleaner("class ");
std::regex propertyCleaner(R"((.*?)\s*(\w+)\s*\{\s*(get)?\s*(const)?;*\s*(set)?\s*(const)?;*\s*\})");
static void ParseType(const std::string& type, TypeDefResult& result, const std::string& data, size_t& index) {
char end;
auto def = trim(ReadUpTo(data, index, {'{', ';'}, end));
long flags;
if (type == "type"){
flags = asOBJ_REF | asOBJ_NOCOUNT;
}
else if (type == "valuetype"){
flags = asOBJ_VALUE;
}
std::string className = def;
if (def.find('<') != std::string::npos) {
flags |= asOBJ_TEMPLATE;
className = std::regex_replace(className, templateTypeCleaner, "");
}
std::vector<std::string> definitions;
std::vector<std::tuple<int, std::string>> behaviours;
if (end == '{') {
index++;
while (index <= data.size()) {
if (data.at(index) == '}') {
break;
}
auto innerDef = trim(ReadUpTo(data, index, {';'}, end));
index++;
if (innerDef.empty()) {
continue;
}
if (innerDef == "}") {
break;
}
if (innerDef.find("behave") == 0){
char c;
size_t pos = 0;
ReadUpTo(innerDef, pos, {'b'}, c);
ReadUpTo(innerDef, pos, {' '}, c);
pos++;
auto behaviour = ReadUpTo(innerDef, pos, {' '}, c);
auto i = std::stoi(behaviour);
auto d = ReadUpTo(innerDef, pos, {';'}, c);
behaviours.emplace_back(i, d);
}
// Ugly hack for properties.
else if (innerDef.find('{') != std::string::npos) {
index--;
innerDef += ReadUpTo(data, index, {'}'}, end) + end;
index++;
std::smatch m;
if (std::regex_search(innerDef, m, propertyCleaner)) {
auto ret = m[1].str();
auto name = m[2].str();
if (m.size() > 3) {
if (m[3].str() == "get") {
bool isConst = m.size() > 4 && m[4].str() == "const";
auto decl = ret + " get_" + name + "() ";
if (isConst) {
decl += "const ";
}
decl += "property";
definitions.push_back(decl);
}
}
if (m.size() > 5) {
if (m[5].str() == "set") {
bool isConst = m.size() > 6 && m[6].str() == "const";
auto decl = "void set_" + name + "(" + ret + " value) ";
if (isConst) {
decl += "const ";
}
decl += "property";
definitions.push_back(decl);
}
}
}
} else {
definitions.push_back(innerDef);
}
}
}
index++;
result.StoreType(type, def, className, flags, definitions, behaviours);
}
static void ParseEnum(TypeDefResult& result, const std::string& data, size_t& index) {
char end;
auto def = trim(ReadUpTo(data, index, {'{'}, end));
index++;
std::vector<std::tuple<std::string, int>> values;
auto block = ReadUpTo(data, index, {'}'}, end);
size_t innerIndex = 0;
int enumValue = 0;
while (true) {
if (innerIndex >= block.size()) {
break;
}
auto statement = trim(ReadUpTo(block, innerIndex, {',', '}'}, end));
innerIndex++;
if (statement.empty()) {
continue;
}
size_t i = 0;
char statementEnd;
auto first = trim(ReadUpTo(statement, i, {'='}, statementEnd));
if (statementEnd == '=') {
i++;
auto val = trim(ReadUpTo(statement, i, {}, statementEnd));
auto num = std::stoi(val);
enumValue = num;
values.emplace_back(first, enumValue);
} else {
values.emplace_back(first, enumValue);
}
enumValue++;
if (end == '}') {
break;
}
}
result.StoreEnum(def, values);
}
static void ParseFunc(TypeDefResult& result, const std::string& data, size_t& index) {
char end;
auto statement = ReadUpTo(data, index, {';'}, end);
result.StoreFunc(statement);
}
TypeDefResult Parser::ParseAndRegister(TypeDefResult& result, const std::string& data) {
size_t index = 0;
while (true) {
if (index >= data.size()) {
break;
}
auto c = data[index];
if (c >= 'a' && c <= 'z') {
std::stringstream identifier;
while (c >= 'a' && c <= 'z') {
identifier << c;
index++;
if (index >= data.size()) {
break;
}
c = data[index];
}
auto str = identifier.str();
if (str == "type" || str == "valuetype") {
index++;
ParseType(str, result, data, index);
} else if (str == "enum") {
index++;
ParseEnum(result, data, index);
} else if (str == "func") {
index++;
ParseFunc(result, data, index);
}
}
index++;
}
return result;
}
}

View File

@ -0,0 +1,15 @@
#ifndef ASLSP_NATIVE_PARSER_HPP
#define ASLSP_NATIVE_PARSER_HPP
#include <string>
#include <vector>
#include "TypeDefResult.hpp"
#include "angelscript.h"
namespace ASTypeDefParser {
class Parser {
public:
static TypeDefResult ParseAndRegister(TypeDefResult& result, const std::string& data);
};
}
#endif // ASLSP_NATIVE_PARSER_HPP

View File

@ -0,0 +1,131 @@
#ifndef ASLSP_NATIVE_TYPEDEFRESULT_HPP
#define ASLSP_NATIVE_TYPEDEFRESULT_HPP
#include <angelscript.h>
#include <iostream>
#include <map>
#include <string>
#include <utility>
#include <vector>
namespace ASTypeDefParser {
class TypeDefResult {
struct Type {
Type(std::string objectType, std::string classDef, std::string className, int flags,
std::vector<std::string> definitions, std::vector<std::tuple<int, std::string>> behaviours)
: ClassDef(std::move(classDef)), ClassName(std::move(className)), Definitions(std::move(definitions)),
Flags(flags), ObjectType(std::move(objectType)), Behaviours(behaviours) {}
std::string ObjectType;
std::string ClassDef;
std::string ClassName;
long Flags;
std::vector<std::string> Definitions;
std::vector<std::tuple<int, std::string>> Behaviours;
};
struct Enum {
Enum(std::string name, std::vector<std::tuple<std::string, int>> values)
: Name(std::move(name)), Values(std::move(values)) {}
std::string Name;
std::vector<std::tuple<std::string, int>> Values;
};
std::vector<Type> _types;
std::vector<Enum> _enums;
std::vector<std::string> _functions;
public:
void StoreType(const std::string& objType, const std::string& classDef, const std::string& className,
long flags, const std::vector<std::string>& definitions,
const std::vector<std::tuple<int, std::string>>& behaviours) {
_types.emplace_back(objType, classDef, className, flags, definitions, behaviours);
}
void StoreEnum(const std::string& name, const std::vector<std::tuple<std::string, int>>& values) {
_enums.emplace_back(name, values);
}
void StoreFunc(const std::string& function) { _functions.push_back(function); }
void Clear() {
_types.clear();
_enums.clear();
_functions.clear();
}
void RegisterTypes(asIScriptEngine* engine) {
for (auto& t : _types) {
try {
auto size = 0;
if (t.ObjectType == "valuetype") {
size = 1;
}
engine->RegisterObjectType(t.ClassDef.c_str(), size, t.Flags);
} catch (std::exception&) {
}
}
for (auto& t : _enums) {
try {
engine->RegisterEnum(t.Name.c_str());
} catch (std::exception&) {
}
}
}
private:
// As opposed to normal functions, behaviours can be called from the builder. If we pass a default value,
// this causes a segfault. As such, we have an empty function for them.
static void BehaviourPlaceHolder(void*){}
static void RegisterTypeImplementation(asIScriptEngine* engine, Type& t) {
for (auto& def : t.Definitions) {
auto i = engine->RegisterObjectMethod(t.ClassName.c_str(), def.c_str(), {}, asCALL_THISCALL);
if (i < 0) {
return;
}
}
for (auto& def : t.Behaviours) {
auto call = asCALL_CDECL;
if (std::get<0>(def) == 0 || std::get<0>(def) == 2) {
call = asCALL_CDECL_OBJLAST;
}
auto i =
engine->RegisterObjectBehaviour(t.ClassName.c_str(), static_cast<asEBehaviours>(std::get<0>(def)),
std::get<1>(def).c_str(), asFUNCTION(BehaviourPlaceHolder), call);
if (i < 0) {
return;
}
}
}
public:
void RegisterImplementation(asIScriptEngine* engine) {
for (auto& t : _types) {
if ((t.Flags & asOBJ_TEMPLATE) == 0) {
continue;
}
RegisterTypeImplementation(engine, t);
}
for (auto& t : _types) {
if ((t.Flags & asOBJ_TEMPLATE) != 0) {
continue;
}
RegisterTypeImplementation(engine, t);
}
for (auto& t : _enums) {
for (auto& v : t.Values) {
engine->RegisterEnumValue(t.Name.c_str(), std::get<0>(v).c_str(), std::get<1>(v));
}
}
for (auto& f : _functions) {
if (engine->GetGlobalFunctionByDecl(f.c_str()) != nullptr) {
continue;
}
engine->RegisterGlobalFunction(f.c_str(), {}, asCALL_CDECL);
}
}
};
}
#endif // ASLSP_NATIVE_TYPEDEFRESULT_HPP

View File

@ -1,3 +1,4 @@
#if !TESTS_BUILD
#include <napi.h>
#include "Database.hpp"
@ -6,3 +7,4 @@ Napi::Object InitAll(Napi::Env env, Napi::Object exports) {
}
NODE_API_MODULE(aslsp, InitAll)
#endif

View File

@ -1,12 +1,16 @@
#if !TESTS_BUILD
#include "Database.hpp"
#include <sstream>
#include "../angelscript/extensions/scriptarray/scriptarray.h"
#include "../angelscript/extensions/scriptstdstring/scriptstdstring.h"
#include "ASTypeDefParser/Parser.hpp"
Napi::Object Database::Init(Napi::Env env, Napi::Object exports) {
Napi::Function func =
DefineClass(env, "Database",
{InstanceMethod("reset", &Database::Reset), InstanceMethod("loadScript", &Database::LoadScript),
InstanceMethod("build", &Database::Build), InstanceMethod("messages", &Database::GetMessages),
{InstanceMethod("reset", &Database::Reset), InstanceMethod("loadTypeDef", &Database::LoadTypeDef),
InstanceMethod("loadScript", &Database::LoadScript), InstanceMethod("build", &Database::Build),
InstanceMethod("messages", &Database::GetMessages),
InstanceMethod("setEngineProperty", &Database::SetEngineProperty)});
auto* constructor = new Napi::FunctionReference();
@ -21,13 +25,24 @@ void Database::MessageCallback(const asSMessageInfo* msg) {
_messages.push_back(new Diagnostic(msg->section, msg->row, msg->col, msg->type, msg->message));
}
Database::Database(const Napi::CallbackInfo& info) : ObjectWrap(info) {
void Database::SetupEngine() {
_engine = asCreateScriptEngine();
_builder = {};
_engine->SetMessageCallback(asMETHOD(Database, MessageCallback), this, asCALL_THISCALL);
_engine->SetEngineProperty(asEP_DISALLOW_EMPTY_LIST_ELEMENTS, true);
_engine->SetEngineProperty(asEP_DISALLOW_VALUE_ASSIGN_FOR_REF_TYPE, false);
_engine->SetEngineProperty(asEP_ALLOW_UNSAFE_REFERENCES, true);
_engine->SetEngineProperty(asEP_ALWAYS_IMPL_DEFAULT_CONSTRUCT, true);
_engine->SetEngineProperty(asEP_AUTO_GARBAGE_COLLECT, false);
_engine->SetEngineProperty(asEP_REQUIRE_ENUM_SCOPE, true);
_engine->SetEngineProperty(asEP_PROPERTY_ACCESSOR_MODE, 2);
_engine->SetEngineProperty(asEP_COMPILER_WARNINGS, 2);
RegisterStdString(_engine);
RegisterScriptArray(_engine, true);
_builder.StartNewModule(_engine, "Module");
}
Database::Database(const Napi::CallbackInfo& info) : ObjectWrap(info) {
Reset(info);
}
@ -39,8 +54,13 @@ void Database::SetEngineProperty(const Napi::CallbackInfo& info) {
}
void Database::Reset(const Napi::CallbackInfo&) {
std::lock_guard<std::mutex> lck(_lock);
if (_engine != nullptr){
_engine->DiscardModule("Module");
_builder.StartNewModule(_engine, "Module");
_engine->Release();
}
SetupEngine();
_result.Clear();
}
void Database::LoadScript(const Napi::CallbackInfo& info) {
@ -51,8 +71,21 @@ void Database::LoadScript(const Napi::CallbackInfo& info) {
_builder.AddSectionFromMemory(name.c_str(), script.c_str());
}
void Database::LoadTypeDef(const Napi::CallbackInfo& info) {
if (info.Length() < 2)
throw std::logic_error("Not enough arguments");
auto name = info[0].As<Napi::String>().Utf8Value();
auto script = info[1].As<Napi::String>().Utf8Value();
ASTypeDefParser::Parser::ParseAndRegister(_result, script);
}
Napi::Value Database::Build(const Napi::CallbackInfo& info) {
std::lock_guard<std::mutex> lck(_lock);
_messages.clear();
_result.RegisterTypes(_engine);
_result.RegisterImplementation(_engine);
_builder.BuildModule();
return Napi::Number::New(info.Env(), 0);
}
@ -70,3 +103,4 @@ Napi::Value Database::GetMessages(const Napi::CallbackInfo& info) {
return messages;
}
#endif

View File

@ -1,17 +1,21 @@
#ifndef AS_LSP_NATIVE_DATABASE_HPP
#define AS_LSP_NATIVE_DATABASE_HPP
#if !TESTS_BUILD
#include "../../node_modules/node-addon-api/napi.h"
#include "../angelscript/extensions/scriptbuilder/scriptbuilder.h"
#include "../angelscript/extensions/scriptstdstring/scriptstdstring.h"
#include "../angelscript/include/angelscript.h"
#include "ASTypeDefParser/TypeDefResult.hpp"
#include "Diagnostic.hpp"
class Database : public Napi::ObjectWrap<Database> {
private:
asIScriptEngine* _engine;
asIScriptEngine* _engine = nullptr;
CScriptBuilder _builder;
ASTypeDefParser::TypeDefResult _result;
std::vector<const Diagnostic*> _messages;
std::mutex _lock;
void SetupEngine();
void MessageCallback(const asSMessageInfo* msg);
public:
@ -21,8 +25,10 @@ public:
void SetEngineProperty(const Napi::CallbackInfo& info);
void Reset(const Napi::CallbackInfo& info);
void LoadScript(const Napi::CallbackInfo& info);
void LoadTypeDef(const Napi::CallbackInfo& info);
Napi::Value Build(const Napi::CallbackInfo& info);
Napi::Value GetMessages(const Napi::CallbackInfo& info);
};
#endif
#endif // AS_LSP_NATIVE_DATABASE_HPP

View File

@ -0,0 +1,418 @@
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include "../../extern/doctest.hpp"
#include "../../src/ASTypeDefParser/Parser.hpp"
TEST_CASE("Register Empty Type") {
asIScriptEngine* engine = asCreateScriptEngine();
ASTypeDefParser::TypeDefResult res;
ASTypeDefParser::Parser::ParseAndRegister(res, "type foo{}");
res.RegisterTypes(engine);
res.RegisterImplementation(engine);
REQUIRE(engine->GetTypeInfoByName("foo") != nullptr);
engine->Release();
}
void MessageCallback(const asSMessageInfo *msg, void *param)
{
const char *type = "ERR ";
if( msg->type == asMSGTYPE_WARNING )
type = "WARN";
else if( msg->type == asMSGTYPE_INFORMATION )
type = "INFO";
printf("%s (%d, %d) : %s : %s\n", msg->section, msg->row, msg->col, type, msg->message);
}
TEST_CASE("Register Empty Value Type") {
asIScriptEngine* engine = asCreateScriptEngine();
ASTypeDefParser::TypeDefResult res;
ASTypeDefParser::Parser::ParseAndRegister(res, "valuetype foo{}");
res.RegisterTypes(engine);
res.RegisterImplementation(engine);
REQUIRE(engine->GetTypeInfoByName("foo") != nullptr);
engine->Release();
}
TEST_CASE("Register Empty Type With Semicolon") {
asIScriptEngine* engine = asCreateScriptEngine();
ASTypeDefParser::TypeDefResult res;
ASTypeDefParser::Parser::ParseAndRegister(res, "type foo;");
res.RegisterTypes(engine);
res.RegisterImplementation(engine);
REQUIRE(engine->GetTypeInfoByName("foo") != nullptr);
engine->Release();
}
TEST_CASE("Register Multiple Empty Types") {
asIScriptEngine* engine = asCreateScriptEngine();
ASTypeDefParser::TypeDefResult res;
ASTypeDefParser::Parser::ParseAndRegister(res, "type foo{} type bar{}");
res.RegisterTypes(engine);
res.RegisterImplementation(engine);
REQUIRE(engine->GetTypeInfoByName("foo") != nullptr);
REQUIRE(engine->GetTypeInfoByName("bar") != nullptr);
engine->Release();
}
TEST_CASE("Register Type With Method") {
asIScriptEngine* engine = asCreateScriptEngine();
ASTypeDefParser::TypeDefResult res;
ASTypeDefParser::Parser::ParseAndRegister(res, R"(
type foo {
int Bar(int a, int b);
}
)");
res.RegisterTypes(engine);
res.RegisterImplementation(engine);
auto type = engine->GetTypeInfoByName("foo");
REQUIRE(type != nullptr);
auto method = type->GetMethodByName("Bar");
REQUIRE(method != nullptr);
REQUIRE_EQ(method->GetParamCount(), 2);
REQUIRE_EQ(std::string(method->GetDeclaration(false, false, true)), "int Bar(int a, int b)");
engine->Release();
}
TEST_CASE("Register Type With Multiple Methods") {
asIScriptEngine* engine = asCreateScriptEngine();
ASTypeDefParser::TypeDefResult res;
ASTypeDefParser::Parser::ParseAndRegister(res, R"(
type foo {
int Bar(int a, int b);
int Zet(int a, int b);
}
)");
res.RegisterTypes(engine);
res.RegisterImplementation(engine);
auto type = engine->GetTypeInfoByName("foo");
REQUIRE(type != nullptr);
auto method = type->GetMethodByName("Bar");
REQUIRE(method != nullptr);
REQUIRE_EQ(method->GetParamCount(), 2);
REQUIRE_EQ(std::string(method->GetDeclaration(false, false, true)), "int Bar(int a, int b)");
method = type->GetMethodByName("Zet");
REQUIRE(method != nullptr);
engine->Release();
}
TEST_CASE("Register Generic Type With Method") {
asIScriptEngine* engine = asCreateScriptEngine();
ASTypeDefParser::TypeDefResult res;
ASTypeDefParser::Parser::ParseAndRegister(res, R"(
type foo<class T> {
T@ Bar(int a, int b);
}
)");
res.RegisterTypes(engine);
res.RegisterImplementation(engine);
auto type = engine->GetTypeInfoByName("foo");
REQUIRE(type != nullptr);
REQUIRE(type == engine->GetTypeInfoByDecl("foo<T>"));
auto method = type->GetMethodByName("Bar");
REQUIRE(method != nullptr);
REQUIRE_EQ(method->GetParamCount(), 2);
REQUIRE_EQ(std::string(method->GetDeclaration(false, false, true)), "T@ Bar(int a, int b)");
engine->Release();
}
TEST_CASE("Register Generic Type With Multiple implementations") {
asIScriptEngine* engine = asCreateScriptEngine();
ASTypeDefParser::TypeDefResult res;
ASTypeDefParser::Parser::ParseAndRegister(res, R"(
type foo<class T> {
T@ Bar(int a, int b);
}
type a {
foo<int> GetFoo();
}
type b {
foo<int> GetBar();
}
)");
res.RegisterTypes(engine);
res.RegisterImplementation(engine);
auto type = engine->GetTypeInfoByName("foo");
REQUIRE(type != nullptr);
REQUIRE(type == engine->GetTypeInfoByDecl("foo<T>"));
auto method = type->GetMethodByName("Bar");
REQUIRE(method != nullptr);
REQUIRE_EQ(method->GetParamCount(), 2);
REQUIRE_EQ(std::string(method->GetDeclaration(false, false, true)), "T@ Bar(int a, int b)");
engine->Release();
}
TEST_CASE("Register Type With Get Property") {
asIScriptEngine* engine = asCreateScriptEngine();
ASTypeDefParser::TypeDefResult res;
ASTypeDefParser::Parser::ParseAndRegister(res, R"(
type foo {
int Bar { get; };
}
)");
res.RegisterTypes(engine);
res.RegisterImplementation(engine);
auto type = engine->GetTypeInfoByName("foo");
REQUIRE(type != nullptr);
auto method = type->GetMethodByName("get_Bar");
REQUIRE(method != nullptr);
REQUIRE_EQ(std::string(method->GetDeclaration(false, false, true)), "int get_Bar()");
engine->Release();
}
TEST_CASE("Register Type With Get Const Property") {
asIScriptEngine* engine = asCreateScriptEngine();
ASTypeDefParser::TypeDefResult res;
ASTypeDefParser::Parser::ParseAndRegister(res, R"(
type foo {
int Bar { get const; };
}
)");
res.RegisterTypes(engine);
res.RegisterImplementation(engine);
auto type = engine->GetTypeInfoByName("foo");
REQUIRE(type != nullptr);
auto method = type->GetMethodByName("get_Bar");
REQUIRE(method != nullptr);
REQUIRE_EQ(std::string(method->GetDeclaration(false, false, true)), "int get_Bar() const");
engine->Release();
}
TEST_CASE("Register Type With Set Property") {
asIScriptEngine* engine = asCreateScriptEngine();
ASTypeDefParser::TypeDefResult res;
ASTypeDefParser::Parser::ParseAndRegister(res, R"(
type foo {
int Bar { set; };
}
)");
res.RegisterTypes(engine);
res.RegisterImplementation(engine);
auto type = engine->GetTypeInfoByName("foo");
REQUIRE(type != nullptr);
auto method = type->GetMethodByName("set_Bar");
REQUIRE(method != nullptr);
REQUIRE_EQ(std::string(method->GetDeclaration(false, false, true)), "void set_Bar(int value)");
engine->Release();
}
TEST_CASE("Register Type With Set Const Property") {
asIScriptEngine* engine = asCreateScriptEngine();
ASTypeDefParser::TypeDefResult res;
ASTypeDefParser::Parser::ParseAndRegister(res, R"(
type foo {
int Bar { set const; };
}
)");
res.RegisterTypes(engine);
res.RegisterImplementation(engine);
auto type = engine->GetTypeInfoByName("foo");
REQUIRE(type != nullptr);
auto method = type->GetMethodByName("set_Bar");
REQUIRE(method != nullptr);
REQUIRE_EQ(std::string(method->GetDeclaration(false, false, true)), "void set_Bar(int value) const");
engine->Release();
}
TEST_CASE("Register Type With Set Property") {
asIScriptEngine* engine = asCreateScriptEngine();
ASTypeDefParser::TypeDefResult res;
ASTypeDefParser::Parser::ParseAndRegister(res, R"(
type foo {
int Bar { set; };
}
)");
res.RegisterTypes(engine);
res.RegisterImplementation(engine);
auto type = engine->GetTypeInfoByName("foo");
REQUIRE(type != nullptr);
auto method = type->GetMethodByName("set_Bar");
REQUIRE(method != nullptr);
REQUIRE_EQ(std::string(method->GetDeclaration(false, false, true)), "void set_Bar(int value)");
engine->Release();
}
TEST_CASE("Register Type With Getter and Setter Property") {
asIScriptEngine* engine = asCreateScriptEngine();
ASTypeDefParser::TypeDefResult res;
ASTypeDefParser::Parser::ParseAndRegister(res, R"(
type foo {
int Bar { get; set; };
}
)");
res.RegisterTypes(engine);
res.RegisterImplementation(engine);
auto type = engine->GetTypeInfoByName("foo");
REQUIRE(type != nullptr);
auto method = type->GetMethodByName("get_Bar");
REQUIRE(method != nullptr);
REQUIRE_EQ(std::string(method->GetDeclaration(false, false, true)), "int get_Bar()");
method = type->GetMethodByName("set_Bar");
REQUIRE(method != nullptr);
REQUIRE_EQ(std::string(method->GetDeclaration(false, false, true)), "void set_Bar(int value)");
engine->Release();
}
TEST_CASE("Register Type With Multiple Getter and Setter Properties") {
asIScriptEngine* engine = asCreateScriptEngine();
ASTypeDefParser::TypeDefResult res;
ASTypeDefParser::Parser::ParseAndRegister(res, R"(
type foo {
int Bar { get; set; };
int Zet { get; set; };
}
)");
res.RegisterTypes(engine);
res.RegisterImplementation(engine);
auto type = engine->GetTypeInfoByName("foo");
REQUIRE(type != nullptr);
auto method = type->GetMethodByName("get_Bar");
REQUIRE(method != nullptr);
REQUIRE_EQ(std::string(method->GetDeclaration(false, false, true)), "int get_Bar()");
method = type->GetMethodByName("set_Bar");
REQUIRE(method != nullptr);
REQUIRE_EQ(std::string(method->GetDeclaration(false, false, true)), "void set_Bar(int value)");
method = type->GetMethodByName("get_Zet");
REQUIRE(method != nullptr);
REQUIRE_EQ(std::string(method->GetDeclaration(false, false, true)), "int get_Zet()");
method = type->GetMethodByName("set_Zet");
REQUIRE(method != nullptr);
REQUIRE_EQ(std::string(method->GetDeclaration(false, false, true)), "void set_Zet(int value)");
engine->Release();
}
TEST_CASE("Register Empty Enum") {
asIScriptEngine* engine = asCreateScriptEngine();
ASTypeDefParser::TypeDefResult res;
ASTypeDefParser::Parser::ParseAndRegister(res, R"(
enum foo {
}
)");
res.RegisterTypes(engine);
res.RegisterImplementation(engine);
auto type = engine->GetTypeInfoByName("foo");
REQUIRE(type != nullptr);
REQUIRE(type->GetTypeId() % asOBJ_ENUM != 0);
engine->Release();
}
TEST_CASE("Register Enum with value") {
asIScriptEngine* engine = asCreateScriptEngine();
ASTypeDefParser::TypeDefResult res;
ASTypeDefParser::Parser::ParseAndRegister(res, R"(
enum foo {
One
}
)");
res.RegisterTypes(engine);
res.RegisterImplementation(engine);
auto type = engine->GetTypeInfoByName("foo");
REQUIRE(type != nullptr);
REQUIRE(type->GetTypeId() % asOBJ_ENUM != 0);
int i = -1;
REQUIRE_EQ(std::string(type->GetEnumValueByIndex(0, &i)), "One");
REQUIRE_EQ(i, 0);
engine->Release();
}
TEST_CASE("Register Enum with multiple values") {
asIScriptEngine* engine = asCreateScriptEngine();
ASTypeDefParser::TypeDefResult res;
ASTypeDefParser::Parser::ParseAndRegister(res, R"(
enum foo {
One,
Two
}
)");
res.RegisterTypes(engine);
res.RegisterImplementation(engine);
auto type = engine->GetTypeInfoByName("foo");
REQUIRE(type != nullptr);
REQUIRE(type->GetTypeId() % asOBJ_ENUM != 0);
int i = -1;
REQUIRE_EQ(std::string(type->GetEnumValueByIndex(0, &i)), "One");
REQUIRE_EQ(i, 0);
REQUIRE_EQ(std::string(type->GetEnumValueByIndex(1, &i)), "Two");
REQUIRE_EQ(i, 1);
engine->Release();
}
TEST_CASE("Register Enum with explicit value") {
asIScriptEngine* engine = asCreateScriptEngine();
ASTypeDefParser::TypeDefResult res;
ASTypeDefParser::Parser::ParseAndRegister(res, R"(
enum foo {
One = 5
}
)");
res.RegisterTypes(engine);
res.RegisterImplementation(engine);
auto type = engine->GetTypeInfoByName("foo");
REQUIRE(type != nullptr);
REQUIRE(type->GetTypeId() % asOBJ_ENUM != 0);
int i = -1;
REQUIRE_EQ(std::string(type->GetEnumValueByIndex(0, &i)), "One");
REQUIRE_EQ(i, 5);
engine->Release();
}
TEST_CASE("Register Enum with multiple explicit values") {
asIScriptEngine* engine = asCreateScriptEngine();
ASTypeDefParser::TypeDefResult res;
ASTypeDefParser::Parser::ParseAndRegister(res, R"(
enum foo {
One = 5,
Two = 1,
Three = -1000,
}
)");
res.RegisterTypes(engine);
res.RegisterImplementation(engine);
auto type = engine->GetTypeInfoByName("foo");
REQUIRE(type != nullptr);
REQUIRE(type->GetTypeId() % asOBJ_ENUM != 0);
int i = -1;
REQUIRE_EQ(std::string(type->GetEnumValueByIndex(0, &i)), "One");
REQUIRE_EQ(i, 5);
REQUIRE_EQ(std::string(type->GetEnumValueByIndex(1, &i)), "Two");
REQUIRE_EQ(i, 1);
REQUIRE_EQ(std::string(type->GetEnumValueByIndex(2, &i)), "Three");
REQUIRE_EQ(i, -1000);
engine->Release();
}
TEST_CASE("Register Global Function") {
asIScriptEngine* engine = asCreateScriptEngine();
ASTypeDefParser::TypeDefResult res;
ASTypeDefParser::Parser::ParseAndRegister(res, R"(
func void print(int i);
)");
res.RegisterTypes(engine);
res.RegisterImplementation(engine);
auto func = engine->GetGlobalFunctionByDecl("void print(int i)");
REQUIRE(func != nullptr);
engine->Release();
}
TEST_CASE("Register Type With Behaviour") {
asIScriptEngine* engine = asCreateScriptEngine();
engine->SetMessageCallback(asFUNCTION(MessageCallback), 0, asCALL_CDECL);
ASTypeDefParser::TypeDefResult res;
ASTypeDefParser::Parser::ParseAndRegister(res, R"(
type fooClass {
behave 4 fooClass@ f(int &in) {repeat int};
}
)");
res.RegisterTypes(engine);
res.RegisterImplementation(engine);
auto type = engine->GetTypeInfoByName("fooClass");
REQUIRE(type != nullptr);
REQUIRE_EQ(type->GetBehaviourCount(), 1);
asEBehaviours b;
REQUIRE_EQ(std::string(type->GetBehaviourByIndex(0, &b)->GetDeclaration()), "fooClass@ $list(int&in) { repeat int }");
engine->Release();
}

View File

@ -51,6 +51,7 @@ export interface Message {
export interface ScriptDatabase {
setEngineProperty(property: ASEngineProperty, value: number): void;
reset(): void;
loadTypeDef(name: string, script: string): void;
loadScript(name: string, script: string): void;
build(): number;
messages(): Message[];

View File

@ -0,0 +1,325 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>comment</key>
<string>Made by Deukhoofd</string>
<key>fileTypes</key>
<array>
<string>as</string>
</array>
<key>firstLineMatch</key>
<string>-\*- C\+\+ -\*-</string>
<key>foldingStartMarker</key>
<string>(?x)
/\*\*(?!\*)
|^(?![^{]*?//|[^{]*?/\*(?!.*?\*/.*?\{)).*?\{\s*($|//|/\*(?!.*?\*/.*\S))
</string>
<key>foldingStopMarker</key>
<string>(?&lt;!\*)\*\*/|^\s*\}</string>
<key>keyEquivalent</key>
<string>^~C</string>
<key>name</key>
<string>AngelScript</string>
<key>patterns</key>
<array>
<!-- <dict>
<key>include</key>
<string>source.c</string>
</dict> -->
<dict>
<key>captures</key>
<dict>
<key>1</key>
<dict>
<key>name</key>
<string>punctuation.definition.comment.angelscript</string>
</dict>
</dict>
<key>match</key>
<string>(//).*$\n?</string>
<key>name</key>
<string>comment.line.double-slash.angelscript</string>
</dict>
<dict>
<key>begin</key>
<string>/\*</string>
<key>captures</key>
<dict>
<key>0</key>
<dict>
<key>name</key>
<string>punctuation.definition.comment.angelscript</string>
</dict>
</dict>
<key>end</key>
<string>\*/</string>
<key>name</key>
<string>comment.block.angelscript</string>
</dict>
<dict>
<key>begin</key>
<string>"""</string>
<key>beginCaptures</key>
<dict>
<key>0</key>
<dict>
<key>name</key>
<string>punctuation.definition.string.begin.angelscript</string>
</dict>
</dict>
<key>end</key>
<string>"""</string>
<key>endCaptures</key>
<dict>
<key>0</key>
<dict>
<key>name</key>
<string>punctuation.definition.string.end.angelscript</string>
</dict>
</dict>
<key>name</key>
<string>string.quoted.double.angelscript</string>
</dict>
<dict>
<key>begin</key>
<string>"</string>
<key>beginCaptures</key>
<dict>
<key>0</key>
<dict>
<key>name</key>
<string>punctuation.definition.string.begin.angelscript</string>
</dict>
</dict>
<key>end</key>
<string>"</string>
<key>endCaptures</key>
<dict>
<key>0</key>
<dict>
<key>name</key>
<string>punctuation.definition.string.end.angelscript</string>
</dict>
</dict>
<key>name</key>
<string>string.quoted.double.angelscript</string>
<key>patterns</key>
<array>
<dict>
<key>match</key>
<string>\\.</string>
<key>name</key>
<string>constant.character.escape.angelscript</string>
</dict>
</array>
</dict>
<dict>
<key>begin</key>
<string>'</string>
<key>beginCaptures</key>
<dict>
<key>0</key>
<dict>
<key>name</key>
<string>punctuation.definition.string.begin.angelscript</string>
</dict>
</dict>
<key>end</key>
<string>'</string>
<key>endCaptures</key>
<dict>
<key>0</key>
<dict>
<key>name</key>
<string>punctuation.definition.string.end.angelscript</string>
</dict>
</dict>
<key>name</key>
<string>string.quoted.single.angelscript</string>
<key>patterns</key>
<array>
<dict>
<key>match</key>
<string>\\.</string>
<key>name</key>
<string>constant.character.escape.angelscript</string>
</dict>
</array>
</dict>
<dict>
<key>match</key>
<string>(~|!|&amp;&amp;|\|\|)</string>
<key>name</key>
<string>keyword.operator.logical.angelscript</string>
</dict>
<dict>
<key>match</key>
<string>[-!%&amp;&gt;&lt;@*+=/?:]</string>
<key>name</key>
<string>keyword.operator.symbolic.angelscript</string>
</dict>
<dict>
<key>match</key>
<string>\b(for|in|break|continue|while|do|return|if|else|case|switch|namespace)\b</string>
<key>name</key>
<string>keyword.control.angelscript</string>
</dict>
<dict>
<key>match</key>
<string>\b(is|cast)\b</string>
<key>name</key>
<string>keyword.operator.angelscript</string>
</dict>
<dict>
<key>match</key>
<string>\b(or|and|xor|not)\b</string>
<key>name</key>
<string>keyword.operator.logical.angelscript</string>
</dict>
<dict>
<key>match</key>
<string>\b(get|in|inout|out|override|set|private|public|const|default|final|shared|mixin)\b</string>
<key>name</key>
<string>storage.modifier.angelscript</string>
</dict>
<dict>
<key>match</key>
<string>\b(type|enum|func|void|bool|int|int8|int16|int32|int64|uint|uint8|uint16|uint32|uint64|string|ref|array|double|float|auto|dictionary)\b</string>
<key>name</key>
<string>storage.type.angelscript</string>
</dict>
<dict>
<key>match</key>
<string>[A-Za-z][A-Za-z0-9]+@</string>
<key>name</key>
<string>storage.type.angelscript</string>
</dict>
<dict>
<key>match</key>
<string>\b(null|true|false)\b</string>
<key>name</key>
<string>constant.language.angelscript</string>
</dict>
<dict>
<key>match</key>
<string>\b(this|super)\b</string>
<key>name</key>
<string>variable.language.angelscript</string>
</dict>
<dict>
<key>match</key>
<string>\b(import|from)\b</string>
<key>name</key>
<string>keyword.control.import.angelscript</string>
</dict>
<dict>
<key>match</key>
<string>\b((0(x|X)[0-9a-fA-F]*)|(([0-9]+\.?[0-9]*)|(\.[0-9]+))((e|E)(\+|-)?[0-9]+)?)(L|l|UL|ul|u|U|F|f)?\b</string>
<key>name</key>
<string>constant.numeric.angelscript</string>
</dict>
<dict>
<key>match</key>
<string>^\s*\#([a-zA-Z_0-9]*)?</string>
<key>name</key>
<string>keyword.control.import.angelscript</string>
</dict>
<!-- This became too much. -->
<!--
<dict>
<key>match</key>
<string>(@[a-zA-Z_]+[0-9a-zA-Z_]*)</string>
<key>name</key>
<string>variable.other.pointer.angelscript</string>
</dict>
-->
<dict>
<key>captures</key>
<dict>
<key>1</key>
<dict>
<key>name</key>
<string>markup.heading.angelscript</string>
</dict>
</dict>
<key>match</key>
<string>^\s*\[(.*)\]\s*?</string>
<key>name</key>
<string>meta.metadata.angelscript</string>
</dict>
<dict>
<key>match</key>
<string>\.[a-zA-Z_][a-zA-Z_0-9]*\b(?!\s*\()</string>
<key>name</key>
<string>variable.other.dot-access.angelscript</string>
</dict>
<dict>
<key>captures</key>
<dict>
<key>1</key>
<dict>
<key>name</key>
<string>storage.type.class.angelscript</string>
</dict>
<key>2</key>
<dict>
<key>name</key>
<string>entity.name.type.class.angelscript</string>
</dict>
<key>3</key>
<dict>
<key>name</key>
<string>entity.other.inherited-class.angelscript</string>
</dict>
<key>5</key>
<dict>
<key>name</key>
<string>entity.other.inherited-class.angelscript</string>
</dict>
<key>7</key>
<dict>
<key>name</key>
<string>entity.other.inherited-class.angelscript</string>
</dict>
<key>9</key>
<dict>
<key>name</key>
<string>entity.other.inherited-class.angelscript</string>
</dict>
<key>11</key>
<dict>
<key>name</key>
<string>entity.other.inherited-class.angelscript</string>
</dict>
</dict>
<key>match</key>
<string>\b(class|interface)\s+([a-zA-Z_0-9]*)(?:\s*:\s*([a-zA-Z_0-9]*)(\s*,\s*([a-zA-Z_0-9]*))?(\s*,\s*([a-zA-Z_0-9]*))?(\s*,\s*([a-zA-Z_0-9]*))?(\s*,\s*([a-zA-Z_0-9]*))?)?</string>
<key>name</key>
<string>meta.class.angelscript</string>
</dict>
<dict>
<key>captures</key>
<dict>
<key>2</key>
<dict>
<key>name</key>
<string>meta.function-call.angelscript</string>
</dict>
</dict>
<key>match</key>
<string>(\b|\.)([a-zA-Z_][a-zA-Z_0-9]*)\b(\s*\()</string>
</dict>
<dict>
<key>match</key>
<string>\b([A-Z][A-Z0-9_]+)\b</string>
<key>name</key>
<string>constant.other.angelscript</string>
</dict>
</array>
<key>scopeName</key>
<string>source.angelscript</string>
<key>uuid</key>
<string>69E25C06-FA48-4207-BF35-11353888A8F6</string>
</dict>
</plist>