diff --git a/AST.fu b/AST.fu index 93077e93..a609ae4b 100644 --- a/AST.fu +++ b/AST.fu @@ -1277,6 +1277,10 @@ class FuMethodGroup : FuMember public abstract class FuContainerType : FuType { internal bool IsPublic; + internal int StartLine; + internal int StartColumn; + internal int EndLine; + internal int EndColumn; } public class FuEnum : FuContainerType diff --git a/Parser.fu b/Parser.fu index 9147923e..364d33c3 100644 --- a/Parser.fu +++ b/Parser.fu @@ -930,6 +930,17 @@ public class FuParser : FuLexer method.Body = ParseBlock(); } + int GetCurrentLine() => this.Host.Program.LineLocs.Count - this.Host.Program.SourceFiles.Last().Line - 1; + + int GetTokenColumn() => this.TokenLoc - this.Host.Program.LineLocs.Last(); + + void CloseContainer!(FuContainerType! type) + { + type.EndLine = GetCurrentLine(); + type.EndColumn = this.Loc - this.Host.Program.LineLocs.Last(); + Expect(FuToken.RightBrace); + } + static string CallTypeToString(FuCallType callType) { switch (callType) { @@ -943,10 +954,10 @@ public class FuParser : FuLexer } } - void ParseClass!(FuCodeDoc# doc, bool isPublic, FuCallType callType) + void ParseClass!(FuCodeDoc# doc, int line, int column, bool isPublic, FuCallType callType) { Expect(FuToken.Class); - FuClass# klass = new FuClass { Loc = this.TokenLoc, Documentation = doc, IsPublic = isPublic, CallType = callType, Name = this.StringValue }; + FuClass# klass = new FuClass { Loc = this.TokenLoc, Documentation = doc, StartLine = line, StartColumn = column, IsPublic = isPublic, CallType = callType, Name = this.StringValue }; if (Expect(FuToken.Id)) AddSymbol(this.Host.Program, klass); if (Eat(FuToken.Colon)) { @@ -1054,16 +1065,18 @@ public class FuParser : FuLexer AddSymbol(klass, field); Expect(FuToken.Semicolon); } - Expect(FuToken.RightBrace); + CloseContainer(klass); } - void ParseEnum!(FuCodeDoc# doc, bool isPublic) + void ParseEnum!(FuCodeDoc# doc, int line, int column, bool isPublic) { Expect(FuToken.Enum); bool flags = Eat(FuToken.Asterisk); FuEnum# enu = this.Host.Program.System.NewEnum(flags); - enu.Loc = this.TokenLoc; enu.Documentation = doc; + enu.StartLine = line; + enu.StartColumn = column; + enu.Loc = this.TokenLoc; enu.IsPublic = isPublic; enu.Name = this.StringValue; if (Expect(FuToken.Id)) @@ -1078,7 +1091,7 @@ public class FuParser : FuLexer ReportError("enum* symbol must be assigned a value"); AddSymbol(enu, konst); } while (Eat(FuToken.Comma)); - Expect(FuToken.RightBrace); + CloseContainer(enu); } public void Parse!(string filename, byte[] input, int inputLength) @@ -1086,21 +1099,23 @@ public class FuParser : FuLexer Open(filename, input, inputLength); while (!See(FuToken.EndOfFile)) { FuCodeDoc# doc = ParseDoc(); + int line = GetCurrentLine(); + int column = GetTokenColumn(); bool isPublic = Eat(FuToken.Public); switch (this.CurrentToken) { // class case FuToken.Class: - ParseClass(doc, isPublic, FuCallType.Normal); + ParseClass(doc, line, column, isPublic, FuCallType.Normal); break; case FuToken.Static: case FuToken.Abstract: case FuToken.Sealed: - ParseClass(doc, isPublic, ParseCallType()); + ParseClass(doc, line, column, isPublic, ParseCallType()); break; // enum case FuToken.Enum: - ParseEnum(doc, isPublic); + ParseEnum(doc, line, column, isPublic); break; // native diff --git a/editors/vscode/src/extension.ts b/editors/vscode/src/extension.ts index ee7e43a0..0801f4a0 100644 --- a/editors/vscode/src/extension.ts +++ b/editors/vscode/src/extension.ts @@ -19,7 +19,7 @@ // along with Fusion Transpiler. If not, see http://www.gnu.org/licenses/ import * as vscode from "vscode"; -import { FuParser, FuProgram, FuSystem, FuSema, FuSemaHost } from "./fucheck.js"; +import { FuParser, FuProgram, FuSystem, FuSema, FuSemaHost, FuEnum } from "./fucheck.js"; class VsCodeHost extends FuSemaHost { @@ -154,6 +154,25 @@ class VsCodeDefinitionProvider extends VsCodeHost } } +class VsCodeSymbolProvider extends VsCodeHost +{ + provideSymbols(document: vscode.TextDocument): vscode.DocumentSymbol[] + { + this.parseDocument(document, this.createParser()); + const symbols : vscode.DocumentSymbol[] = []; + for (let container = this.program.first; container != null; container = container.next) { + const loc = container.loc; + const line = this.program.getLine(loc); + const startColumn = loc - this.program.lineLocs[line]; + const endColumn = startColumn + container.getLocLength(); + symbols.push(new vscode.DocumentSymbol(container.name, "", container instanceof FuEnum ? vscode.SymbolKind.Enum : vscode.SymbolKind.Class, + new vscode.Range(container.startLine, container.startColumn, container.endLine, container.endColumn), + new vscode.Range(line, startColumn, line, endColumn))); + } + return symbols; + } +} + export function activate(context: vscode.ExtensionContext): void { const diagnostics = new VsCodeDiagnostics(); @@ -170,4 +189,9 @@ export function activate(context: vscode.ExtensionContext): void return new VsCodeDefinitionProvider().findDefinition(document, position); } }); + vscode.languages.registerDocumentSymbolProvider("fusion", { + provideDocumentSymbols(document, token) { + return new VsCodeSymbolProvider().provideSymbols(document); + } + }); } diff --git a/libfut.cpp b/libfut.cpp index 35715c56..06ffe6b5 100644 --- a/libfut.cpp +++ b/libfut.cpp @@ -4106,6 +4106,23 @@ void FuParser::parseMethod(FuClass * klass, std::shared_ptr method) method->body = parseBlock(); } +int FuParser::getCurrentLine() const +{ + return std::ssize(this->host->program->lineLocs) - this->host->program->sourceFiles.back().line - 1; +} + +int FuParser::getTokenColumn() const +{ + return this->tokenLoc - this->host->program->lineLocs.back(); +} + +void FuParser::closeContainer(FuContainerType * type) +{ + type->endLine = getCurrentLine(); + type->endColumn = this->loc - this->host->program->lineLocs.back(); + expect(FuToken::rightBrace); +} + std::string_view FuParser::callTypeToString(FuCallType callType) { switch (callType) { @@ -4126,12 +4143,14 @@ std::string_view FuParser::callTypeToString(FuCallType callType) } } -void FuParser::parseClass(std::shared_ptr doc, bool isPublic, FuCallType callType) +void FuParser::parseClass(std::shared_ptr doc, int line, int column, bool isPublic, FuCallType callType) { expect(FuToken::class_); std::shared_ptr klass = std::make_shared(); klass->loc = this->tokenLoc; klass->documentation = doc; + klass->startLine = line; + klass->startColumn = column; klass->isPublic = isPublic; klass->callType = callType; klass->name = this->stringValue; @@ -4240,16 +4259,18 @@ void FuParser::parseClass(std::shared_ptr doc, bool isPublic, FuCallT addSymbol(klass.get(), field); expect(FuToken::semicolon); } - expect(FuToken::rightBrace); + closeContainer(klass.get()); } -void FuParser::parseEnum(std::shared_ptr doc, bool isPublic) +void FuParser::parseEnum(std::shared_ptr doc, int line, int column, bool isPublic) { expect(FuToken::enum_); bool flags = eat(FuToken::asterisk); std::shared_ptr enu = this->host->program->system->newEnum(flags); - enu->loc = this->tokenLoc; enu->documentation = doc; + enu->startLine = line; + enu->startColumn = column; + enu->loc = this->tokenLoc; enu->isPublic = isPublic; enu->name = this->stringValue; if (expect(FuToken::id)) @@ -4271,7 +4292,7 @@ void FuParser::parseEnum(std::shared_ptr doc, bool isPublic) addSymbol(enu.get(), konst); } while (eat(FuToken::comma)); - expect(FuToken::rightBrace); + closeContainer(enu.get()); } void FuParser::parse(std::string_view filename, uint8_t const * input, int inputLength) @@ -4279,18 +4300,20 @@ void FuParser::parse(std::string_view filename, uint8_t const * input, int input open(filename, input, inputLength); while (!see(FuToken::endOfFile)) { std::shared_ptr doc = parseDoc(); + int line = getCurrentLine(); + int column = getTokenColumn(); bool isPublic = eat(FuToken::public_); switch (this->currentToken) { case FuToken::class_: - parseClass(doc, isPublic, FuCallType::normal); + parseClass(doc, line, column, isPublic, FuCallType::normal); break; case FuToken::static_: case FuToken::abstract: case FuToken::sealed: - parseClass(doc, isPublic, parseCallType()); + parseClass(doc, line, column, isPublic, parseCallType()); break; case FuToken::enum_: - parseEnum(doc, isPublic); + parseEnum(doc, line, column, isPublic); break; case FuToken::native: this->host->program->topLevelNatives.push_back(parseNative()->content); diff --git a/libfut.cs b/libfut.cs index 6525e0bc..cc618470 100644 --- a/libfut.cs +++ b/libfut.cs @@ -2839,6 +2839,14 @@ public abstract class FuContainerType : FuType { internal bool IsPublic; + + internal int StartLine; + + internal int StartColumn; + + internal int EndLine; + + internal int EndColumn; } public class FuEnum : FuContainerType @@ -4382,6 +4390,17 @@ void ParseMethod(FuClass klass, FuMethod method) method.Body = ParseBlock(); } + int GetCurrentLine() => this.Host.Program.LineLocs.Count - this.Host.Program.SourceFiles[^1].Line - 1; + + int GetTokenColumn() => this.TokenLoc - this.Host.Program.LineLocs[^1]; + + void CloseContainer(FuContainerType type) + { + type.EndLine = GetCurrentLine(); + type.EndColumn = this.Loc - this.Host.Program.LineLocs[^1]; + Expect(FuToken.RightBrace); + } + static string CallTypeToString(FuCallType callType) { switch (callType) { @@ -4402,10 +4421,10 @@ static string CallTypeToString(FuCallType callType) } } - void ParseClass(FuCodeDoc doc, bool isPublic, FuCallType callType) + void ParseClass(FuCodeDoc doc, int line, int column, bool isPublic, FuCallType callType) { Expect(FuToken.Class); - FuClass klass = new FuClass { Loc = this.TokenLoc, Documentation = doc, IsPublic = isPublic, CallType = callType, Name = this.StringValue }; + FuClass klass = new FuClass { Loc = this.TokenLoc, Documentation = doc, StartLine = line, StartColumn = column, IsPublic = isPublic, CallType = callType, Name = this.StringValue }; if (Expect(FuToken.Id)) AddSymbol(this.Host.Program, klass); if (Eat(FuToken.Colon)) { @@ -4490,16 +4509,18 @@ void ParseClass(FuCodeDoc doc, bool isPublic, FuCallType callType) AddSymbol(klass, field); Expect(FuToken.Semicolon); } - Expect(FuToken.RightBrace); + CloseContainer(klass); } - void ParseEnum(FuCodeDoc doc, bool isPublic) + void ParseEnum(FuCodeDoc doc, int line, int column, bool isPublic) { Expect(FuToken.Enum); bool flags = Eat(FuToken.Asterisk); FuEnum enu = this.Host.Program.System.NewEnum(flags); - enu.Loc = this.TokenLoc; enu.Documentation = doc; + enu.StartLine = line; + enu.StartColumn = column; + enu.Loc = this.TokenLoc; enu.IsPublic = isPublic; enu.Name = this.StringValue; if (Expect(FuToken.Id)) @@ -4515,7 +4536,7 @@ void ParseEnum(FuCodeDoc doc, bool isPublic) AddSymbol(enu, konst); } while (Eat(FuToken.Comma)); - Expect(FuToken.RightBrace); + CloseContainer(enu); } public void Parse(string filename, byte[] input, int inputLength) @@ -4523,18 +4544,20 @@ public void Parse(string filename, byte[] input, int inputLength) Open(filename, input, inputLength); while (!See(FuToken.EndOfFile)) { FuCodeDoc doc = ParseDoc(); + int line = GetCurrentLine(); + int column = GetTokenColumn(); bool isPublic = Eat(FuToken.Public); switch (this.CurrentToken) { case FuToken.Class: - ParseClass(doc, isPublic, FuCallType.Normal); + ParseClass(doc, line, column, isPublic, FuCallType.Normal); break; case FuToken.Static: case FuToken.Abstract: case FuToken.Sealed: - ParseClass(doc, isPublic, ParseCallType()); + ParseClass(doc, line, column, isPublic, ParseCallType()); break; case FuToken.Enum: - ParseEnum(doc, isPublic); + ParseEnum(doc, line, column, isPublic); break; case FuToken.Native: this.Host.Program.TopLevelNatives.Add(ParseNative().Content); diff --git a/libfut.hpp b/libfut.hpp index 1eb3eaf6..e1d115a4 100644 --- a/libfut.hpp +++ b/libfut.hpp @@ -1341,6 +1341,10 @@ class FuContainerType : public FuType FuContainerType() = default; public: bool isPublic; + int startLine; + int startColumn; + int endLine; + int endColumn; }; class FuEnum : public FuContainerType @@ -1619,9 +1623,12 @@ class FuParser : public FuLexer std::shared_ptr parseStatement(); FuCallType parseCallType(); void parseMethod(FuClass * klass, std::shared_ptr method); + int getCurrentLine() const; + int getTokenColumn() const; + void closeContainer(FuContainerType * type); static std::string_view callTypeToString(FuCallType callType); - void parseClass(std::shared_ptr doc, bool isPublic, FuCallType callType); - void parseEnum(std::shared_ptr doc, bool isPublic); + void parseClass(std::shared_ptr doc, int line, int column, bool isPublic, FuCallType callType); + void parseEnum(std::shared_ptr doc, int line, int column, bool isPublic); }; class FuSemaHost : public FuParserHost diff --git a/libfut.js b/libfut.js index 99f02868..c120a6eb 100644 --- a/libfut.js +++ b/libfut.js @@ -2971,6 +2971,10 @@ class FuMethodGroup extends FuMember export class FuContainerType extends FuType { isPublic; + startLine; + startColumn; + endLine; + endColumn; } export class FuEnum extends FuContainerType @@ -4591,6 +4595,23 @@ export class FuParser extends FuLexer method.body = this.#parseBlock(); } + #getCurrentLine() + { + return this.host.program.lineLocs.length - this.host.program.sourceFiles.at(-1).line - 1; + } + + #getTokenColumn() + { + return this.tokenLoc - this.host.program.lineLocs.at(-1); + } + + #closeContainer(type) + { + type.endLine = this.#getCurrentLine(); + type.endColumn = this.loc - this.host.program.lineLocs.at(-1); + this.expect(FuToken.RIGHT_BRACE); + } + static #callTypeToString(callType) { switch (callType) { @@ -4611,10 +4632,10 @@ export class FuParser extends FuLexer } } - #parseClass(doc, isPublic, callType) + #parseClass(doc, line, column, isPublic, callType) { this.expect(FuToken.CLASS); - let klass = Object.assign(new FuClass(), { loc: this.tokenLoc, documentation: doc, isPublic: isPublic, callType: callType, name: this.stringValue }); + let klass = Object.assign(new FuClass(), { loc: this.tokenLoc, documentation: doc, startLine: line, startColumn: column, isPublic: isPublic, callType: callType, name: this.stringValue }); if (this.expect(FuToken.ID)) this.#addSymbol(this.host.program, klass); if (this.eat(FuToken.COLON)) { @@ -4700,16 +4721,18 @@ export class FuParser extends FuLexer this.#addSymbol(klass, field); this.expect(FuToken.SEMICOLON); } - this.expect(FuToken.RIGHT_BRACE); + this.#closeContainer(klass); } - #parseEnum(doc, isPublic) + #parseEnum(doc, line, column, isPublic) { this.expect(FuToken.ENUM); let flags = this.eat(FuToken.ASTERISK); let enu = this.host.program.system.newEnum(flags); - enu.loc = this.tokenLoc; enu.documentation = doc; + enu.startLine = line; + enu.startColumn = column; + enu.loc = this.tokenLoc; enu.isPublic = isPublic; enu.name = this.stringValue; if (this.expect(FuToken.ID)) @@ -4725,7 +4748,7 @@ export class FuParser extends FuLexer this.#addSymbol(enu, konst); } while (this.eat(FuToken.COMMA)); - this.expect(FuToken.RIGHT_BRACE); + this.#closeContainer(enu); } parse(filename, input, inputLength) @@ -4733,18 +4756,20 @@ export class FuParser extends FuLexer this.open(filename, input, inputLength); while (!this.see(FuToken.END_OF_FILE)) { let doc = this.#parseDoc(); + let line = this.#getCurrentLine(); + let column = this.#getTokenColumn(); let isPublic = this.eat(FuToken.PUBLIC); switch (this.currentToken) { case FuToken.CLASS: - this.#parseClass(doc, isPublic, FuCallType.NORMAL); + this.#parseClass(doc, line, column, isPublic, FuCallType.NORMAL); break; case FuToken.STATIC: case FuToken.ABSTRACT: case FuToken.SEALED: - this.#parseClass(doc, isPublic, this.#parseCallType()); + this.#parseClass(doc, line, column, isPublic, this.#parseCallType()); break; case FuToken.ENUM: - this.#parseEnum(doc, isPublic); + this.#parseEnum(doc, line, column, isPublic); break; case FuToken.NATIVE: this.host.program.topLevelNatives.push(this.#parseNative().content);