wip
This commit is contained in:
Vendored
+1
-1
@@ -16,7 +16,7 @@
|
||||
"${workspaceFolder}/dutchies-dcs-scripting-tools/dist/**/*.js",
|
||||
"${workspaceFolder}/compiler/dist/**/*.js"
|
||||
],
|
||||
"preLaunchTask": "compile"
|
||||
"preLaunchTask": "watch"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Vendored
+1
@@ -24,6 +24,7 @@
|
||||
"reveal": "never"
|
||||
},
|
||||
"group": "build",
|
||||
"problemMatcher": "$tsc-watch",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/dutchies-dcs-scripting-tools"
|
||||
}
|
||||
|
||||
+175
-69
@@ -8,7 +8,7 @@ import { CompilationError } from './CompilationError';
|
||||
Require is a special case as it's technically a function call, but it's a special use case.
|
||||
*/
|
||||
export enum BlockType {
|
||||
Line,
|
||||
CodeTextBlock,
|
||||
Function,
|
||||
If,
|
||||
While,
|
||||
@@ -16,9 +16,15 @@ export enum BlockType {
|
||||
Do,
|
||||
Return,
|
||||
Require,
|
||||
Table,
|
||||
File
|
||||
}
|
||||
|
||||
const keywords = ['if', 'while', 'for', 'do', 'return', 'function', 'require', 'end'];
|
||||
function isKeyWord(word: string): boolean {
|
||||
return keywords.includes(word);
|
||||
}
|
||||
|
||||
export abstract class CodeBlock {
|
||||
protected childBlocks: CodeBlock[] = [];
|
||||
private parentBlock?: CodeBlock;
|
||||
@@ -26,7 +32,7 @@ export abstract class CodeBlock {
|
||||
|
||||
public readonly sourceLineNumber?: number;
|
||||
|
||||
constructor(sourceLineNumber: number, blockType: BlockType, parentBlock?: CodeBlock){
|
||||
constructor(sourceLineNumber: number, blockType: BlockType, parentBlock?: CodeBlock) {
|
||||
this.sourceLineNumber = sourceLineNumber;
|
||||
this.parentBlock = parentBlock;
|
||||
this.blockType = blockType;
|
||||
@@ -47,58 +53,57 @@ export abstract class CodeBlock {
|
||||
if (!fs.existsSync(luaFilePath)) {
|
||||
throw new Error(`File not found: ${luaFilePath}`);
|
||||
}
|
||||
|
||||
|
||||
const fileContent = fs.readFileSync(luaFilePath, 'utf-8');
|
||||
|
||||
let leftCursor = 0;
|
||||
let lineCounter = 0;
|
||||
const file : LuaFile = new LuaFile();
|
||||
let currentBlock : CodeBlock = file;
|
||||
|
||||
const file: LuaFile = new LuaFile();
|
||||
let currentBlock: CodeBlock = file;
|
||||
|
||||
let currentWord = '';
|
||||
let currentBlockString = '';
|
||||
|
||||
while(leftCursor < fileContent.length){
|
||||
const currentChar : string = fileContent[leftCursor];
|
||||
|
||||
while (leftCursor < fileContent.length) {
|
||||
const currentChar: string = fileContent[leftCursor];
|
||||
currentWord += currentChar;
|
||||
currentBlockString += currentChar;
|
||||
const nextChar = leftCursor < fileContent.length - 1 ? fileContent[leftCursor + 1] : '';
|
||||
|
||||
if(currentChar === '\n'){
|
||||
if (currentChar === '\n') {
|
||||
lineCounter++;
|
||||
}
|
||||
|
||||
if(currentChar === '-' && nextChar === '-'){
|
||||
if (currentChar === '-' && nextChar === '-') {
|
||||
currentBlockString = currentBlockString.slice(0, -1); // Remove the '-' from the block string
|
||||
const nextNextChar = leftCursor < fileContent.length - 2 ? fileContent[leftCursor + 2] : '';
|
||||
if(nextNextChar === '['){
|
||||
if (nextNextChar === '[') {
|
||||
// Multiline comment, skip to closing ]]
|
||||
leftCursor += 3; // Skip the --[
|
||||
while(leftCursor < fileContent.length && !(fileContent[leftCursor] === ']' && fileContent[leftCursor + 1] === ']')){
|
||||
if(fileContent[leftCursor] === '\n'){
|
||||
while (leftCursor < fileContent.length && !(fileContent[leftCursor] === ']' && fileContent[leftCursor + 1] === ']')) {
|
||||
if (fileContent[leftCursor] === '\n') {
|
||||
lineCounter++;
|
||||
}
|
||||
leftCursor++;
|
||||
}
|
||||
leftCursor += 2; // Skip the closing ]]
|
||||
} else {
|
||||
// Comment line, skip to end of line
|
||||
while(leftCursor < fileContent.length && fileContent[leftCursor] !== '\n'){
|
||||
// Comment line, skip to end of line
|
||||
while (leftCursor < fileContent.length && fileContent[leftCursor] !== '\n') {
|
||||
leftCursor++;
|
||||
}
|
||||
if(fileContent[leftCursor] === '\n'){
|
||||
if (fileContent[leftCursor] === '\n') {
|
||||
lineCounter++;
|
||||
}
|
||||
}
|
||||
currentWord = '';
|
||||
continue;
|
||||
}
|
||||
|
||||
if(currentChar === '"' || currentChar === "'"){
|
||||
else if (currentChar === '"' || currentChar === "'") {
|
||||
// String literal, skip to closing quote
|
||||
const quoteType = currentChar;
|
||||
leftCursor++;
|
||||
while(leftCursor < fileContent.length && fileContent[leftCursor] !== quoteType){
|
||||
while (leftCursor < fileContent.length && fileContent[leftCursor] !== quoteType) {
|
||||
// Add string but don't process it for keywords
|
||||
currentBlockString += fileContent[leftCursor];
|
||||
leftCursor++;
|
||||
@@ -109,61 +114,141 @@ export abstract class CodeBlock {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(currentChar === "\n") {
|
||||
// In return block, read till the end of the return block
|
||||
// else if (currentBlock.blockType === BlockType.Return) {
|
||||
|
||||
// //Continue reading until there's at least a new word
|
||||
// }
|
||||
else if (currentChar === "\n") {
|
||||
// Process line
|
||||
currentBlockString = currentBlockString.trimEnd(); // Remove trailing whitespace
|
||||
const lineBlock = new LineBlock(lineCounter, currentBlockString, currentBlock);
|
||||
currentBlockString += '\n'; // Add the newline back for the line block
|
||||
if (currentBlockString !== '') {
|
||||
const lineBlock = new CodeTextBlock(lineCounter, currentBlockString, currentBlock);
|
||||
currentBlock.childBlocks.push(lineBlock);
|
||||
currentBlockString = '';
|
||||
currentWord = '';
|
||||
}
|
||||
}
|
||||
else if (currentChar === ")" && currentBlock.blockType === BlockType.Require) {
|
||||
currentBlockString = currentBlockString.trimEnd(); // Remove trailing whitespace
|
||||
currentBlockString += '\n'; // Add the newline back for the line block as it will be skipped otherwise
|
||||
const lineBlock = new CodeTextBlock(lineCounter, currentBlockString, currentBlock);
|
||||
currentBlock.childBlocks.push(lineBlock);
|
||||
currentBlockString = '';
|
||||
|
||||
if(currentBlock.blockType === BlockType.Return){
|
||||
// TODO: multi line return blocks
|
||||
//Return blocks don't end on end, but on a new line
|
||||
currentBlock = currentBlock.getParentBlock()!;
|
||||
}
|
||||
}
|
||||
|
||||
if(currentChar === ")" && currentBlock.blockType === BlockType.Require){
|
||||
currentBlock = currentBlock.getParentBlock()!;
|
||||
}
|
||||
}
|
||||
//Table blocks
|
||||
else if (currentChar === "{") {
|
||||
// Only create LineBlock if there's content before the brace
|
||||
if (currentBlockString.trimEnd() !== '' && currentBlockString.trimEnd() !== '{') {
|
||||
const currentLineBlock = new CodeTextBlock(lineCounter, currentBlockString.slice(0, -1).trimEnd(), currentBlock);
|
||||
currentBlock.childBlocks.push(currentLineBlock);
|
||||
}
|
||||
|
||||
if(nextChar.trim() === '' || nextChar === '('){
|
||||
let leadingWhiteSpace = '';
|
||||
const braceIndex = currentBlockString.lastIndexOf('{');
|
||||
if (braceIndex > 0) {
|
||||
let wsStart = braceIndex - 1;
|
||||
while (wsStart >= 0 && /[ \t]/.test(currentBlockString[wsStart])) {
|
||||
wsStart--;
|
||||
}
|
||||
leadingWhiteSpace = currentBlockString.substring(wsStart + 1, braceIndex);
|
||||
}
|
||||
currentBlockString = leadingWhiteSpace + '{'; // Start the new block string with the opening brace
|
||||
|
||||
const tableBlock = new TableBlock(lineCounter, currentBlock);
|
||||
currentBlock.childBlocks.push(tableBlock);
|
||||
currentBlock = tableBlock;
|
||||
|
||||
let braceCounter = 1;
|
||||
leftCursor++;
|
||||
while (leftCursor < fileContent.length && braceCounter > 0) {
|
||||
const char = fileContent[leftCursor];
|
||||
currentBlockString += char;
|
||||
if (char === '{') {
|
||||
braceCounter++;
|
||||
} else if (char === '}') {
|
||||
braceCounter--;
|
||||
}
|
||||
|
||||
if (char === '\n') {
|
||||
let block = currentBlockString.trimEnd();
|
||||
block += '\n';
|
||||
const lineBlock = new CodeTextBlock(lineCounter, block, currentBlock);
|
||||
currentBlock.childBlocks.push(lineBlock);
|
||||
currentBlockString = '';
|
||||
lineCounter++;
|
||||
}
|
||||
leftCursor++;
|
||||
}
|
||||
|
||||
// Find newline or other character
|
||||
let tempCursor = leftCursor;
|
||||
while (tempCursor < fileContent.length && fileContent[tempCursor] !== '\n' && fileContent[tempCursor].trim() === '') {
|
||||
currentBlockString += fileContent[tempCursor];
|
||||
tempCursor++;
|
||||
}
|
||||
|
||||
if (tempCursor < fileContent.length && fileContent[tempCursor] === '\n') {
|
||||
currentBlockString += '\n';
|
||||
lineCounter++;
|
||||
} else {
|
||||
leftCursor = tempCursor - 1; // Set leftCursor to the last character before the newline or non-whitespace character
|
||||
}
|
||||
|
||||
if (currentBlockString !== '') {
|
||||
const lineBlock = new CodeTextBlock(lineCounter, currentBlockString, currentBlock);
|
||||
currentBlock.childBlocks.push(lineBlock);
|
||||
currentBlockString = '';
|
||||
}
|
||||
|
||||
currentBlock = currentBlock.getParentBlock()!;
|
||||
currentWord = ''; // Reset word accumulator after table block
|
||||
continue; // Skip the leftCursor++ at the end of the loop since we already incremented it
|
||||
}
|
||||
else if (nextChar.trim() === '' || nextChar === '(') {
|
||||
// End of a word, check for keywords
|
||||
const trimmedWord = currentWord.trim();
|
||||
let blockToAdd : CodeBlock | null = null;
|
||||
if(trimmedWord === 'if'){
|
||||
let blockToAdd: CodeBlock | null = null;
|
||||
if (trimmedWord === 'if') {
|
||||
blockToAdd = new IfBlock(lineCounter, currentBlock);
|
||||
}
|
||||
else if(trimmedWord === 'while'){
|
||||
else if (trimmedWord === 'while') {
|
||||
blockToAdd = new WhileBlock(lineCounter, currentBlock);
|
||||
}
|
||||
else if(trimmedWord === 'for'){
|
||||
else if (trimmedWord === 'for') {
|
||||
blockToAdd = new ForBlock(lineCounter, currentBlock);
|
||||
}
|
||||
else if(trimmedWord === 'do'){
|
||||
else if (trimmedWord === 'do') {
|
||||
blockToAdd = new DoBlock(lineCounter, currentBlock);
|
||||
}
|
||||
else if(trimmedWord === 'return'){
|
||||
else if (trimmedWord === 'return') {
|
||||
const line = new CodeTextBlock(lineCounter, currentBlockString.trimEnd().slice(0, -trimmedWord.length), currentBlock);
|
||||
currentBlock.childBlocks.push(line);
|
||||
currentBlockString = 'return';
|
||||
blockToAdd = new ReturnBlock(lineCounter, currentBlock);
|
||||
}
|
||||
else if(trimmedWord === 'function') {
|
||||
else if (trimmedWord === 'function') {
|
||||
blockToAdd = new FunctionBlock(lineCounter, currentBlock);
|
||||
}
|
||||
else if (trimmedWord === 'require') {
|
||||
blockToAdd = new RequireBlock(lineCounter, currentBlock, leftCursor - currentWord.length, leftCursor);
|
||||
} else if(trimmedWord === 'end'){
|
||||
} else if (trimmedWord === 'end') {
|
||||
const parent = currentBlock.getParentBlock();
|
||||
if(!parent){
|
||||
if (!parent) {
|
||||
onError?.({
|
||||
filePath: luaFilePath,
|
||||
line: fileContent.substring(0, leftCursor).split('\n').length - 1,
|
||||
message: "Unexpected 'end' without matching block start"
|
||||
});
|
||||
} else {
|
||||
currentBlock = parent;
|
||||
}
|
||||
currentBlock = currentBlock.getParentBlock()!
|
||||
}
|
||||
}
|
||||
|
||||
if(blockToAdd){
|
||||
if (blockToAdd) {
|
||||
currentBlock.childBlocks.push(blockToAdd);
|
||||
currentBlock = blockToAdd;
|
||||
}
|
||||
@@ -176,26 +261,26 @@ export abstract class CodeBlock {
|
||||
}
|
||||
}
|
||||
|
||||
export class LineBlock extends CodeBlock {
|
||||
export class CodeTextBlock extends CodeBlock {
|
||||
|
||||
constructor(sourceLineNumber: number, private line: string, parent?: CodeBlock){
|
||||
super(sourceLineNumber, BlockType.Line, parent);
|
||||
constructor(sourceLineNumber: number, private line: string, parent?: CodeBlock) {
|
||||
super(sourceLineNumber, BlockType.CodeTextBlock, parent);
|
||||
}
|
||||
|
||||
|
||||
toLines(): string[] {
|
||||
return [this.line];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class IfBlock extends CodeBlock {
|
||||
|
||||
constructor(sourceLineNumber: number, parent: CodeBlock){
|
||||
|
||||
constructor(sourceLineNumber: number, parent: CodeBlock) {
|
||||
super(sourceLineNumber, BlockType.If, parent);
|
||||
}
|
||||
|
||||
toLines(): string[] {
|
||||
const lines: string[] = [];
|
||||
for(const child of this.childBlocks){
|
||||
for (const child of this.childBlocks) {
|
||||
lines.push(...child.toLines());
|
||||
}
|
||||
return lines;
|
||||
@@ -204,13 +289,13 @@ export class IfBlock extends CodeBlock {
|
||||
|
||||
export class WhileBlock extends CodeBlock {
|
||||
|
||||
constructor(sourceLineNumber: number, parent: CodeBlock){
|
||||
constructor(sourceLineNumber: number, parent: CodeBlock) {
|
||||
super(sourceLineNumber, BlockType.While, parent);
|
||||
}
|
||||
|
||||
toLines(): string[] {
|
||||
const lines: string[] = [];
|
||||
for(const child of this.childBlocks){
|
||||
for (const child of this.childBlocks) {
|
||||
lines.push(...child.toLines());
|
||||
}
|
||||
return lines;
|
||||
@@ -219,13 +304,13 @@ export class WhileBlock extends CodeBlock {
|
||||
|
||||
export class ForBlock extends CodeBlock {
|
||||
|
||||
constructor(sourceLineNumber: number, parent: CodeBlock){
|
||||
constructor(sourceLineNumber: number, parent: CodeBlock) {
|
||||
super(sourceLineNumber, BlockType.For, parent);
|
||||
}
|
||||
|
||||
toLines(): string[] {
|
||||
const lines: string[] = [];
|
||||
for(const child of this.childBlocks){
|
||||
for (const child of this.childBlocks) {
|
||||
lines.push(...child.toLines());
|
||||
}
|
||||
return lines;
|
||||
@@ -234,14 +319,14 @@ export class ForBlock extends CodeBlock {
|
||||
|
||||
|
||||
export class LuaFile extends CodeBlock {
|
||||
|
||||
|
||||
constructor() {
|
||||
super(0, BlockType.File);
|
||||
}
|
||||
|
||||
toLines(): string[] {
|
||||
const lines: string[] = [];
|
||||
for(const child of this.childBlocks){
|
||||
for (const child of this.childBlocks) {
|
||||
lines.push(...child.toLines());
|
||||
}
|
||||
return lines;
|
||||
@@ -250,13 +335,13 @@ export class LuaFile extends CodeBlock {
|
||||
|
||||
export class DoBlock extends CodeBlock {
|
||||
|
||||
constructor(sourceLineNumber: number, parent: CodeBlock){
|
||||
constructor(sourceLineNumber: number, parent: CodeBlock) {
|
||||
super(sourceLineNumber, BlockType.Do, parent);
|
||||
}
|
||||
|
||||
toLines(): string[] {
|
||||
const lines: string[] = [];
|
||||
for(const child of this.childBlocks){
|
||||
for (const child of this.childBlocks) {
|
||||
lines.push(...child.toLines());
|
||||
}
|
||||
return lines;
|
||||
@@ -265,13 +350,28 @@ export class DoBlock extends CodeBlock {
|
||||
|
||||
export class ReturnBlock extends CodeBlock {
|
||||
|
||||
constructor(sourceLineNumber: number, parent: CodeBlock){
|
||||
constructor(sourceLineNumber: number, parent: CodeBlock) {
|
||||
super(sourceLineNumber, BlockType.Return, parent);
|
||||
}
|
||||
|
||||
toLines(): string[] {
|
||||
const lines: string[] = [];
|
||||
for(const child of this.childBlocks){
|
||||
for (const child of this.childBlocks) {
|
||||
lines.push(...child.toLines());
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
}
|
||||
|
||||
export class TableBlock extends CodeBlock {
|
||||
|
||||
constructor(sourceLineNumber: number, parent: CodeBlock) {
|
||||
super(sourceLineNumber, BlockType.Table, parent);
|
||||
}
|
||||
|
||||
toLines(): string[] {
|
||||
const lines: string[] = [];
|
||||
for (const child of this.childBlocks) {
|
||||
lines.push(...child.toLines());
|
||||
}
|
||||
return lines;
|
||||
@@ -280,13 +380,13 @@ export class ReturnBlock extends CodeBlock {
|
||||
|
||||
export class FunctionBlock extends CodeBlock {
|
||||
|
||||
constructor(sourceLineNumber: number, parent: CodeBlock){
|
||||
constructor(sourceLineNumber: number, parent: CodeBlock) {
|
||||
super(sourceLineNumber, BlockType.Function, parent);
|
||||
}
|
||||
|
||||
toLines(): string[] {
|
||||
const lines: string[] = [];
|
||||
for(const child of this.childBlocks){
|
||||
for (const child of this.childBlocks) {
|
||||
lines.push(...child.toLines());
|
||||
}
|
||||
return lines;
|
||||
@@ -295,25 +395,31 @@ export class FunctionBlock extends CodeBlock {
|
||||
|
||||
export class RequireBlock extends CodeBlock {
|
||||
|
||||
constructor(sourceLineNumber: number, parent: CodeBlock, public readonly charStart?: number, public readonly charEnd?: number){
|
||||
constructor(sourceLineNumber: number, parent: CodeBlock, public readonly charStart?: number, public readonly charEnd?: number) {
|
||||
super(sourceLineNumber, BlockType.Require, parent);
|
||||
}
|
||||
|
||||
toLines(): string[] {
|
||||
const lines: string[] = [];
|
||||
for(const child of this.childBlocks){
|
||||
for (const child of this.childBlocks) {
|
||||
lines.push(...child.toLines());
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
getRequiredString(): string {
|
||||
if(this.childBlocks.length === 0){
|
||||
if (this.childBlocks.length === 0) {
|
||||
return '';
|
||||
}
|
||||
const firstChild = this.childBlocks[0];
|
||||
if(firstChild instanceof LineBlock){
|
||||
return firstChild.toLines()[0].trim().replace(/['"]/g, '');
|
||||
if (firstChild instanceof CodeTextBlock) {
|
||||
// local module = require("module") -> module
|
||||
// local module = require('module') -> module
|
||||
const line = firstChild.toLines()[0];
|
||||
const requireMatch = line.match(/require\s*\(\s*["']([^"']+)["']\s*\)/);
|
||||
if (requireMatch && requireMatch[1]) {
|
||||
return requireMatch[1];
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -63,7 +63,9 @@ export class ScriptCompiler {
|
||||
const fullPath = path.join(entry.parentPath, entry.name);
|
||||
const relativePath = path.relative(this.options.sourcePath, fullPath);
|
||||
const luaFile = LuaFile.createFromFile(fullPath, this.options.onError);
|
||||
const parsedFile = new ParsedFile(fileReferenceToLuaVariable(relativePath), luaFile, fullPath);
|
||||
const luaReference = fileReferenceToLuaVariable(relativePath);
|
||||
const key = luaReference.replace(LUA_SCRIPT_GLOBAL_KEYWORD + '.', '').toLowerCase();
|
||||
const parsedFile = new ParsedFile(key, luaFile, fullPath);
|
||||
|
||||
parsedFiles.set(parsedFile.fileKey, parsedFile);
|
||||
metricsMeter.filesRead++;
|
||||
@@ -97,6 +99,7 @@ export class ScriptCompiler {
|
||||
|
||||
class Dependency {
|
||||
public readonly fileKey: string;
|
||||
public readonly luaReference: string;
|
||||
|
||||
constructor(
|
||||
public readonly requiredModule: string,
|
||||
@@ -105,7 +108,8 @@ class Dependency {
|
||||
public readonly charEnd: number
|
||||
)
|
||||
{
|
||||
this.fileKey = fileReferenceToLuaVariable(requiredModule);
|
||||
this.luaReference = fileReferenceToLuaVariable(requiredModule);
|
||||
this.fileKey = this.luaReference.replace(LUA_SCRIPT_GLOBAL_KEYWORD + '.', '').toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,8 +186,8 @@ class Writer {
|
||||
|
||||
private getStartLines(): string[] {
|
||||
return [
|
||||
`-- Transpiled at (UTC): ${new Date().toISOString()}`,
|
||||
`local ${LUA_SCRIPT_GLOBAL_KEYWORD} = {}`
|
||||
`-- Transpiled at (UTC): ${new Date().toISOString()}\n`,
|
||||
`local ${LUA_SCRIPT_GLOBAL_KEYWORD} = {}\n`
|
||||
];
|
||||
}
|
||||
|
||||
@@ -287,11 +291,11 @@ class Writer {
|
||||
}
|
||||
}
|
||||
|
||||
outputLines.push("do -- " + parsedFile.fileKey);
|
||||
outputLines.push("do -- " + parsedFile.fileKey + "\n");
|
||||
const lines = parsedFile.luaFile.toLines();
|
||||
metrics.totalLinesWritten += lines.length;
|
||||
outputLines.push(...lines.filter(line => line.trim() !== '')); // Filter out empty lines
|
||||
outputLines.push("end -- " + parsedFile.fileKey);
|
||||
outputLines.push("end -- " + parsedFile.fileKey + "\n");
|
||||
writtenFiles.add(parsedFile.fileKey);
|
||||
metrics.filesWritten++;
|
||||
};
|
||||
@@ -302,7 +306,7 @@ class Writer {
|
||||
}
|
||||
|
||||
fs.mkdirSync(path.dirname(this.location), { recursive: true });
|
||||
fs.writeFileSync(this.location, outputLines.join('\n'), 'utf-8');
|
||||
fs.writeFileSync(this.location, outputLines.join(''), 'utf-8');
|
||||
|
||||
if(includeDevScript) {
|
||||
const devFileLocation = this.location.replace('.lua', '.dev.lua');
|
||||
|
||||
Reference in New Issue
Block a user