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 // Options to control the language client
let clientOptions: LanguageClientOptions = { let clientOptions: LanguageClientOptions = {
documentSelector: [{ scheme: 'file', language: 'Angelscript' }], documentSelector: [
{ scheme: 'file', language: 'Angelscript' },
{ scheme: 'file', language: 'AngelscriptTypeDefinition' }
],
synchronize: { synchronize: {
fileEvents: workspace.createFileSystemWatcher('**/.clientrc'), fileEvents: workspace.createFileSystemWatcher('**/.clientrc'),
} }

View File

@ -4,7 +4,7 @@
"author": "Deukhoofd", "author": "Deukhoofd",
"publisher": "Deukhoofd", "publisher": "Deukhoofd",
"license": "MIT", "license": "MIT",
"version": "1.0.1", "version": "1.1.0",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://git.p-epsilon.com/Deukhoofd/AngelscriptLanguageServer" "url": "https://git.p-epsilon.com/Deukhoofd/AngelscriptLanguageServer"
@ -17,7 +17,8 @@
"vscode": "^1.43.0" "vscode": "^1.43.0"
}, },
"activationEvents": [ "activationEvents": [
"onLanguage:Angelscript" "onLanguage:Angelscript",
"onLanguage:AngelscriptTypeDefinition"
], ],
"main": "./client/out/extension", "main": "./client/out/extension",
"contributes": { "contributes": {
@ -45,6 +46,13 @@
"*.as" "*.as"
], ],
"configuration": "./language-configuration.json" "configuration": "./language-configuration.json"
},
{
"id": "AngelscriptTypeDefinition",
"filenamePatterns": [
"*.astypedef"
],
"configuration": "./astypedef-configuration.json"
} }
], ],
"grammars": [ "grammars": [
@ -52,6 +60,11 @@
"language": "Angelscript", "language": "Angelscript",
"scopeName": "source.angelscript", "scopeName": "source.angelscript",
"path": "./syntaxes/as.tmGrammar" "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) cmake_policy(SET CMP0042 NEW)
project(aslsp-native) project(aslsp-native)
option(BUILD_TESTS "Whether or not to build the test executable" ON)
set (CMAKE_CXX_STANDARD 20) set (CMAKE_CXX_STANDARD 20)
set(BUILD_SHARED_LIBS OFF) set(BUILD_SHARED_LIBS OFF)
@ -24,4 +25,13 @@ string(REPLACE "\"" "" NODE_ADDON_API_DIR ${NODE_ADDON_API_DIR})
target_include_directories(${PROJECT_NAME} PRIVATE ${NODE_ADDON_API_DIR}) target_include_directories(${PROJECT_NAME} PRIVATE ${NODE_ADDON_API_DIR})
# define NPI_VERSION # define NPI_VERSION
add_definitions(-DNAPI_VERSION=6) 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 enum asEBehaviours
{ {
// Value object memory management // Value object memory management
asBEHAVE_CONSTRUCT, asBEHAVE_CONSTRUCT = 0,
asBEHAVE_LIST_CONSTRUCT, asBEHAVE_LIST_CONSTRUCT = 1,
asBEHAVE_DESTRUCT, asBEHAVE_DESTRUCT = 2,
// Reference object memory management // Reference object memory management
asBEHAVE_FACTORY, asBEHAVE_FACTORY = 3,
asBEHAVE_LIST_FACTORY, asBEHAVE_LIST_FACTORY = 4,
asBEHAVE_ADDREF, asBEHAVE_ADDREF,
asBEHAVE_RELEASE, asBEHAVE_RELEASE,
asBEHAVE_GET_WEAKREF_FLAG, 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

@ -3,20 +3,20 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */ * ------------------------------------------------------------------------------------------ */
import { import {
createConnection, createConnection,
TextDocuments, TextDocuments,
Diagnostic, Diagnostic,
DiagnosticSeverity, DiagnosticSeverity,
ProposedFeatures, ProposedFeatures,
InitializeParams, InitializeParams,
DidChangeConfigurationNotification, DidChangeConfigurationNotification,
TextDocumentSyncKind, TextDocumentSyncKind,
InitializeResult, InitializeResult,
Position, Position,
} from 'vscode-languageserver'; } from 'vscode-languageserver';
import { import {
TextDocument TextDocument
} from 'vscode-languageserver-textdocument'; } from 'vscode-languageserver-textdocument';
import * as NativeWrapper from './wrapper' import * as NativeWrapper from './wrapper'
@ -47,74 +47,77 @@ let hasWorkspaceFolderCapability: boolean = false;
let hasDiagnosticRelatedInformationCapability: boolean = false; let hasDiagnosticRelatedInformationCapability: boolean = false;
function registerFiles(directoryName: string) { function registerFiles(directoryName: string) {
let files = fs.readdirSync(directoryName); let files = fs.readdirSync(directoryName);
files.forEach(function (file) { files.forEach(function (file) {
let f = fs.statSync(directoryName + path.sep + file); let f = fs.statSync(directoryName + path.sep + file);
if (f.isDirectory()) { if (f.isDirectory()) {
registerFiles(directoryName + path.sep + file) registerFiles(directoryName + path.sep + file)
} else { } else {
if (path.extname(file) == ".as") { if (path.extname(file) == ".as" || path.extname(file) == ".astypedef") {
const filepath = directoryName + path.sep + file; const filepath = directoryName + path.sep + file;
var uri = "file://" + filepath; var uri = "file://" + filepath;
if (!allFiles.get(uri)) { if (!allFiles.get(uri)) {
let td = TextDocument.create(uri, "Angelscript", 0, fs.readFileSync(filepath, 'utf8')); let td = TextDocument.create(uri, "Angelscript", 0, fs.readFileSync(filepath, 'utf8'));
allFiles.set(uri, td); allFiles.set(uri, td);
} }
} }
} }
}) })
} }
connection.onInitialize((params: InitializeParams) => { connection.onInitialize((params: InitializeParams) => {
let capabilities = params.capabilities; let capabilities = params.capabilities;
hasConfigurationCapability = !!( hasConfigurationCapability = !!(
capabilities.workspace && !!capabilities.workspace.configuration capabilities.workspace && !!capabilities.workspace.configuration
); );
hasWorkspaceFolderCapability = !!( hasWorkspaceFolderCapability = !!(
capabilities.workspace && !!capabilities.workspace.workspaceFolders capabilities.workspace && !!capabilities.workspace.workspaceFolders
); );
hasDiagnosticRelatedInformationCapability = !!( hasDiagnosticRelatedInformationCapability = !!(
capabilities.textDocument && capabilities.textDocument &&
capabilities.textDocument.publishDiagnostics && capabilities.textDocument.publishDiagnostics &&
capabilities.textDocument.publishDiagnostics.relatedInformation capabilities.textDocument.publishDiagnostics.relatedInformation
); );
const result: InitializeResult = { const result: InitializeResult = {
capabilities: { capabilities: {
textDocumentSync: TextDocumentSyncKind.Incremental, textDocumentSync: TextDocumentSyncKind.Incremental,
} }
}; };
if (hasWorkspaceFolderCapability) { if (hasWorkspaceFolderCapability) {
result.capabilities.workspace = { result.capabilities.workspace = {
workspaceFolders: { workspaceFolders: {
supported: true supported: true
} }
}; };
} }
return result; return result;
}); });
connection.onInitialized(() => { let isInitialized =false;
if (hasConfigurationCapability) {
// Register for all configuration changes.
connection.client.register(DidChangeConfigurationNotification.type, undefined);
}
if (hasWorkspaceFolderCapability) {
connection.workspace.
connection.workspace.onDidChangeWorkspaceFolders(_event => {
connection.console.log('Workspace folder change event received.');
});
}
connection.workspace.getWorkspaceFolders().then((ws) => { connection.onInitialized(() => {
ws?.forEach(element => { if (hasConfigurationCapability) {
registerFiles(element.uri.substr(7)) // Register for all configuration changes.
}); connection.client.register(DidChangeConfigurationNotification.type, undefined);
database.reset(); }
allFiles.forEach(loadScript); if (hasWorkspaceFolderCapability) {
validateBuild(); connection.workspace.connection.workspace.onDidChangeWorkspaceFolders(_event => {
}) connection.console.log('Workspace folder change event received.');
});
}
connection.workspace.getWorkspaceFolders().then((ws) => {
ws?.forEach(element => {
registerFiles(element.uri.substr(7))
});
database.reset();
allFiles.forEach(loadScript);
validateBuild().then(() => {
isInitialized = true;
});
})
}); });
@ -124,111 +127,117 @@ connection.onDidChangeConfiguration(change => {
// The content of a text document has changed. This event is emitted // The content of a text document has changed. This event is emitted
// when the text document first opened or when its content has changed. // when the text document first opened or when its content has changed.
documents.onDidChangeContent(change => { documents.onDidChangeContent(change => {
if (change.document.languageId != "Angelscript") if (!isInitialized){
return; return;
var current = allFiles.get(change.document.uri); }
if (current != null && current != change.document){ if (change.document.languageId != "Angelscript" && change.document.languageId != "AngelscriptTypeDefinition")
if (current.version == 0){ return;
allFiles.set(change.document.uri, change.document); var current = allFiles.get(change.document.uri);
return; if (current != null && current != change.document) {
} if (current.version == 0) {
} allFiles.set(change.document.uri, change.document);
else if (current == null){ return;
allFiles.set(change.document.uri, change.document); }
} } else if (current == null) {
allFiles.set(change.document.uri, change.document);
}
database.reset(); database.reset();
allFiles.forEach(loadScript); allFiles.forEach(loadScript);
validateBuild(); validateBuild();
}); });
function convertSeverity(type: NativeWrapper.MessageType): DiagnosticSeverity { function convertSeverity(type: NativeWrapper.MessageType): DiagnosticSeverity {
switch (type) { switch (type) {
case NativeWrapper.MessageType.Information: case NativeWrapper.MessageType.Information:
return DiagnosticSeverity.Information; return DiagnosticSeverity.Information;
case NativeWrapper.MessageType.Warning: case NativeWrapper.MessageType.Warning:
return DiagnosticSeverity.Warning; return DiagnosticSeverity.Warning;
case NativeWrapper.MessageType.Error: case NativeWrapper.MessageType.Error:
return DiagnosticSeverity.Error; return DiagnosticSeverity.Error;
} }
} }
function loadScript(textDocument: TextDocument): void { function loadScript(textDocument: TextDocument): void {
if (textDocument.languageId != "Angelscript") if (textDocument.languageId != "Angelscript" && textDocument.languageId != "AngelscriptTypeDefinition")
return; return;
if (!fs.existsSync(textDocument.uri.substr(7))){ if (!fs.existsSync(textDocument.uri.substr(7))) {
allFiles.delete(textDocument.uri); allFiles.delete(textDocument.uri);
return; return;
} }
let text = textDocument.getText(); let text = textDocument.getText();
database.loadScript(textDocument.uri, text); if (path.extname(textDocument.uri) == ".astypedef") {
database.loadTypeDef(textDocument.uri, text);
} else {
database.loadScript(textDocument.uri, text);
}
} }
async function validateBuild(): Promise<void> { async function validateBuild(): Promise<void> {
var r = database.build(); console.log("Building");
if (r < -1) { var r = database.build();
console.log(r.toString()); if (r <= -1) {
} console.log(r.toString());
console.log("Building"); }
var messages = database.messages(); var messages = database.messages();
let diagnostics: Map<string, Diagnostic[]> = new Map<string, Diagnostic[]>(); let diagnostics: Map<string, Diagnostic[]> = new Map<string, Diagnostic[]>();
for (let i = 0; i < messages.length; i++) { for (let i = 0; i < messages.length; i++) {
let msg = messages[i]; let msg = messages[i];
if (msg.type == NativeWrapper.MessageType.Information) if (msg.type == NativeWrapper.MessageType.Information)
continue; continue;
if (msg.section == ""){ console.log('[ ' + msg.section + ' ] ' + msg.message);
if (msg.type == NativeWrapper.MessageType.Error){ if (msg.section == "") {
connection.window.showErrorMessage(msg.message); 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); connection.window.showWarningMessage(msg.message);
} }
continue; continue;
} }
let startPos = Position.create(msg.row - 1, msg.col - 1); let startPos = Position.create(msg.row - 1, msg.col - 1);
if (startPos.line < 0) startPos.line = 0; if (startPos.line < 0) startPos.line = 0;
if (startPos.character < 0) startPos.character = 0; if (startPos.character < 0) startPos.character = 0;
let textDocument = documents.get(msg.section); let textDocument = documents.get(msg.section);
if (textDocument == undefined) if (textDocument == undefined)
continue; continue;
let offset = textDocument.offsetAt(startPos); let offset = textDocument.offsetAt(startPos);
let diagnostic: Diagnostic = { let diagnostic: Diagnostic = {
severity: convertSeverity(msg.type), severity: convertSeverity(msg.type),
range: { range: {
start: startPos, start: startPos,
end: textDocument.positionAt(offset + 1) end: textDocument.positionAt(offset + 1)
}, },
message: msg.message, message: msg.message,
source: 'Angelscript' 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}`) connection.console.error(`[${msg.section}:${msg.row - 1},${msg.col - 1}] ${msg.message}`)
} }
var diag = diagnostics.get(msg.section); var diag = diagnostics.get(msg.section);
if (diag) { if (diag) {
diag.push(diagnostic) diag.push(diagnostic)
} } else {
else { diagnostics.set(msg.section, [diagnostic]);
diagnostics.set(msg.section, [diagnostic]); }
} }
}
documents.all().forEach(element => { documents.all().forEach(element => {
let v = diagnostics.get(element.uri); let v = diagnostics.get(element.uri);
if (v) if (v)
connection.sendDiagnostics({ uri: element.uri, diagnostics: v }); connection.sendDiagnostics({uri: element.uri, diagnostics: v});
else else
connection.sendDiagnostics({ uri: element.uri, diagnostics: [] }); connection.sendDiagnostics({uri: element.uri, diagnostics: []});
}); });
// Send the computed diagnostics to VSCode. console.log("Finished building");
// Send the computed diagnostics to VSCode.
} }
connection.onDidChangeWatchedFiles(_change => { connection.onDidChangeWatchedFiles(_change => {
// Monitored files have change in VSCode // Monitored files have change in VSCode
connection.console.log('We received an file change event'); connection.console.log('We received an file change event');
}); });
// Make the text document manager listen on the connection // Make the text document manager listen on the connection

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 <napi.h>
#include "Database.hpp" #include "Database.hpp"
@ -5,4 +6,5 @@ Napi::Object InitAll(Napi::Env env, Napi::Object exports) {
return Database::Init(env, exports); return Database::Init(env, exports);
} }
NODE_API_MODULE(aslsp, InitAll) NODE_API_MODULE(aslsp, InitAll)
#endif

View File

@ -1,12 +1,16 @@
#if !TESTS_BUILD
#include "Database.hpp" #include "Database.hpp"
#include <sstream> #include <sstream>
#include "../angelscript/extensions/scriptarray/scriptarray.h" #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::Object Database::Init(Napi::Env env, Napi::Object exports) {
Napi::Function func = Napi::Function func =
DefineClass(env, "Database", DefineClass(env, "Database",
{InstanceMethod("reset", &Database::Reset), InstanceMethod("loadScript", &Database::LoadScript), {InstanceMethod("reset", &Database::Reset), InstanceMethod("loadTypeDef", &Database::LoadTypeDef),
InstanceMethod("build", &Database::Build), InstanceMethod("messages", &Database::GetMessages), InstanceMethod("loadScript", &Database::LoadScript), InstanceMethod("build", &Database::Build),
InstanceMethod("messages", &Database::GetMessages),
InstanceMethod("setEngineProperty", &Database::SetEngineProperty)}); InstanceMethod("setEngineProperty", &Database::SetEngineProperty)});
auto* constructor = new Napi::FunctionReference(); 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)); _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(); _engine = asCreateScriptEngine();
_builder = {};
_engine->SetMessageCallback(asMETHOD(Database, MessageCallback), this, asCALL_THISCALL); _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); RegisterStdString(_engine);
RegisterScriptArray(_engine, true); RegisterScriptArray(_engine, true);
_builder.StartNewModule(_engine, "Module");
}
Database::Database(const Napi::CallbackInfo& info) : ObjectWrap(info) {
Reset(info); Reset(info);
} }
@ -39,8 +54,13 @@ void Database::SetEngineProperty(const Napi::CallbackInfo& info) {
} }
void Database::Reset(const Napi::CallbackInfo&) { void Database::Reset(const Napi::CallbackInfo&) {
_engine->DiscardModule("Module"); std::lock_guard<std::mutex> lck(_lock);
_builder.StartNewModule(_engine, "Module"); if (_engine != nullptr){
_engine->DiscardModule("Module");
_engine->Release();
}
SetupEngine();
_result.Clear();
} }
void Database::LoadScript(const Napi::CallbackInfo& info) { 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()); _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) { Napi::Value Database::Build(const Napi::CallbackInfo& info) {
std::lock_guard<std::mutex> lck(_lock);
_messages.clear(); _messages.clear();
_result.RegisterTypes(_engine);
_result.RegisterImplementation(_engine);
_builder.BuildModule(); _builder.BuildModule();
return Napi::Number::New(info.Env(), 0); return Napi::Number::New(info.Env(), 0);
} }
@ -70,3 +103,4 @@ Napi::Value Database::GetMessages(const Napi::CallbackInfo& info) {
return messages; return messages;
} }
#endif

View File

@ -1,17 +1,21 @@
#ifndef AS_LSP_NATIVE_DATABASE_HPP #ifndef AS_LSP_NATIVE_DATABASE_HPP
#define AS_LSP_NATIVE_DATABASE_HPP #define AS_LSP_NATIVE_DATABASE_HPP
#if !TESTS_BUILD
#include "../../node_modules/node-addon-api/napi.h" #include "../../node_modules/node-addon-api/napi.h"
#include "../angelscript/extensions/scriptbuilder/scriptbuilder.h" #include "../angelscript/extensions/scriptbuilder/scriptbuilder.h"
#include "../angelscript/extensions/scriptstdstring/scriptstdstring.h"
#include "../angelscript/include/angelscript.h" #include "../angelscript/include/angelscript.h"
#include "ASTypeDefParser/TypeDefResult.hpp"
#include "Diagnostic.hpp" #include "Diagnostic.hpp"
class Database : public Napi::ObjectWrap<Database> { class Database : public Napi::ObjectWrap<Database> {
private: private:
asIScriptEngine* _engine; asIScriptEngine* _engine = nullptr;
CScriptBuilder _builder; CScriptBuilder _builder;
ASTypeDefParser::TypeDefResult _result;
std::vector<const Diagnostic*> _messages; std::vector<const Diagnostic*> _messages;
std::mutex _lock;
void SetupEngine();
void MessageCallback(const asSMessageInfo* msg); void MessageCallback(const asSMessageInfo* msg);
public: public:
@ -21,8 +25,10 @@ public:
void SetEngineProperty(const Napi::CallbackInfo& info); void SetEngineProperty(const Napi::CallbackInfo& info);
void Reset(const Napi::CallbackInfo& info); void Reset(const Napi::CallbackInfo& info);
void LoadScript(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 Build(const Napi::CallbackInfo& info);
Napi::Value GetMessages(const Napi::CallbackInfo& info); Napi::Value GetMessages(const Napi::CallbackInfo& info);
}; };
#endif
#endif // AS_LSP_NATIVE_DATABASE_HPP #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 { export interface ScriptDatabase {
setEngineProperty(property: ASEngineProperty, value: number): void; setEngineProperty(property: ASEngineProperty, value: number): void;
reset(): void; reset(): void;
loadTypeDef(name: string, script: string): void;
loadScript(name: string, script: string): void; loadScript(name: string, script: string): void;
build(): number; build(): number;
messages(): Message[]; 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>