Created
October 28, 2013 02:53
-
-
Save drslump/7190767 to your computer and use it in GitHub Desktop.
Proposal for a diagnostics system in the Boo compiler
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Diagnostics system based on the one from clang | |
http://clang.llvm.org/diagnostics.html | |
http://clang.llvm.org/docs/InternalsManual.html#the-diagnostics-subsystem | |
http://clang.llvm.org/doxygen/Diagnostic_8h_source.html | |
A DiagsEngine will be associated with the the CompilerContext and made | |
available to all the compiler components, including meta programming | |
extension points (macros, ast attributes, ..). The idea is that an | |
extensible compiler like Boo should be very user friendly when reporting | |
errors. | |
Best practices for creating diagnostics: | |
- Every diagnostic must have a unique numeric identifier. | |
- Keep the string short. It should ideally be less than 80 chars. This | |
avoids the diagnostic wrapping when printed, and forces you to think | |
about the important point you are conveying with the diagnostic. | |
- Take advantage of location information. The user will be able to see | |
the line and location of the caret, so you don’t need to tell them that | |
the problem is with the 4th argument to the function: just point to it. | |
- Do not capitalize the diagnostic string, and do not end it with a period. | |
- If you need to quote something in the diagnostic string, use single quotes. | |
- Produce Hints only when actually sure they will work as intended. | |
The message will be processed by String.Format so it can contain advanced | |
placeholder logic besides these custom formatters: | |
- Plurals: `{2:plural=none|one|some|||many||||a lot}` 1 = one, 3 = some, 1000 = a lot | |
- Select: `{1:sel=unary|binary}` -> 0 = unary, 1 = binary | |
- Ordinal: `{1:ord}` -> 2 = 2nd | |
- Or: `{1:or}` -> (this, that, another) = this, that or another | |
- And: `{1:and}` -> (this, that, another) = this, that and another | |
- Type: `{1:type}` -> node = FooBar | |
- Fully Qualified Name: `{1:fqn}` -> node = System.Console.WriteLine | |
""" | |
namespace Boo.Lang.Compiler.Diagnostics | |
import Boo.Lang.Compiler(CompilerContext, ICompilerInput) | |
import Boo.Lang.Compiler.Ast(LexicalInfo, SourceLocation, Node) | |
# The level of a diagnostic | |
enum DiagLevel: | |
Ignored | |
Note | |
Warning | |
Error | |
Fatal | |
# Delegate for the notification of new diagnostics | |
callable DiagnosticHandler(DiagLevel, Diagnostic) | |
class DiagsEngine: | |
""" | |
Main interface for the diagnostics system | |
""" | |
# Notify a new diagnostics session | |
event OnStartContext as callable(CompilerContext) | |
event OnStopContext as callable(CompilerContext) | |
# Notify a new file being analyzed | |
event OnStartFile as callable(ICompilerInput) | |
event OnStopFile as callable(ICompilerInput) | |
# Notify successfully consumed diagnostics | |
event Handler as DiagnosticHandler | |
property IgnoreAllWarnings as bool | |
property WarningsAsErrors as bool | |
property ErrorsAsFatal as bool | |
property SuppressAllDiagnostics as bool | |
property ErrorLimit as uint | |
property IgnoredCodes as (int) | |
[getter(FatalOcurred)] | |
_fatalOcurred = false | |
[getter(NoteCount)] | |
_noteCount = 0 | |
[getter(WarningCount)] | |
_warningCount = 0 | |
[getter(ErrorCount)] | |
_errorCount = 0 | |
Count: | |
get: return ErrorCount + WarningCount + NoteCount + (1 if FatalOcurred else 0) | |
virtual protected def Map(diag as Diagnostic) as DiagLevel: | |
""" Maps a diagnostic to a normalized severity level based on the configuration | |
""" | |
if FatalOcurred or SuppressAllDiagnostics: | |
return DiagLevel.Ignored | |
level = diag.Level | |
if diag.Code in IgnoredCodes: | |
level = DiagLevel.Ignored | |
if WarningsAsErrors and level == DiagLevel.Warning: | |
level = DiagLevel.Error | |
if IgnoreAllWarnings and level == DiagLevel.Warning: | |
level = DiagLevel.Ignored | |
if ErrorsAsFatal and level == DiagLevel.Error: | |
level = DiagLevel.Fatal | |
if ErrorLimit != 0 and _errorCount >= ErrorLimit: | |
level = DiagLevel.Ignored | |
return level | |
virtual def Consume(diag as Diagnostic): | |
""" Consume a diagnostic produced by the compiler to notify | |
the configured handlers if needed. | |
""" | |
level = Map(diag) | |
if level == DiagLevel.Ignored: | |
return | |
if level == DiagLevel.Fatal: | |
_fatalOcurred = true | |
elif level == DiagLevel.Error: | |
_errorCount += 1 | |
elif level == DiagLevel.Warning: | |
_warningCount += 1 | |
elif level == DiagLevel.Note: | |
_noteCount += 1 | |
if Handler: | |
Handler(level, diag) | |
class Hint: | |
""" | |
Represents a code modification: | |
- Insert: Count = 0, Insert = 'foo' | |
- Replace: Count = 5, Insert = 'foo' | |
- Remove: Count = 5, Insert = '' | |
""" | |
# Original position | |
property Caret as SourceLocation | |
# Number of characters to remove | |
property Count as int | |
# Text to insert at the caret | |
property Insert as string | |
class Range: | |
""" | |
Represents a segment of the source code | |
""" | |
property From as SourceLocation | |
property Until as SourceLocation | |
class NodeRange(Range): | |
def constructor(node as Node): | |
From = SourceLocation(node.LexicalInfo.Line, node.LexicalInfo.Column) | |
Until = SourceLocation( | |
node.LexicalInfo.Line, | |
node.LexicalInfo.Column + node.ToCodeString().Length | |
) | |
class Diagnostic: | |
""" | |
Represents a diagnostic. It can either be subclassed for common cases | |
or generated via factories to reduce repeated code. | |
""" | |
# Severity of the diagnostic | |
property Level as DiagLevel | |
# Unique code identifier for the diagnostic | |
property Code as int | |
# Reported message. It can use placeholders for Arguments | |
property Message as string | |
# Exact position where the diagnostic found the problem | |
property Caret as LexicalInfo | |
# Values to fill the placeholders in the message | |
property Arguments as List[of object] | |
# Signals important aspects of the original code (from, until) | |
property Ranges as List[of Range] | |
# Additional information about how to solve this specific problem | |
property Hints as List[of Hint] | |
class NodeDiagnostic(Diagnostic): | |
def constructor(node as Node): | |
Caret = node.LexicalInfo | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment