1(*
2    Copyright (c) 2009 David C.J. Matthews 2008, 2013, 2015.
3
4    This library is free software; you can redistribute it and/or
5    modify it under the terms of the GNU Lesser General Public
6    License version 2.1 as published by the Free Software Foundation.
7    
8    This library is distributed in the hope that it will be useful,
9    but WITHOUT ANY WARRANTY; without even the implied warranty of
10    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11    Lesser General Public License for more details.
12    
13    You should have received a copy of the GNU Lesser General Public
14    License along with this library; if not, write to the Free Software
15    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
16*)
17
18structure Pretty:> PRETTYSIG =
19struct
20
21(*    abstype context =
22        AbsContextLocation of
23            { file: string, startLine: int, startPosition: int, endLine: int, endPosition: int }
24    |   AbsContextProperty of string * string (* User property. *)
25
26    and pretty =
27        AbsPrettyBlock of int * bool * context list * pretty list
28    |   AbsPrettyBreak of int * int
29    |   AbsPrettyString of string
30    |   AbsPrettyStringAndWidth of string * int
31    |   AbsPrettyLineBreak
32    
33    with
34        val ContextLocation = AbsContextLocation
35        and ContextProperty = AbsContextProperty
36        
37        val PrettyBlock = AbsPrettyBlock
38        and PrettyBreak = AbsPrettyBreak
39        and PrettyString = AbsPrettyString
40        
41        fun isPrettyBlock(AbsPrettyBlock _) = true | isPrettyBlock _ = false
42        and isPrettyBreak(AbsPrettyBreak _) = true | isPrettyBreak _ = false
43        and isPrettyString(AbsPrettyString _) = true | isPrettyString _ = false
44
45        fun projPrettyBlock(AbsPrettyBlock b) = b | projPrettyBlock _ = raise Match
46        and projPrettyBreak(AbsPrettyBreak b) = b | projPrettyBreak _ = raise Match
47        and projPrettyString(AbsPrettyString b) = b | projPrettyString _ = raise Match
48    end;*)
49
50    (* This is complicated because the data structures we use here will be exported into
51       the code produced by the compiler.  We can't assume that the same representations
52       will be used by this version of the compiler as are used by the compiler that is
53       compiling this code.  We use an explicit representation here which must be kept in
54       synch with the representation used in DATATYPE_REP.ML *)
55    local
56        open Address
57        fun cast p = toAddress(toMachineWord p)
58    in
59        type context = address
60        type loc = { file: string, startLine: FixedInt.int, startPosition: FixedInt.int, endLine: FixedInt.int, endPosition: FixedInt.int }
61        (* Because the argument tuple has more than 4 fields the address is used rather than copying the fields. *)
62        fun ContextLocation(p: loc): context = cast(0w0, p)
63        and ContextProperty(s1: string, s2: string): context = cast(0w1, s1, s2)
64    end
65
66    local
67        open Address
68        fun cast p = toAddress(toMachineWord p)
69    in
70        type pretty = address
71        
72        val tagPrettyBlock = 0w0
73        and tagPrettyBreak = 0w1
74        (*and tagPrettyLineBreak = 0w2*)        (* Not used in the compiler. *)
75        and tagPrettyString = 0w3
76        (*and tagPrettyStringWithWidth = 0w4*)   (* Not used in the compiler. *)
77        
78        val maxPrettyTag = 0w4 (* Exported because it is used in TagTest. *)
79
80        fun PrettyBlock(offset: FixedInt.int, consistent: bool, context: context list, items: pretty list): pretty =
81            cast(tagPrettyBlock, offset, consistent, context, items)
82        and PrettyBreak(breaks: FixedInt.int, offset: FixedInt.int): pretty = cast(tagPrettyBreak, breaks, offset)
83        and PrettyString(s: string): pretty = cast(tagPrettyString, s)
84
85        fun isPrettyBlock p = toShort(loadWord(p, 0w0)) = tagPrettyBlock
86        and isPrettyBreak p = toShort(loadWord(p, 0w0)) = tagPrettyBreak
87        and isPrettyString p = toShort(loadWord(p, 0w0)) = tagPrettyString
88
89        fun projPrettyBlock p =
90            if isPrettyBlock p
91            then
92            let
93                val (_: int, offset: int, consistent: bool, context: context list, items: pretty list) =
94                    RunCall.unsafeCast p
95            in
96                (offset, consistent, context, items)
97            end
98            else raise Match
99
100        and projPrettyBreak p =
101            if isPrettyBreak p
102            then
103            let
104                val (_: int, breaks: int, offset: int) = RunCall.unsafeCast p
105            in
106                (breaks, offset)
107            end
108            else raise Match
109
110        and projPrettyString p =
111            if isPrettyString p
112            then
113            let
114                val (_: int, s: string) = RunCall.unsafeCast p
115            in
116                s
117            end
118            else raise Match
119    end
120
121    fun uglyPrint p =
122        if isPrettyBlock p then String.concat(map uglyPrint(#4 (projPrettyBlock p)))
123        else if isPrettyBreak p then String.implode(List.tabulate(#1 (projPrettyBreak p), fn _ => #" "))
124        else projPrettyString p
125
126    (* Pretty printer copied directly from basis/PrettyPrinter.  We can't use the
127       same code because the "pretty" type is not the same. *)
128    fun prettyPrint (stream : string -> unit, lineWidth : int) (pretty: pretty): unit =
129    let
130        fun printBlanks n =
131            if n > 0 then (stream " "; printBlanks(n-1)) else ()
132
133        (* Find out whether the block fits and return the space left if it does.
134           Terminates with NONE as soon as it finds the line doesn't fit. *)
135        fun getSize(p, spaceLeft) =
136            if isPrettyBlock p
137            then
138            let
139                val (_, _, _, entries) = projPrettyBlock p
140            in
141                List.foldl(fn (p, SOME s) => getSize(p, s) | (_, NONE) => NONE)
142                    (SOME spaceLeft) entries
143            end
144            
145            else if isPrettyBreak p
146            then
147            let
148                val (blanks, _) = projPrettyBreak p
149            in
150                if blanks <= spaceLeft then SOME(spaceLeft-blanks) else NONE
151            end
152            
153            else
154            let
155                val size = String.size (projPrettyString p)
156            in
157                if size <= spaceLeft
158                then SOME(spaceLeft-size)
159                else NONE
160            end
161
162        (* Lay out the block and return the space that is left after the line
163           has been printed. *)
164        fun layOut (p, indent, spaceLeft) =
165            if isPrettyBlock p
166            then
167            let
168                val (blockOffset, consistent, _, entries) = projPrettyBlock p
169                val blockIndent = indent+blockOffset
170            in
171                case getSize(p, spaceLeft) of
172                    SOME s => (* Fits *)
173                    (
174                        (* Lay out the contents. This will not need to break. *)
175                        List.foldl(fn(p, space) => layOut(p, blockIndent, space)) spaceLeft entries;
176                        s
177                    )
178                |   NONE => (* Doesn't fit - break line somewhere. *)
179                    let
180                        (* Lay out this block, breaking where necessary. *)
181                        fun doPrint([], left) = (* Finished: return what's left. *) left
182                        
183                        |   doPrint(hd :: rest, left) =
184                            if isPrettyBreak hd
185                            then if null rest
186                            then left (* Ignore trailing breaks. *)
187                            else
188                            let
189                                val (blanks, breakOffset) = projPrettyBreak hd
190                                 (* Compute the space of the next item(s) up to the end or the
191                                   next space.  Since we only break at spaces if there are
192                                   Blocks or Strings without spaces between we need to know
193                                   the total size. *)
194                                fun getsp([], left) = SOME left
195                                |   getsp(next::rest, left) =
196                                        if isPrettyBreak next
197                                        then SOME left
198                                        else case getSize(next, left) of
199                                            NONE => NONE
200                                        |   SOME sp => getsp(rest, sp)
201                            in
202                                if consistent orelse left <= blanks orelse
203                                    not(isSome(getsp(rest, left-blanks)))
204                                then (* Either a consistent break or the next item won't fit. *)
205                                (
206                                    stream "\n";
207                                    printBlanks(blockIndent+breakOffset);
208                                    doPrint(rest, lineWidth-blockIndent-breakOffset)
209                                )
210                                else (* We don't need to break here. *)
211                                (
212                                    printBlanks blanks;
213                                    doPrint(rest, left-blanks)
214                                )
215                            end
216                            
217                            else if isPrettyString hd
218                            then
219                            let
220                                val s = projPrettyString hd
221                            in
222                                stream s;
223                                doPrint(rest, left-size s)
224                            end
225
226                            else (* Block *) doPrint(rest, layOut(hd, blockIndent, left))
227
228                        val onLine = doPrint(entries, spaceLeft);
229                    in
230                        onLine
231                    end
232            end
233            
234            else if isPrettyBreak p
235            then
236            let
237                val (blanks, _) = projPrettyBreak p
238            in
239                printBlanks blanks; Int.max(spaceLeft-blanks, 0)
240            end
241            
242            else
243            let
244                val st = projPrettyString p
245            in
246                stream st; Int.max(spaceLeft-String.size st, 0)
247            end
248
249    in
250        if layOut(pretty, 0, lineWidth) <> lineWidth
251        then stream "\n" (* End the line unless we haven't written anything. *)
252        else ()
253    end
254
255    local
256        open Universal
257    in
258        (* Tag for pretty printed out from PolyML.print. *)
259        val printOutputTag : (pretty -> unit) tag = tag()
260        (* Compiler output.  Used for timing information and compiler debug output. *)
261        and compilerOutputTag: (pretty->unit) tag = tag()
262    end
263
264    local
265        open Universal
266        fun getTag (t: (pretty -> unit) tag) (tagList: universal list) : pretty -> unit =
267            case List.find (tagIs t) tagList of
268                SOME a => tagProject t a
269            |   NONE => fn _ => () (* Use the default *)
270    in
271        val getPrintOutput = getTag printOutputTag
272        and getCompilerOutput = getTag compilerOutputTag
273        
274        (* The low-level code-generators print strings a bit at a time and separate the lines
275           with new-line characters.  This provides a simple string printer for backwards
276           compatibility.  It has now been extended with explicit tab stops.  By default
277           tabs are inserted every eight characters. *)
278        fun getSimplePrinter(parameters, tabStops) =
279        let
280            val compilerOut: pretty -> unit = getTag compilerOutputTag parameters
281            val buff = ref ""
282            fun printStream (s: string) =
283            let
284                (* If there's a newline split there. *)
285                val (a, b) = Substring.splitl(fn #"\n" => false | _ => true) (Substring.full(!buff ^ s))
286            in
287                if Substring.size b = 0 (* No newline. *)
288                then buff := Substring.string a
289                else
290                let
291                    val str = Substring.string a
292                    (* Split at the tabs. *)
293                    val fields = String.fields (fn #"\t" => true | _ => false) str
294                    fun nSpaces n = if n <= 0 then "" else CharVector.tabulate(n, fn _ => #" ")
295                    fun rebuild(f::l, tabs) =
296                        let
297                            fun findTab(length, []) =
298                                (8 - Int.rem(length, 8), [])
299                            |   findTab(length, tab::tabs) =
300                                if length >= tab
301                                then
302                                let
303                                    val (t, ts) = findTab(length-tab, tabs)
304                                in
305                                    (t+length, ts)
306                                end
307                                else (tab-length, tabs)
308                            val (spaces, nextTab) = findTab(size f, tabs)
309                        in
310                            f :: nSpaces spaces :: rebuild(l, nextTab)
311                        end
312
313                    |   rebuild([], _) = []
314                in
315                    compilerOut(PrettyString(String.concat(rebuild(fields, tabStops))));
316                    buff := "";
317                    printStream(Substring.string(Substring.slice(b, 1, NONE))) 
318                end
319            end
320        in
321            printStream
322        end
323    end
324
325    (* Types that can be shared. *)
326    structure Sharing =
327    struct
328        type pretty     = pretty
329        and  context    = context
330    end
331end;
332