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 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020
|
//=========================================================================
// (c) Microsoft Corporation 2005-2009.
//=========================================================================
#nowarn "52" // The value has been copied to ensure the original is not mutated by this operation
namespace Microsoft.FSharp.Text.StructuredFormat
// Breakable block layout implementation.
// This is a fresh implementation of pre-existing ideas.
open System
open System.Diagnostics
open System.Text
open System.IO
open System.Reflection
open System.Globalization
open System.Collections.Generic
open Microsoft.FSharp.Reflection
/// A joint, between 2 layouts, is either:
/// - unbreakable, or
/// - breakable, and if broken the second block has a given indentation.
[<StructuralEquality; NoComparison>]
type Joint =
| Unbreakable
| Breakable of int
| Broken of int
/// Leaf juxt,data,juxt
/// Node juxt,left,juxt,right,juxt and joint
///
/// If either juxt flag is true, then no space between words.
[<NoEquality; NoComparison>]
type Layout =
| Leaf of bool * obj * bool
| Node of bool * layout * bool * layout * bool * joint
| Attr of string * (string * string) list * layout
and layout = Layout
and joint = Joint
[<NoEquality; NoComparison>]
type IEnvironment =
abstract GetLayout : obj -> layout
abstract MaxColumns : int
abstract MaxRows : int
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module LayoutOps =
let rec juxtLeft = function
| Leaf (jl,_,_) -> jl
| Node (jl,_,_,_,_,_) -> jl
| Attr (_,_,l) -> juxtLeft l
let rec juxtRight = function
| Leaf (_,_,jr) -> jr
| Node (_,_,_,_,jr,_) -> jr
| Attr (_,_,l) -> juxtRight l
let mkNode l r joint =
let jl = juxtLeft l
let jm = juxtRight l || juxtLeft r
let jr = juxtRight r
Node(jl,l,jm,r,jr,joint)
// constructors
let objL (obj:obj) = Leaf (false,obj,false)
let sLeaf (l,(str:string),r) = Leaf (l,(str:>obj),r)
let wordL str = sLeaf (false,str,false)
let sepL str = sLeaf (true ,str,true)
let rightL str = sLeaf (true ,str,false)
let leftL str = sLeaf (false,str,true)
let emptyL = sLeaf (true,"",true)
let isEmptyL = function
| Leaf(true,s,true) ->
match s with
| :? string as s -> s = ""
| _ -> false
| _ -> false
let aboveL l r = mkNode l r (Broken 0)
let joinN i l r = mkNode l r (Breakable i)
let join = joinN 0
let join1 = joinN 1
let join2 = joinN 2
let join3 = joinN 3
let tagAttrL tag attrs l = Attr(tag,attrs,l)
let apply2 f l r = if isEmptyL l then r else
if isEmptyL r then l else f l r
let (^^) l r = mkNode l r (Unbreakable)
let (++) l r = mkNode l r (Breakable 0)
let (--) l r = mkNode l r (Breakable 1)
let (---) l r = mkNode l r (Breakable 2)
let (@@) l r = apply2 (fun l r -> mkNode l r (Broken 0)) l r
let (@@-) l r = apply2 (fun l r -> mkNode l r (Broken 1)) l r
let (@@--) l r = apply2 (fun l r -> mkNode l r (Broken 2)) l r
let tagListL tagger = function
| [] -> emptyL
| [x] -> x
| x::xs ->
let rec process' prefixL = function
[] -> prefixL
| y::ys -> process' ((tagger prefixL) ++ y) ys
in process' x xs
let commaListL x = tagListL (fun prefixL -> prefixL ^^ rightL ",") x
let semiListL x = tagListL (fun prefixL -> prefixL ^^ rightL ";") x
let spaceListL x = tagListL (fun prefixL -> prefixL) x
let sepListL x y = tagListL (fun prefixL -> prefixL ^^ x) y
let bracketL l = leftL "(" ^^ l ^^ rightL ")"
let tupleL xs = bracketL (sepListL (sepL ",") xs)
let aboveListL = function
| [] -> emptyL
| [x] -> x
| x::ys -> List.fold (fun pre y -> pre @@ y) x ys
let optionL xL = function
None -> wordL "None"
| Some x -> wordL "Some" -- (xL x)
let listL xL xs = leftL "[" ^^ sepListL (sepL ";") (List.map xL xs) ^^ rightL "]"
let squareBracketL x = leftL "[" ^^ x ^^ rightL "]"
let braceL x = leftL "{" ^^ x ^^ rightL "}"
let boundedUnfoldL
(itemL : 'a -> layout)
(project : 'z -> ('a * 'z) option)
(stopShort : 'z -> bool)
(z : 'z)
maxLength =
let rec consume n z =
if stopShort z then [wordL "..."] else
match project z with
| None -> [] (* exhaused input *)
| Some (x,z) -> if n<=0 then [wordL "..."] (* hit print_length limit *)
else itemL x :: consume (n-1) z (* cons recursive... *)
consume maxLength z
let unfoldL itemL project z maxLength = boundedUnfoldL itemL project (fun _ -> false) z maxLength
/// These are a typical set of options used to control structured formatting.
[<NoEquality; NoComparison>]
type FormatOptions =
{ FloatingPointFormat: string;
AttributeProcessor: (string -> (string * string) list -> bool -> unit);
FormatProvider: System.IFormatProvider;
BindingFlags: System.Reflection.BindingFlags
PrintWidth : int;
PrintDepth : int;
PrintLength : int;
PrintSize : int;
ShowProperties : bool;
ShowIEnumerable: bool; }
static member Default =
{ FormatProvider = (System.Globalization.CultureInfo.InvariantCulture :> System.IFormatProvider);
AttributeProcessor= (fun _ _ _ -> ());
BindingFlags = System.Reflection.BindingFlags.Public;
FloatingPointFormat = "g10";
PrintWidth = 80 ;
PrintDepth = 100 ;
PrintLength = 100;
PrintSize = 10000;
ShowProperties = false;
ShowIEnumerable = true; }
module ReflectUtils =
open System
open System.Reflection
[<NoEquality; NoComparison>]
type TypeInfo =
| TupleType of Type list
| FunctionType of Type * Type
| RecordType of (string * Type) list
| SumType of (string * (string * Type) list) list
| UnitType
| ObjectType of Type
let isNamedType(typ:Type) = not (typ.IsArray || typ.IsByRef || typ.IsPointer)
let equivHeadTypes (ty1:Type) (ty2:Type) =
isNamedType(ty1) &&
if ty1.IsGenericType then
ty2.IsGenericType && (ty1.GetGenericTypeDefinition()).Equals(ty2.GetGenericTypeDefinition())
else
ty1.Equals(ty2)
let option = typedefof<obj option>
let func = typedefof<(obj -> obj)>
let isOptionType typ = equivHeadTypes typ (typeof<int option>)
let isUnitType typ = equivHeadTypes typ (typeof<unit>)
let isListType typ =
FSharpType.IsUnion typ &&
(let cases = FSharpType.GetUnionCases typ
cases.Length > 0 && equivHeadTypes (typedefof<list<_>>) cases.[0].DeclaringType)
module Type =
let recdDescOfProps props =
props |> Array.toList |> List.map (fun (p:PropertyInfo) -> p.Name, p.PropertyType)
let getTypeInfoOfType (bindingFlags:BindingFlags) (typ:Type) =
if FSharpType.IsTuple(typ) then TypeInfo.TupleType (FSharpType.GetTupleElements(typ) |> Array.toList)
elif FSharpType.IsFunction(typ) then let ty1,ty2 = FSharpType.GetFunctionElements typ in TypeInfo.FunctionType( ty1,ty2)
elif FSharpType.IsUnion(typ,bindingFlags) then
let cases = FSharpType.GetUnionCases(typ,bindingFlags)
match cases with
| [| |] -> TypeInfo.ObjectType(typ)
| _ ->
TypeInfo.SumType(cases |> Array.toList |> List.map (fun case ->
let flds = case.GetFields()
case.Name,recdDescOfProps(flds)))
elif FSharpType.IsRecord(typ,bindingFlags) then
let flds = FSharpType.GetRecordFields(typ,bindingFlags)
TypeInfo.RecordType(recdDescOfProps(flds))
else
TypeInfo.ObjectType(typ)
let IsOptionType (typ:Type) = isOptionType typ
let IsListType (typ:Type) = isListType typ
let IsUnitType (typ:Type) = isUnitType typ
[<NoEquality; NoComparison>]
type ValueInfo =
| TupleValue of obj list
| FunctionClosureValue of System.Type
| RecordValue of (string * obj) list
| ConstructorValue of string * (string * obj) list
| ExceptionValue of System.Type * (string * obj) list
| UnitValue
| ObjectValue of obj
module Value =
// Analyze an object to see if it the representation
// of an F# value.
let GetValueInfoOfObject (bindingFlags:BindingFlags) (obj : obj) =
match obj with
| null -> ObjectValue(obj)
| _ ->
let reprty = obj.GetType()
// First a bunch of special rules for tuples
// Because of the way F# currently compiles tuple values
// of size > 7 we can only reliably reflect on sizes up
// to 7.
if FSharpType.IsTuple reprty then
TupleValue (FSharpValue.GetTupleFields obj |> Array.toList)
elif FSharpType.IsFunction reprty then
FunctionClosureValue reprty
// It must be exception, abstract, record or union.
// Either way we assume the only properties defined on
// the type are the actual fields of the type. Again,
// we should be reading attributes here that indicate the
// true structure of the type, e.g. the order of the fields.
elif FSharpType.IsUnion(reprty,bindingFlags) then
let tag,vals = FSharpValue.GetUnionFields (obj,reprty,bindingFlags)
let props = tag.GetFields()
let pvals = (props,vals) ||> Array.map2 (fun prop v -> prop.Name,v)
ConstructorValue(tag.Name, Array.toList pvals)
elif FSharpType.IsExceptionRepresentation(reprty,bindingFlags) then
let props = FSharpType.GetExceptionFields(reprty,bindingFlags)
let vals = FSharpValue.GetExceptionFields(obj,bindingFlags)
let pvals = (props,vals) ||> Array.map2 (fun prop v -> prop.Name,v)
ExceptionValue(reprty, pvals |> Array.toList)
elif FSharpType.IsRecord(reprty,bindingFlags) then
let props = FSharpType.GetRecordFields(reprty,bindingFlags)
RecordValue(props |> Array.map (fun prop -> prop.Name, prop.GetValue(obj,null)) |> Array.toList)
else
ObjectValue(obj)
// This one is like the above but can make use of additional
// statically-known type information to aid in the
// analysis of null values.
let GetValueInfo bindingFlags (x : 'a) (* x could be null *) =
let obj = (box x)
match obj with
| null ->
let typ = typeof<'a>
if isOptionType typ then ConstructorValue("None", [])
elif isUnitType typ then UnitValue
else ObjectValue(obj)
| _ ->
GetValueInfoOfObject bindingFlags (obj)
let GetInfo bindingFlags (v:'a) = GetValueInfo bindingFlags (v:'a)
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module Display =
open ReflectUtils
open LayoutOps
let string_of_int (i:int) = i.ToString()
let typeUsesSystemObjectToString (typ:System.Type) =
try let methInfo = typ.GetMethod("ToString",BindingFlags.Public ||| BindingFlags.Instance,null,[| |],null)
methInfo.DeclaringType = typeof<System.Object>
with e -> false
/// If "str" ends with "ending" then remove it from "str", otherwise no change.
let trimEnding (ending:string) (str:string) =
#if FX_NO_CULTURE_INFO_ARGS
if str.EndsWith(ending) then
#else
if str.EndsWith(ending,StringComparison.Ordinal) then
#endif
str.Substring(0,str.Length - ending.Length)
else str
let catchExn f = try Choice1Of2 (f ()) with e -> Choice2Of2 e
// An implementation of break stack.
// Uses mutable state, relying on linear threading of the state.
[<NoEquality; NoComparison>]
type Breaks =
Breaks of
int * // pos of next free slot
int * // pos of next possible "outer" break - OR - outer=next if none possible
int array // stack of savings, -ve means it has been broken
// next is next slot to push into - aka size of current occupied stack.
// outer counts up from 0, and is next slot to break if break forced.
// - if all breaks forced, then outer=next.
// - popping under these conditions needs to reduce outer and next.
//let dumpBreaks prefix (Breaks(next,outer,stack)) = ()
// printf "%s: next=%d outer=%d stack.Length=%d\n" prefix next outer stack.Length;
// stdout.Flush()
let chunkN = 400
let breaks0 () = Breaks(0,0,Array.create chunkN 0)
let pushBreak saving (Breaks(next,outer,stack)) =
//dumpBreaks "pushBreak" (next,outer,stack);
let stack =
if next = stack.Length then
Array.init (next + chunkN) (fun i -> if i < next then stack.[i] else 0) // expand if full
else
stack
stack.[next] <- saving;
Breaks(next+1,outer,stack)
let popBreak (Breaks(next,outer,stack)) =
//dumpBreaks "popBreak" (next,outer,stack);
if next=0 then raise (Failure "popBreak: underflow");
let topBroke = stack.[next-1] < 0
let outer = if outer=next then outer-1 else outer // if all broken, unwind
let next = next - 1
Breaks(next,outer,stack),topBroke
let forceBreak (Breaks(next,outer,stack)) =
//dumpBreaks "forceBreak" (next,outer,stack);
if outer=next then
// all broken
None
else
let saving = stack.[outer]
stack.[outer] <- -stack.[outer];
let outer = outer+1
Some (Breaks(next,outer,stack),saving)
// -------------------------------------------------------------------------
// fitting
// ------------------------------------------------------------------------
let squashTo (maxWidth,leafFormatter) layout =
if maxWidth <= 0 then layout else
let rec fit breaks (pos,layout) =
// breaks = break context, can force to get indentation savings.
// pos = current position in line
// layout = to fit
//------
// returns:
// breaks
// layout - with breaks put in to fit it.
// pos - current pos in line = rightmost position of last line of block.
// offset - width of last line of block
// NOTE: offset <= pos -- depending on tabbing of last block
let breaks,layout,pos,offset =
match layout with
| Attr (tag,attrs,l) ->
let breaks,layout,pos,offset = fit breaks (pos,l)
let layout = Attr (tag,attrs,layout)
breaks,layout,pos,offset
| Leaf (jl,obj,jr) ->
let text:string = leafFormatter obj
// save the formatted text from the squash
let layout = Leaf(jl,(text :> obj),jr)
let textWidth = text.Length
let rec fitLeaf breaks pos =
if pos + textWidth <= maxWidth then
breaks,layout,pos + textWidth,textWidth // great, it fits
else
match forceBreak breaks with
| None ->
breaks,layout,pos + textWidth,textWidth // tough, no more breaks
| Some (breaks,saving) ->
let pos = pos - saving
fitLeaf breaks pos
fitLeaf breaks pos
| Node (jl,l,jm,r,jr,joint) ->
let mid = if jm then 0 else 1
match joint with
| Unbreakable ->
let breaks,l,pos,offsetl = fit breaks (pos,l) // fit left
let pos = pos + mid // fit space if juxt says so
let breaks,r,pos,offsetr = fit breaks (pos,r) // fit right
breaks,Node (jl,l,jm,r,jr,Unbreakable),pos,offsetl + mid + offsetr
| Broken indent ->
let breaks,l,pos,offsetl = fit breaks (pos,l) // fit left
let pos = pos - offsetl + indent // broken so - offset left + ident
let breaks,r,pos,offsetr = fit breaks (pos,r) // fit right
breaks,Node (jl,l,jm,r,jr,Broken indent),pos,indent + offsetr
| Breakable indent ->
let breaks,l,pos,offsetl = fit breaks (pos,l) // fit left
// have a break possibility, with saving
let saving = offsetl + mid - indent
let pos = pos + mid
if saving>0 then
let breaks = pushBreak saving breaks
let breaks,r,pos,offsetr = fit breaks (pos,r)
let breaks,broken = popBreak breaks
if broken then
breaks,Node (jl,l,jm,r,jr,Broken indent) ,pos,indent + offsetr
else
breaks,Node (jl,l,jm,r,jr,Breakable indent),pos,offsetl + mid + offsetr
else
// actually no saving so no break
let breaks,r,pos,offsetr = fit breaks (pos,r)
breaks,Node (jl,l,jm,r,jr,Breakable indent) ,pos,offsetl + mid + offsetr
//Printf.printf "\nDone: pos=%d offset=%d" pos offset;
breaks,layout,pos,offset
let breaks = breaks0 ()
let pos = 0
let _,layout,_,_ = fit breaks (pos,layout)
layout
// -------------------------------------------------------------------------
// showL
// ------------------------------------------------------------------------
let combine strs = System.String.Concat(Array.ofList(strs) : string[])
let showL opts leafFormatter layout =
let push x rstrs = x::rstrs
let z0 = [],0
let addText (rstrs,i) (text:string) = push text rstrs,i + text.Length
let index (_,i) = i
let extract rstrs = combine(List.rev rstrs)
let newLine (rstrs,_) n = // \n then spaces...
let indent = new System.String(' ', n)
let rstrs = push System.Environment.NewLine rstrs
let rstrs = push indent rstrs
rstrs,n
// addL: pos is tab level
let rec addL z pos layout =
match layout with
| Leaf (_,obj,_) ->
let text = leafFormatter obj
addText z text
| Node (_,l,_,r,_,Broken indent)
// Print width = 0 implies 1D layout, no squash
when not (opts.PrintWidth = 0) ->
let z = addL z pos l
let z = newLine z (pos+indent)
let z = addL z (pos+indent) r
z
| Node (_,l,jm,r,_,_) ->
let z = addL z pos l
let z = if jm then z else addText z " "
let pos = index z
let z = addL z pos r
z
| Attr (_,_,l) ->
addL z pos l
let rstrs,_ = addL z0 0 layout
extract rstrs
// -------------------------------------------------------------------------
// outL
// ------------------------------------------------------------------------
let outL outAttribute leafFormatter (chan : TextWriter) layout =
// write layout to output chan directly
let write (s:string) = chan.Write(s)
// z is just current indent
let z0 = 0
let index i = i
let addText z text = write text; (z + text.Length)
let newLine _ n = // \n then spaces...
let indent = new System.String(' ',n)
chan.WriteLine();
write indent;
n
// addL: pos is tab level
let rec addL z pos layout =
match layout with
| Leaf (_,obj,_) ->
let text = leafFormatter obj
addText z text
| Node (_,l,_,r,_,Broken indent) ->
let z = addL z pos l
let z = newLine z (pos+indent)
let z = addL z (pos+indent) r
z
| Node (_,l,jm,r,_,_) ->
let z = addL z pos l
let z = if jm then z else addText z " "
let pos = index z
let z = addL z pos r
z
| Attr (tag,attrs,l) ->
let _ = outAttribute tag attrs true
let z = addL z pos l
let _ = outAttribute tag attrs false
z
let _ = addL z0 0 layout
()
// --------------------------------------------------------------------
// pprinter: using general-purpose reflection...
// --------------------------------------------------------------------
let getValueInfo bindingFlags (x:'a) = Value.GetInfo bindingFlags (x:'a)
let unpackCons recd =
match recd with
| [(_,h);(_,t)] -> (h,t)
| _ -> failwith "unpackCons"
let getListValueInfo bindingFlags (x:obj) =
match x with
| null -> None
| _ ->
match getValueInfo bindingFlags x with
| ConstructorValue ("Cons",recd) -> Some (unpackCons recd)
| ConstructorValue ("Empty",[]) -> None
| _ -> failwith "List value had unexpected ValueInfo"
let compactCommaListL xs = sepListL (sepL ",") xs // compact, no spaces around ","
let nullL = wordL "null"
let measureL = wordL "()"
// --------------------------------------------------------------------
// pprinter: attributes
// --------------------------------------------------------------------
let makeRecordVerticalL nameXs =
let itemL (name,xL) = let labelL = wordL name in ((labelL ^^ wordL "=")) -- (xL ^^ (rightL ";"))
let braceL xs = (leftL "{") ^^ xs ^^ (rightL "}")
braceL (aboveListL (List.map itemL nameXs))
let makeRecordHorizontalL nameXs = (* This is a more compact rendering of records - and is more like tuples *)
let itemL (name,xL) = let labelL = wordL name in ((labelL ^^ wordL "=")) -- xL
let braceL xs = (leftL "{") ^^ xs ^^ (rightL "}")
braceL (sepListL (rightL ";") (List.map itemL nameXs))
let makeRecordL nameXs = makeRecordVerticalL nameXs (* REVIEW: switch to makeRecordHorizontalL ? *)
let makePropertiesL nameXs =
let itemL (name,v) =
let labelL = wordL name
(labelL ^^ wordL "=")
^^ (match v with
| None -> wordL "?"
| Some xL -> xL)
^^ (rightL ";")
let braceL xs = (leftL "{") ^^ xs ^^ (rightL "}")
braceL (aboveListL (List.map itemL nameXs))
let makeListL itemLs =
(leftL "[")
^^ sepListL (rightL ";") itemLs
^^ (rightL "]")
let makeArrayL xs =
(leftL "[|")
^^ sepListL (rightL ";") xs
^^ (rightL "|]")
let makeArray2L xs = leftL "[" ^^ aboveListL xs ^^ rightL "]"
// --------------------------------------------------------------------
// pprinter: anyL - support functions
// --------------------------------------------------------------------
let getProperty (obj: obj) name =
let ty = obj.GetType()
#if FX_NO_CULTURE_INFO_ARGS
ty.InvokeMember(name, (BindingFlags.GetProperty ||| BindingFlags.Instance ||| BindingFlags.Public ||| BindingFlags.NonPublic), null, obj, [| |])
#else
ty.InvokeMember(name, (BindingFlags.GetProperty ||| BindingFlags.Instance ||| BindingFlags.Public ||| BindingFlags.NonPublic), null, obj, [| |],CultureInfo.InvariantCulture)
#endif
let formatChar isChar c =
match c with
| '\'' when isChar -> "\\\'"
| '\"' when not isChar -> "\\\""
//| '\n' -> "\\n"
//| '\r' -> "\\r"
//| '\t' -> "\\t"
| '\\' -> "\\\\"
| '\b' -> "\\b"
| _ when System.Char.IsControl(c) ->
let d1 = (int c / 100) % 10
let d2 = (int c / 10) % 10
let d3 = int c % 10
"\\" + d1.ToString() + d2.ToString() + d3.ToString()
| _ -> c.ToString()
let formatString (s:string) =
let rec check i = i < s.Length && not (System.Char.IsControl(s,i)) && s.[i] <> '\"' && check (i+1)
let rec conv i acc = if i = s.Length then combine (List.rev acc) else conv (i+1) (formatChar false s.[i] :: acc)
"\"" + s + "\""
// REVIEW: should we check for the common case of no control characters? Reinstate the following?
//"\"" + (if check 0 then s else conv 0 []) + "\""
let formatStringInWidth (width:int) (str:string) =
// Return a truncated version of the string, e.g.
// "This is the initial text, which has been truncat"+[12 chars]
//
// Note: The layout code forces breaks based on leaf size and possible break points.
// It does not force leaf size based on width.
// So long leaf-string width can not depend on their printing context...
//
// The suffix like "+[dd chars]" is 11 chars.
// 12345678901
let suffixLength = 11 // turning point suffix length
let prefixMinLength = 12 // arbitrary. If print width is reduced, want to print a minimum of information on strings...
let prefixLength = max (width - 2 (*quotes*) - suffixLength) prefixMinLength
"\"" + (str.Substring(0,prefixLength)) + "\"" + "+[" + (str.Length - prefixLength).ToString() + " chars]"
// --------------------------------------------------------------------
// pprinter: anyL
// --------------------------------------------------------------------
type Precedence =
| BracketIfTupleOrNotAtomic = 2
| BracketIfTuple = 3
| NeverBracket = 4
// In fsi.exe, certain objects are not printed for top-level bindings.
[<StructuralEquality; NoComparison>]
type ShowMode =
| ShowAll
| ShowTopLevelBinding
// polymorphic and inner recursion limitations prevent us defining polyL in the recursive loop
let polyL bindingFlags (objL: ShowMode -> int -> Precedence -> ValueInfo -> obj -> Layout) showMode i prec (x:'a) (* x could be null *) =
objL showMode i prec (getValueInfo bindingFlags (x:'a)) (box x)
let anyL showMode bindingFlags (opts:FormatOptions) (x:'a) =
// showMode = ShowTopLevelBinding on the outermost expression when called from fsi.exe,
// This allows certain outputs, e.g. objects that would print as <seq> to be suppressed, etc. See 4343.
// Calls to layout proper sub-objects should pass showMode = ShowAll.
// Precedences to ensure we add brackets in the right places
// Keep a record of objects encountered along the way
let path = Dictionary<obj,int>(10,HashIdentity.Reference)
// Roughly count the "nodes" printed, e.g. leaf items and inner nodes, but not every bracket and comma.
let size = ref opts.PrintSize
let exceededPrintSize() = !size<=0
let countNodes n = if !size > 0 then size := !size - n else () (* no need to keep decrementing (and avoid wrap around) *)
let stopShort _ = exceededPrintSize() // for unfoldL
// Recursive descent
let rec objL depthLim prec (x:obj) = polyL bindingFlags objWithReprL ShowAll depthLim prec x (* showMode for inner expr *)
and sameObjL depthLim prec (x:obj) = polyL bindingFlags objWithReprL showMode depthLim prec x (* showMode preserved *)
and objWithReprL showMode depthLim prec (info:ValueInfo) (x:obj) (* x could be null *) =
try
if depthLim<=0 || exceededPrintSize() then wordL "..." else
match x with
| null ->
reprL showMode (depthLim-1) prec info x
| _ ->
if (path.ContainsKey(x)) then
wordL "..."
else
path.Add(x,0);
let res =
// Lazy<T> values. VS2008 used StructuredFormatDisplayAttribute to show via ToString. Dev10 (no attr) needs a special case.
let ty = x.GetType()
if ty.IsGenericType && ty.GetGenericTypeDefinition() = typedefof<Lazy<_>> then
Some (wordL (x.ToString()))
else
// Try the StructuredFormatDisplayAttribute extensibility attribute
match x.GetType().GetCustomAttributes (typeof<StructuredFormatDisplayAttribute>, true) with
| null | [| |] -> None
| res ->
let attr = (res.[0] :?> StructuredFormatDisplayAttribute)
let txt = attr.Value
if txt = null || txt.Length <= 1 then
None
else
let p1 = txt.IndexOf ("{", StringComparison.Ordinal)
let p2 = txt.LastIndexOf ("}", StringComparison.Ordinal)
if p1 < 0 || p2 < 0 || p1+1 >= p2 then
None
else
let preText = if p1 <= 0 then "" else txt.[0..p1-1]
let postText = if p2+1 >= txt.Length then "" else txt.[p2+1..]
let prop = txt.[p1+1..p2-1]
match catchExn (fun () -> getProperty x prop) with
| Choice2Of2 e -> Some (wordL ("<StructuredFormatDisplay exception: " + e.Message + ">"))
| Choice1Of2 alternativeObj ->
try
let alternativeObjL =
match alternativeObj with
// A particular rule is that if the alternative property
// returns a string, we turn off auto-quoting and esaping of
// the string, i.e. just treat the string as display text.
// This allows simple implementations of
// such as
//
// [<StructuredFormatDisplay("{StructuredDisplayString}I")>]
// type BigInt(signInt:int, v : BigNat) =
// member x.StructuredDisplayString = x.ToString()
//
| :? string as s -> sepL s
| _ -> sameObjL (depthLim-1) Precedence.BracketIfTuple alternativeObj
countNodes 0 (* 0 means we do not count the preText and postText *)
Some (leftL preText ^^ alternativeObjL ^^ rightL postText)
with _ ->
None
let res =
match res with
| Some res -> res
| None -> reprL showMode (depthLim-1) prec info x
path .Remove(x) |> ignore;
res
with
e ->
countNodes 1
wordL ("Error: " + e.Message)
and recdAtomicTupleL depthLim recd =
// tuples up args to UnionConstruction or ExceptionConstructor. no node count.
match recd with
| [(_,x)] -> objL depthLim Precedence.BracketIfTupleOrNotAtomic x
| txs -> leftL "(" ^^ compactCommaListL (List.map (snd >> objL depthLim Precedence.BracketIfTuple) txs) ^^ rightL ")"
and bracketIfL b basicL =
if b then (leftL "(") ^^ basicL ^^ (rightL ")") else basicL
and reprL showMode depthLim prec repr x (* x could be null *) =
let showModeFilter lay = match showMode with ShowAll -> lay | ShowTopLevelBinding -> emptyL
match repr with
| TupleValue vals ->
let basicL = sepListL (rightL ",") (List.map (objL depthLim Precedence.BracketIfTuple ) vals)
bracketIfL (prec <= Precedence.BracketIfTuple) basicL
| RecordValue items ->
let itemL (name,x) =
countNodes 1 // record labels are counted as nodes. [REVIEW: discussion under 4090].
(name,objL depthLim Precedence.BracketIfTuple x)
makeRecordL (List.map itemL items)
| ConstructorValue (constr,recd) when (* x is List<T>. Note: "null" is never a valid list value. *)
x<>null && Type.IsListType (x.GetType()) ->
match constr with
| "Cons" ->
let (x,xs) = unpackCons recd
let project xs = getListValueInfo bindingFlags xs
let itemLs = objL depthLim Precedence.BracketIfTuple x :: boundedUnfoldL (objL depthLim Precedence.BracketIfTuple) project stopShort xs (opts.PrintLength - 1)
makeListL itemLs
| _ ->
countNodes 1
wordL "[]"
| ConstructorValue(nm,[]) ->
countNodes 1
(wordL nm)
| ConstructorValue(nm,recd) ->
countNodes 1 (* e.g. Some (Some (Some (Some 2))) should count for 5 *)
(wordL nm --- recdAtomicTupleL depthLim recd) |> bracketIfL (prec <= Precedence.BracketIfTupleOrNotAtomic)
| ExceptionValue(ty,recd) ->
countNodes 1
let name = ty.Name
match recd with
| [] -> (wordL name)
| recd -> (wordL name --- recdAtomicTupleL depthLim recd) |> bracketIfL (prec <= Precedence.BracketIfTupleOrNotAtomic)
| FunctionClosureValue ty ->
// Q: should function printing include the ty.Name? It does not convey much useful info to most users, e.g. "clo@0_123".
countNodes 1
wordL ("<fun:"+ty.Name+">") |> showModeFilter
| ObjectValue(obj) ->
match obj with
| null -> (countNodes 1; nullL)
| _ ->
let ty = obj.GetType()
match obj with
| :? string as s ->
countNodes 1
wordL (formatString s)
| :? System.Array as arr ->
match arr.Rank with
| 1 ->
let n = arr.Length
let b1 = arr.GetLowerBound(0)
let project depthLim = if depthLim=(b1+n) then None else Some (box (arr.GetValue(depthLim)),depthLim+1)
let itemLs = boundedUnfoldL (objL depthLim Precedence.BracketIfTuple) project stopShort b1 opts.PrintLength
makeArrayL (if b1 = 0 then itemLs else wordL("bound1="+string_of_int b1)::itemLs)
| 2 ->
let n1 = arr.GetLength(0)
let n2 = arr.GetLength(1)
let b1 = arr.GetLowerBound(0)
let b2 = arr.GetLowerBound(1)
let project2 x y =
if x>=(b1+n1) || y>=(b2+n2) then None
else Some (box (arr.GetValue(x,y)),y+1)
let rowL x = boundedUnfoldL (objL depthLim Precedence.BracketIfTuple) (project2 x) stopShort b2 opts.PrintLength |> makeListL
let project1 x = if x>=(b1+n1) then None else Some (x,x+1)
let rowsL = boundedUnfoldL rowL project1 stopShort b1 opts.PrintLength
makeArray2L (if b1=0 && b2 = 0 then rowsL else wordL("bound1=" + string_of_int b1)::wordL("bound2=" + string_of_int b2)::rowsL)
| n ->
makeArrayL [wordL("rank=" + string_of_int n)]
// Format 'set' and 'map' nicely
| _ when
(let ty = obj.GetType()
ty.IsGenericType && (ty.GetGenericTypeDefinition() = typedefof<Map<int,int>>
|| ty.GetGenericTypeDefinition() = typedefof<Set<int>>) ) ->
let ty = obj.GetType()
let word = if ty.GetGenericTypeDefinition() = typedefof<Map<int,int>> then "map" else "set"
let possibleKeyValueL v =
if word = "map" &&
(match v with null -> false | _ -> true) &&
v.GetType().IsGenericType &&
v.GetType().GetGenericTypeDefinition() = typedefof<KeyValuePair<int,int>> then
objL depthLim Precedence.BracketIfTuple (v.GetType().GetProperty("Key").GetValue(v, [| |]),
v.GetType().GetProperty("Value").GetValue(v, [| |]))
else
objL depthLim Precedence.BracketIfTuple v
let it = (obj :?> System.Collections.IEnumerable).GetEnumerator()
try
let itemLs = boundedUnfoldL possibleKeyValueL (fun () -> if it.MoveNext() then Some(it.Current,()) else None) stopShort () (1+opts.PrintLength/12)
(wordL word --- makeListL itemLs) |> bracketIfL (prec <= Precedence.BracketIfTupleOrNotAtomic)
finally
match it with
| :? System.IDisposable as e -> e.Dispose()
| _ -> ()
| :? System.Collections.IEnumerable as ie ->
if opts.ShowIEnumerable then
let word = "seq"
let it = ie.GetEnumerator()
try
let itemLs = boundedUnfoldL (objL depthLim Precedence.BracketIfTuple) (fun () -> if it.MoveNext() then Some(it.Current,()) else None) stopShort () (1+opts.PrintLength/30)
(wordL word --- makeListL itemLs) |> bracketIfL (prec <= Precedence.BracketIfTupleOrNotAtomic)
finally
match it with
| :? System.IDisposable as e -> e.Dispose()
| _ -> ()
else
// Sequence printing is turned off for declared-values, and maybe be disabled to users.
// There is choice here, what to print? <seq> or ... or ?
// Also, in the declared values case, if the sequence is actually a known non-lazy type (list, array etc etc) we could print it.
wordL "<seq>" |> showModeFilter
| _ ->
if showMode = ShowTopLevelBinding && typeUsesSystemObjectToString (obj.GetType()) then
emptyL
else
countNodes 1
let basicL = LayoutOps.objL obj // This buries an obj in the layout, rendered at squash time via a leafFormatter.
// If the leafFormatter was directly here, then layout leaves could store strings.
match obj with
| _ when opts.ShowProperties ->
let props = ty.GetProperties(BindingFlags.GetField ||| BindingFlags.Instance ||| BindingFlags.Public)
// massively reign in deep printing of properties
let nDepth = depthLim/10
System.Array.Sort((props:>System.Array),{ new System.Collections.IComparer with member this.Compare(p1,p2) = compare ((p1 :?> PropertyInfo).Name) ((p2 :?> PropertyInfo).Name) } );
if props.Length = 0 || (nDepth <= 0) then basicL
else basicL ---
(props
|> Array.toList
|> List.map (fun p -> (p.Name,(try Some (objL nDepth Precedence.BracketIfTuple (getProperty obj p.Name))
with _ -> None)))
|> makePropertiesL)
| _ -> basicL
| UnitValue -> countNodes 1; measureL
polyL bindingFlags objWithReprL showMode opts.PrintDepth Precedence.BracketIfTuple x
// --------------------------------------------------------------------
// pprinter: leafFormatter
// --------------------------------------------------------------------
#if Suggestion4299
// See bug 4299. Suppress FSI_dddd+<etc> from fsi printer.
let fixupForInteractiveFSharpClassesWithNoToString obj (text:string) =
// Given obj:T.
// If T is a nested type inside a parent type called FSI_dddd, then it looks like an F# Interactive type.
// Further, if the .ToString() text starts with "FSI_dddd+T" then it looks like it's the default ToString.
// A better test: it is default ToString if the MethodInfo.DeclaringType is System.Object.
// In this case, replace "FSI_dddd+T" by "T".
// assert(obj <> null)
let fullName = obj.GetType().FullName // e.g. "FSI_0123+Name"
let name = obj.GetType().Name // e.g. "Name"
let T = obj.GetType()
if text.StartsWith(fullName) then
// text could be a default .ToString() since it starts with the FullName of the type. More checks...
if T.IsNested &&
T.DeclaringType.Name.StartsWith("FSI_") && // Name has "FSI_" which is
T.DeclaringType.Name.Substring(4) |> Seq.forall System.Char.IsDigit // followed by digits?
then
name ^ text.Substring(fullName.Length) // replace fullName by name at start of text
else
text
else
text
#endif
let leafFormatter (opts:FormatOptions) (obj :obj) =
match obj with
| null -> "null"
| :? double as d ->
let s = d.ToString(opts.FloatingPointFormat,opts.FormatProvider)
if System.Double.IsNaN(d) then "nan"
elif System.Double.IsNegativeInfinity(d) then "-infinity"
elif System.Double.IsPositiveInfinity(d) then "infinity"
elif opts.FloatingPointFormat.[0] = 'g' && String.forall(fun c -> System.Char.IsDigit(c) || c = '-') s
then s + ".0"
else s
| :? single as d ->
(if System.Single.IsNaN(d) then "nan"
elif System.Single.IsNegativeInfinity(d) then "-infinity"
elif System.Single.IsPositiveInfinity(d) then "infinity"
elif opts.FloatingPointFormat.Length >= 1 && opts.FloatingPointFormat.[0] = 'g'
&& float32(System.Int32.MinValue) < d && d < float32(System.Int32.MaxValue)
&& float32(int32(d)) = d
then (System.Convert.ToInt32 d).ToString(opts.FormatProvider) + ".0"
else d.ToString(opts.FloatingPointFormat,opts.FormatProvider))
+ "f"
| :? System.Decimal as d -> d.ToString("g",opts.FormatProvider) + "M"
| :? uint64 as d -> d.ToString(opts.FormatProvider) + "UL"
| :? int64 as d -> d.ToString(opts.FormatProvider) + "L"
| :? int32 as d -> d.ToString(opts.FormatProvider)
| :? uint32 as d -> d.ToString(opts.FormatProvider) + "u"
| :? int16 as d -> d.ToString(opts.FormatProvider) + "s"
| :? uint16 as d -> d.ToString(opts.FormatProvider) + "us"
| :? sbyte as d -> d.ToString(opts.FormatProvider) + "y"
| :? byte as d -> d.ToString(opts.FormatProvider) + "uy"
| :? nativeint as d -> d.ToString() + "n"
| :? unativeint as d -> d.ToString() + "un"
| :? bool as b -> (if b then "true" else "false")
| :? char as c -> "\'" + formatChar true c + "\'"
| _ -> try let text = obj.ToString()
text
with e ->
// If a .ToString() call throws an exception, catch it and use the message as the result.
// This may be informative, e.g. division by zero etc...
"<ToString exception: " + e.Message + ">"
let any_to_layout opts x = anyL ShowAll BindingFlags.Public opts x
let squash_layout opts l =
// Print width = 0 implies 1D layout, no squash
if opts.PrintWidth = 0 then
l
else
l |> squashTo (opts.PrintWidth,leafFormatter opts)
let output_layout opts oc l =
l |> squash_layout opts
|> outL opts.AttributeProcessor (leafFormatter opts) oc
let layout_to_string opts l =
l |> squash_layout opts
|> showL opts (leafFormatter opts)
let output_any_ex opts oc x = x |> any_to_layout opts |> output_layout opts oc
let output_any oc x = output_any_ex FormatOptions.Default oc x
let layout_as_string opts x = x |> any_to_layout opts |> layout_to_string opts
let any_to_string x = layout_as_string FormatOptions.Default x
|