20 people like it.

Pretty Print Source Code Errors

A snippet that allows you to pretty print source code errors

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
37: 
38: 
39: 
40: 
41: 
42: 
43: 
44: 
45: 
46: 
47: 
48: 
49: 
50: 
51: 
52: 
53: 
54: 
55: 
56: 
57: 
58: 
59: 
60: 
61: 
62: 
63: 
64: 
65: 
66: 
67: 
68: 
69: 
70: 
71: 
72: 
73: 
74: 
75: 
76: 
77: 
78: 
79: 
80: 
module SourceErrorPrinter = 
  
  open System
  open System.Text

  let private splitLines (input:string) = 
    let input = (string input).Replace("\r\n", "\n").Replace("\r", "\n")
    RegularExpressions.Regex.Split(input, "\n")

  let private lineNum (pad:int) (input:int) = 
    (string input).PadLeft(pad, '0')

  let private makeArrow (times:int) =
    let result = new StringBuilder(times)
    result.Insert(0, "-", times).ToString() + "^"
  
  let sprintError (above:int, below:int) (line:int, column:int) (source:string) =
    let source = source |> splitLines
    let result = ref ""

    if line <= source.Length && line > 0 then
      let text = source.[line - 1]
      let nrLength = (line+below |> string).Length
      
      // Deal with lines above the error one
      for i = 1 to above do
        let prevLine = line - i

        if prevLine >= 1 then
          let num = prevLine |> lineNum nrLength
          let text = sprintf "%s: %s\n" num source.[prevLine-1] 
          result := text + !result

      // Error line and arrow
      let arrow = nrLength + column + 1 |> makeArrow
      let num = line |> lineNum nrLength
      let text = sprintf "%s: %s\n%s\n" num text arrow
      result := !result + text

      // Deal with lines below the error one
      for i = 1 to below do
        let nextLine = line + i

        if nextLine <= source.Length then
          let num = nextLine |> lineNum nrLength
          let text = sprintf "%s: %s\n" num source.[nextLine-1] 
          result := !result + text

    !result

// Example, a piece of source code from IronJS 
let source = @"/// Adds a catch variable to the scope
let addCatchLocal name (s:S) = 
  match s |> locals |> Map.tryFind name with
  | None -> 
    s |> addLocal name None
    let local = s |> locals |> Map.find name 
    let local = local |> Local.decreaseActive
    s |> replaceLocal local

  | Some local ->
    let index = LocalIndex.New (s |> localCount) None
    let local = local |> Local.addIndex index

    s := 
      {!s with 
        LocalCount = index.Index + 1
        Locals = s |> locals |> Map.add name local}"


// We partially appli SourceErrorPrinter.sprintError with
// a tuple that is the lines to show before and after
// the errornous line
let linesBefore = 2
let linesAfter = 3
let sprintError = SourceErrorPrinter.sprintError (linesBefore, linesAfter)

// We can use our function like this to print error on line 12, column 15
// from source and then just pipe it into printfn
source |> sprintError (12, 15) |> printfn "%s"
namespace System
namespace System.Text
val private splitLines : input:string -> string []

Full name: Script.SourceErrorPrinter.splitLines
val input : string
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = String

Full name: Microsoft.FSharp.Core.string
namespace System.Text.RegularExpressions
Multiple items
type Regex =
  new : pattern:string -> Regex + 1 overload
  member GetGroupNames : unit -> string[]
  member GetGroupNumbers : unit -> int[]
  member GroupNameFromNumber : i:int -> string
  member GroupNumberFromName : name:string -> int
  member IsMatch : input:string -> bool + 1 overload
  member Match : input:string -> Match + 2 overloads
  member Matches : input:string -> MatchCollection + 1 overload
  member Options : RegexOptions
  member Replace : input:string * replacement:string -> string + 5 overloads
  ...

Full name: System.Text.RegularExpressions.Regex

--------------------
RegularExpressions.Regex(pattern: string) : unit
RegularExpressions.Regex(pattern: string, options: RegularExpressions.RegexOptions) : unit
RegularExpressions.Regex.Split(input: string, pattern: string) : string []
RegularExpressions.Regex.Split(input: string, pattern: string, options: RegularExpressions.RegexOptions) : string []
val private lineNum : pad:int -> input:int -> string

Full name: Script.SourceErrorPrinter.lineNum
val pad : int
Multiple items
val int : value:'T -> int (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.int

--------------------
type int = int32

Full name: Microsoft.FSharp.Core.int

--------------------
type int<'Measure> = int

Full name: Microsoft.FSharp.Core.int<_>
val input : int
val private makeArrow : times:int -> string

Full name: Script.SourceErrorPrinter.makeArrow
val times : int
val result : StringBuilder
Multiple items
type StringBuilder =
  new : unit -> StringBuilder + 5 overloads
  member Append : value:string -> StringBuilder + 18 overloads
  member AppendFormat : format:string * arg0:obj -> StringBuilder + 4 overloads
  member AppendLine : unit -> StringBuilder + 1 overload
  member Capacity : int with get, set
  member Chars : int -> char with get, set
  member Clear : unit -> StringBuilder
  member CopyTo : sourceIndex:int * destination:char[] * destinationIndex:int * count:int -> unit
  member EnsureCapacity : capacity:int -> int
  member Equals : sb:StringBuilder -> bool
  ...

Full name: System.Text.StringBuilder

--------------------
StringBuilder() : unit
StringBuilder(capacity: int) : unit
StringBuilder(value: string) : unit
StringBuilder(value: string, capacity: int) : unit
StringBuilder(capacity: int, maxCapacity: int) : unit
StringBuilder(value: string, startIndex: int, length: int, capacity: int) : unit
StringBuilder.Insert(index: int, value: obj) : StringBuilder
   (+0 other overloads)
StringBuilder.Insert(index: int, value: uint64) : StringBuilder
   (+0 other overloads)
StringBuilder.Insert(index: int, value: uint32) : StringBuilder
   (+0 other overloads)
StringBuilder.Insert(index: int, value: uint16) : StringBuilder
   (+0 other overloads)
StringBuilder.Insert(index: int, value: decimal) : StringBuilder
   (+0 other overloads)
StringBuilder.Insert(index: int, value: float) : StringBuilder
   (+0 other overloads)
StringBuilder.Insert(index: int, value: float32) : StringBuilder
   (+0 other overloads)
StringBuilder.Insert(index: int, value: int64) : StringBuilder
   (+0 other overloads)
StringBuilder.Insert(index: int, value: int) : StringBuilder
   (+0 other overloads)
StringBuilder.Insert(index: int, value: char []) : StringBuilder
   (+0 other overloads)
val sprintError : above:int * below:int -> line:int * column:int -> source:string -> string

Full name: Script.SourceErrorPrinter.sprintError
val above : int
val below : int
val line : int
val column : int
val source : string
val source : string []
val result : string ref
Multiple items
val ref : value:'T -> 'T ref

Full name: Microsoft.FSharp.Core.Operators.ref

--------------------
type 'T ref = Ref<'T>

Full name: Microsoft.FSharp.Core.ref<_>
property Array.Length: int
val text : string
val nrLength : int
val i : int
val prevLine : int
val num : string
val sprintf : format:Printf.StringFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.sprintf
val arrow : string
val nextLine : int
val source : string

Full name: Script.source
val linesBefore : int

Full name: Script.linesBefore
val linesAfter : int

Full name: Script.linesAfter
val sprintError : (int * int -> string -> string)

Full name: Script.sprintError
module SourceErrorPrinter

from Script
val printfn : format:Printf.TextWriterFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
Next Version Raw view Test code New version

More information

Link:http://fssnip.net/3E
Posted:13 years ago
Author:fholm
Tags: prettyprint , source , print , error