2020-05-23 21:15:20 +02:00
2020-05-31 20:48:13 +02:00
import * as fs from "fs"
2020-05-23 21:15:20 +02:00
import chalk from "chalk"
2020-05-31 20:48:13 +02:00
import { Syntax } from "./ast" ;
2020-06-16 22:24:01 +02:00
import { format , MapLike , FormatArg , countDigits , mapValues , prettyPrint , assert , isPlainObject , isPrimitive , JsonObject , Json } from "./util" ;
2020-05-28 14:08:49 +02:00
import { BOLT_DIAG_NUM_EXTRA_LINES } from "./constants" ;
2020-05-31 20:48:13 +02:00
import { TextPos , TextFile , TextSpan } from "./text" ;
2020-05-23 21:15:20 +02:00
2020-06-07 08:36:15 +02:00
export const E_INVALID_TEST_COMPARE = "The given test results cannot be compared because they use different specifications."
export const E_TESTS_DO_NOT_COMPARE = "This test does not compare with its expected output."
2020-05-31 20:48:13 +02:00
export const E_NO_BOLTFILE_FOUND_IN_PATH_OR_PARENT_DIRS = 'No Boltfile found in {path} or any of its parent directories.'
export const E_SSCAN_ERROR = "Got an unexpected {char}"
export const E_STDLIB_NOT_FOUND = "Package 'stdlib' is required to build the current source set but it was not found. Use --no-std if you know what you are doing."
export const E_PARSE_ERROR = "Expected {expected:enum} but got {actual}"
2020-05-27 19:57:15 +02:00
export const E_MAY_NOT_RETURN_A_VALUE = "Returning a value inside a function that does not return values."
2020-05-26 08:39:46 +02:00
export const E_MUST_RETURN_A_VALUE = "The function must return a value on all control paths." ; ; ; ;
2020-05-25 15:52:11 +02:00
export const E_FILE_NOT_FOUND = "A file named {filename} was not found." ;
2020-05-25 11:29:19 +02:00
export const E_FIELD_HAS_INVALID_VERSION_NUMBER = "Field '{name}' contains an invalid version nunmber."
export const E_FIELD_MUST_BE_STRING = "Field '{name}' must be a string."
export const E_FIELD_NOT_PRESENT = "Field '{name}' is not present."
2020-05-28 14:08:49 +02:00
export const E_FIELD_MUST_BE_BOOLEAN = "Field '{name}' must be a either 'true' or 'false'."
2020-05-23 21:15:20 +02:00
export const E_TYPE_DECLARATION_NOT_FOUND = "A type declaration named '{name}' was not found."
export const E_DECLARATION_NOT_FOUND = "Reference to an undefined declaration '{name}'." ;
2020-10-30 14:53:56 +01:00
export const E_TYPE_MISMATCH = "Types {left} and {right} are not compatible with one another." ;
export const E_THIS_NODE_CAUSED_INVALID_TYPE = "This node resolved to the type {type}, which is incompatible with {origType}."
2020-05-23 22:23:17 +02:00
export const E_TOO_FEW_ARGUMENTS_FOR_FUNCTION_CALL = "Too few arguments for function call. Expected {expected} but got {actual}." ;
export const E_TOO_MANY_ARGUMENTS_FOR_FUNCTION_CALL = "Too many arguments for function call. Expected {expected} but got {actual}." ;
2020-10-29 21:12:11 +01:00
export const E_NOT_CALLABLE = "The result of this expression is not callable."
2020-05-28 14:08:49 +02:00
export const E_CANDIDATE_FUNCTION_REQUIRES_THIS_PARAMETER = "Candidate function requires this parameter."
2020-05-29 20:33:04 +02:00
export const E_ARGUMENT_HAS_NO_CORRESPONDING_PARAMETER = "This argument is missing a corresponding parameter."
2020-05-28 14:08:49 +02:00
export const E_INVALID_ARGUMENTS = "Invalid arguments passed to function '{name}'"
export const E_RECORD_MISSING_MEMBER = "Record {name} does not have a member declaration named {memberName}"
2020-10-30 14:53:56 +01:00
export const E_TYPE_NEVER_MATCHES = "Type '{type}' never matches anything."
2020-05-28 14:08:49 +02:00
export const E_TYPES_MISSING_MEMBER = "Not all types resolve to a record with the a member named '{name}'."
export const E_NODE_DOES_NOT_CONTAIN_MEMBER = "This node does not contain the the member '{name}'."
export const E_MAY_NOT_RETURN_BECAUSE_TYPE_RESOLVES_TO_VOID = "May not return a value because the function's return type resolves to '()'"
export const E_MUST_RETURN_BECAUSE_TYPE_DOES_NOT_RESOLVE_TO_VOID = "Must return a value because the function's return type does not resolve to '()'"
2020-10-30 14:53:56 +01:00
export const E_ARGUMENT_TYPE_NOT_ASSIGNABLE = "This argument's type '{argType}' is not assignable to the function's parameter type '{paramType}'."
2020-10-29 21:12:11 +01:00
export const E_PARAMETER_DECLARED_HERE = "Function parameter was declared here."
export const E_BUILTIN_TYPE_MISSING = "A built-in type named '{name}' in the prelude."
2020-05-23 21:15:20 +02:00
2020-05-29 20:33:04 +02:00
export const TYPE_ERROR_MESSAGES = [
E_TOO_FEW_ARGUMENTS_FOR_FUNCTION_CALL ,
E_TOO_MANY_ARGUMENTS_FOR_FUNCTION_CALL ,
E_ARGUMENT_TYPE_NOT_ASSIGNABLE ,
E_TYPE_MISMATCH ,
]
2020-05-26 08:39:46 +02:00
const BOLT_HARD_ERRORS = process . env [ 'BOLT_HARD_ERRORS' ]
2020-05-23 21:15:20 +02:00
export interface Diagnostic {
message : string ;
severity : string ;
args? : MapLike < FormatArg > ;
2020-06-07 08:36:15 +02:00
node ? : { span : TextSpan | null } ;
2020-05-28 14:08:49 +02:00
nested? : Diagnostic [ ] ;
2020-05-31 20:48:13 +02:00
position? : TextPos ,
file? : TextFile ,
2020-05-23 21:15:20 +02:00
}
2020-05-24 11:00:40 +02:00
function firstIndexOfNonEmpty ( str : string ) {
let j = 0 ;
for ( ; j < str . length ; j ++ ) {
const ch = str [ j ] ;
if ( ch !== ' ' && ch !== '\t' ) {
break ;
}
}
return j
}
2020-06-03 09:19:30 +02:00
export class DiagnosticIndex {
2020-05-31 20:48:13 +02:00
2020-06-03 09:19:30 +02:00
private diagnostics = new Array < Diagnostic > ( ) ;
2020-05-31 20:48:13 +02:00
public add ( diagnostic : Diagnostic ) {
2020-06-03 09:19:30 +02:00
this . diagnostics . push ( diagnostic ) ;
}
public getAllDiagnostics ( ) : IterableIterator < Diagnostic > {
return this . diagnostics [ Symbol . iterator ] ( ) ;
2020-05-31 20:48:13 +02:00
}
}
2020-05-23 21:15:20 +02:00
export class DiagnosticPrinter {
2020-05-27 20:59:45 +02:00
public hasErrors = false
public hasFatal = false ;
2020-05-23 21:15:20 +02:00
2020-05-29 20:33:04 +02:00
private indent = 0 ;
2020-05-23 21:15:20 +02:00
public add ( diagnostic : Diagnostic ) : void {
2020-05-26 08:39:46 +02:00
if ( BOLT_HARD_ERRORS && ( diagnostic . severity === 'error' || diagnostic . severity === 'fatal' ) ) {
let out = ''
if ( diagnostic . args !== undefined ) {
out += format ( diagnostic . message , diagnostic . args ) ;
} else {
out += diagnostic . message ;
}
throw new Error ( out ) ;
}
2020-05-29 20:33:04 +02:00
const indentation = ' ' . repeat ( this . indent ) ;
let out = indentation ;
2020-05-26 08:39:46 +02:00
switch ( diagnostic . severity ) {
case 'error' :
this . hasErrors = true ;
out += chalk . bold . red ( 'error: ' ) ;
break ;
2020-05-27 20:59:45 +02:00
case 'fatal' :
this . hasFatal = true ;
out += chalk . bold . red ( 'fatal:' ) ;
2020-10-29 21:12:11 +01:00
break ;
2020-05-26 08:39:46 +02:00
case 'warning' :
this . hasErrors = true ;
out += chalk . bold . red ( 'warning: ' ) ;
break ;
2020-05-29 20:33:04 +02:00
case 'info' :
out += chalk . bold . yellow ( 'info: ' )
break ;
2020-05-26 08:39:46 +02:00
default :
throw new Error ( ` Unkown severity for diagnostic message. ` ) ;
}
2020-05-23 22:36:18 +02:00
if ( diagnostic . node !== undefined ) {
2020-05-26 08:39:46 +02:00
const span = diagnostic . node . span ! ;
out += chalk . bold . yellow ( ` ${ span . file . origPath } : ${ span . start . line } : ${ span . start . column } : ` ) ;
}
if ( diagnostic . args !== undefined ) {
2020-06-16 22:24:01 +02:00
out += format ( diagnostic . message , diagnostic . args ) + '\n' ;
2020-05-26 08:39:46 +02:00
} else {
out += diagnostic . message + '\n' ;
}
2020-05-31 20:48:13 +02:00
let span = null ;
2020-05-26 08:39:46 +02:00
if ( diagnostic . node !== undefined ) {
2020-05-31 20:48:13 +02:00
span = diagnostic . node . span ! ;
} else if ( diagnostic . position !== undefined ) {
assert ( diagnostic . file !== undefined ) ;
span = new TextSpan ( diagnostic . file ! , diagnostic . position , diagnostic . position )
}
if ( span !== null ) {
2020-05-26 08:39:46 +02:00
out += '\n'
2020-05-23 22:48:24 +02:00
const content = span . file . getText ( ) ;
2020-05-28 14:08:49 +02:00
const startLine = Math . max ( 0 , span . start . line - 1 - BOLT_DIAG_NUM_EXTRA_LINES )
2020-05-23 22:36:18 +02:00
const lines = content . split ( '\n' )
2020-05-28 14:08:49 +02:00
const endLine = Math . min ( lines . length - 1 , ( span . end !== undefined ? span.end.line : startLine ) + BOLT_DIAG_NUM_EXTRA_LINES )
2020-05-23 22:36:18 +02:00
const gutterWidth = Math . max ( 2 , countDigits ( endLine + 1 ) )
for ( let i = startLine ; i < endLine ; i ++ ) {
2020-05-24 11:00:40 +02:00
const line = lines [ i ] ;
let j = firstIndexOfNonEmpty ( line ) ;
2020-05-29 20:33:04 +02:00
out += indentation + ' ' + chalk . bgWhite . black ( ' ' . repeat ( gutterWidth - countDigits ( i + 1 ) ) + ( i + 1 ) . toString ( ) ) + ' ' + line + '\n'
const gutter = indentation + ' ' + chalk . bgWhite . black ( ' ' . repeat ( gutterWidth ) ) + ' '
2020-05-24 11:00:40 +02:00
let mark : number ;
let skip : number ;
if ( i === span . start . line - 1 && i === span . end . line - 1 ) {
skip = span . start . column - 1 ;
mark = span . end . column - span . start . column ;
} else if ( i === span . start . line - 1 ) {
skip = span . start . column - 1 ;
mark = line . length - span . start . column + 1 ;
} else if ( i === span . end . line - 1 ) {
skip = 0 ;
mark = span . end . column - 1 ;
} else if ( i > span . start . line - 1 && i < span . end . line - 1 ) {
skip = 0 ;
mark = line . length ;
} else {
continue ;
}
2020-05-26 08:39:46 +02:00
if ( j <= skip ) {
2020-05-24 11:00:40 +02:00
j = 0 ;
2020-05-23 22:36:18 +02:00
}
2020-05-24 11:00:40 +02:00
out += gutter + ' ' . repeat ( j + skip ) + chalk . red ( '~' . repeat ( mark - j ) ) + '\n'
2020-05-23 22:48:24 +02:00
}
out += '\n'
2020-05-23 21:15:20 +02:00
}
2020-05-26 08:39:46 +02:00
2020-05-23 22:48:24 +02:00
process . stderr . write ( out ) ;
2020-05-29 20:33:04 +02:00
if ( diagnostic . nested !== undefined ) {
this . indent += 2 ;
for ( const nested of diagnostic . nested ) {
this . add ( nested ) ;
}
this . indent -= 2 ;
}
2020-05-23 21:15:20 +02:00
}
}