wip
This commit is contained in:
@@ -0,0 +1,320 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { CompilationError } from './CompilationError';
|
||||
|
||||
/*
|
||||
Block types.
|
||||
|
||||
Require is a special case as it's technically a function call, but it's a special use case.
|
||||
*/
|
||||
export enum BlockType {
|
||||
Line,
|
||||
Function,
|
||||
If,
|
||||
While,
|
||||
For,
|
||||
Do,
|
||||
Return,
|
||||
Require,
|
||||
File
|
||||
}
|
||||
|
||||
export abstract class CodeBlock {
|
||||
protected childBlocks: CodeBlock[] = [];
|
||||
private parentBlock?: CodeBlock;
|
||||
private blockType: BlockType;
|
||||
|
||||
public readonly sourceLineNumber?: number;
|
||||
|
||||
constructor(sourceLineNumber: number, blockType: BlockType, parentBlock?: CodeBlock){
|
||||
this.sourceLineNumber = sourceLineNumber;
|
||||
this.parentBlock = parentBlock;
|
||||
this.blockType = blockType;
|
||||
}
|
||||
|
||||
public getChildren(): CodeBlock[] {
|
||||
return this.childBlocks;
|
||||
}
|
||||
|
||||
abstract toLines(): string[];
|
||||
|
||||
getParentBlock(): CodeBlock | undefined {
|
||||
return this.parentBlock;
|
||||
}
|
||||
|
||||
static createFromFile(luaFilePath: string, onError?: (error: CompilationError) => void): LuaFile {
|
||||
|
||||
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;
|
||||
|
||||
let currentWord = '';
|
||||
let currentBlockString = '';
|
||||
|
||||
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'){
|
||||
lineCounter++;
|
||||
}
|
||||
|
||||
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 === '['){
|
||||
// Multiline comment, skip to closing ]]
|
||||
leftCursor += 3; // Skip the --[
|
||||
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'){
|
||||
leftCursor++;
|
||||
}
|
||||
if(fileContent[leftCursor] === '\n'){
|
||||
lineCounter++;
|
||||
}
|
||||
}
|
||||
currentWord = '';
|
||||
continue;
|
||||
}
|
||||
|
||||
if(currentChar === '"' || currentChar === "'"){
|
||||
// String literal, skip to closing quote
|
||||
const quoteType = currentChar;
|
||||
leftCursor++;
|
||||
while(leftCursor < fileContent.length && fileContent[leftCursor] !== quoteType){
|
||||
// Add string but don't process it for keywords
|
||||
currentBlockString += fileContent[leftCursor];
|
||||
leftCursor++;
|
||||
}
|
||||
currentBlockString += quoteType; // Add the closing quote
|
||||
leftCursor++; // Skip the closing quote
|
||||
currentWord = '';
|
||||
continue;
|
||||
}
|
||||
|
||||
if(currentChar === "\n") {
|
||||
// Process line
|
||||
currentBlockString = currentBlockString.trimEnd(); // Remove trailing whitespace
|
||||
const lineBlock = new LineBlock(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()!;
|
||||
}
|
||||
|
||||
if(nextChar.trim() === '' || nextChar === '('){
|
||||
// End of a word, check for keywords
|
||||
const trimmedWord = currentWord.trim();
|
||||
let blockToAdd : CodeBlock | null = null;
|
||||
if(trimmedWord === 'if'){
|
||||
blockToAdd = new IfBlock(lineCounter, currentBlock);
|
||||
}
|
||||
else if(trimmedWord === 'while'){
|
||||
blockToAdd = new WhileBlock(lineCounter, currentBlock);
|
||||
}
|
||||
else if(trimmedWord === 'for'){
|
||||
blockToAdd = new ForBlock(lineCounter, currentBlock);
|
||||
}
|
||||
else if(trimmedWord === 'do'){
|
||||
blockToAdd = new DoBlock(lineCounter, currentBlock);
|
||||
}
|
||||
else if(trimmedWord === 'return'){
|
||||
blockToAdd = new ReturnBlock(lineCounter, currentBlock);
|
||||
}
|
||||
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'){
|
||||
const parent = currentBlock.getParentBlock();
|
||||
if(!parent){
|
||||
onError?.({
|
||||
filePath: luaFilePath,
|
||||
line: fileContent.substring(0, leftCursor).split('\n').length - 1,
|
||||
message: "Unexpected 'end' without matching block start"
|
||||
});
|
||||
}
|
||||
currentBlock = currentBlock.getParentBlock()!
|
||||
}
|
||||
|
||||
if(blockToAdd){
|
||||
currentBlock.childBlocks.push(blockToAdd);
|
||||
currentBlock = blockToAdd;
|
||||
}
|
||||
|
||||
currentWord = '';
|
||||
}
|
||||
leftCursor++;
|
||||
}
|
||||
return file;
|
||||
}
|
||||
}
|
||||
|
||||
export class LineBlock extends CodeBlock {
|
||||
|
||||
constructor(sourceLineNumber: number, private line: string, parent?: CodeBlock){
|
||||
super(sourceLineNumber, BlockType.Line, parent);
|
||||
}
|
||||
|
||||
toLines(): string[] {
|
||||
return [this.line];
|
||||
}
|
||||
}
|
||||
|
||||
export class IfBlock extends CodeBlock {
|
||||
|
||||
constructor(sourceLineNumber: number, parent: CodeBlock){
|
||||
super(sourceLineNumber, BlockType.If, parent);
|
||||
}
|
||||
|
||||
toLines(): string[] {
|
||||
const lines: string[] = [];
|
||||
for(const child of this.childBlocks){
|
||||
lines.push(...child.toLines());
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
}
|
||||
|
||||
export class WhileBlock extends CodeBlock {
|
||||
|
||||
constructor(sourceLineNumber: number, parent: CodeBlock){
|
||||
super(sourceLineNumber, BlockType.While, parent);
|
||||
}
|
||||
|
||||
toLines(): string[] {
|
||||
const lines: string[] = [];
|
||||
for(const child of this.childBlocks){
|
||||
lines.push(...child.toLines());
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
}
|
||||
|
||||
export class ForBlock extends CodeBlock {
|
||||
|
||||
constructor(sourceLineNumber: number, parent: CodeBlock){
|
||||
super(sourceLineNumber, BlockType.For, parent);
|
||||
}
|
||||
|
||||
toLines(): string[] {
|
||||
const lines: string[] = [];
|
||||
for(const child of this.childBlocks){
|
||||
lines.push(...child.toLines());
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class LuaFile extends CodeBlock {
|
||||
|
||||
constructor() {
|
||||
super(0, BlockType.File);
|
||||
}
|
||||
|
||||
toLines(): string[] {
|
||||
const lines: string[] = [];
|
||||
for(const child of this.childBlocks){
|
||||
lines.push(...child.toLines());
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
}
|
||||
|
||||
export class DoBlock extends CodeBlock {
|
||||
|
||||
constructor(sourceLineNumber: number, parent: CodeBlock){
|
||||
super(sourceLineNumber, BlockType.Do, parent);
|
||||
}
|
||||
|
||||
toLines(): string[] {
|
||||
const lines: string[] = [];
|
||||
for(const child of this.childBlocks){
|
||||
lines.push(...child.toLines());
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
}
|
||||
|
||||
export class ReturnBlock extends CodeBlock {
|
||||
|
||||
constructor(sourceLineNumber: number, parent: CodeBlock){
|
||||
super(sourceLineNumber, BlockType.Return, parent);
|
||||
}
|
||||
|
||||
toLines(): string[] {
|
||||
const lines: string[] = [];
|
||||
for(const child of this.childBlocks){
|
||||
lines.push(...child.toLines());
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
}
|
||||
|
||||
export class FunctionBlock extends CodeBlock {
|
||||
|
||||
constructor(sourceLineNumber: number, parent: CodeBlock){
|
||||
super(sourceLineNumber, BlockType.Function, parent);
|
||||
}
|
||||
|
||||
toLines(): string[] {
|
||||
const lines: string[] = [];
|
||||
for(const child of this.childBlocks){
|
||||
lines.push(...child.toLines());
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
}
|
||||
|
||||
export class RequireBlock extends CodeBlock {
|
||||
|
||||
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){
|
||||
lines.push(...child.toLines());
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
getRequiredString(): string {
|
||||
if(this.childBlocks.length === 0){
|
||||
return '';
|
||||
}
|
||||
const firstChild = this.childBlocks[0];
|
||||
if(firstChild instanceof LineBlock){
|
||||
return firstChild.toLines()[0].trim().replace(/['"]/g, '');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user