"A JSON Printer"
by("Stéphane Épardaud")
shared abstract class Printer(Boolean pretty = false){
    
    variable Integer level = 0;
    
    void enter(){
        level++;
    }
    
    void leave(){
        level--;
    }
    
    void indent(){
        if(pretty){
            print("\n");
            if(level > 0){
                for(i in 0..level-1){
                    print(" ");
                }
            }
        }
    }
    
    "Override to implement the printing part"
    shared formal void print(String string);

    "Prints an `Object`"
    shared default void printObject(Object o){
        print("{");
        enter();
        variable Boolean once = true;
        for(entry in o.sort(compareKeys)){
            if(once){
                once = false;
            }else{
                print(",");
            }
            indent();
            printString(entry.key);
            print(":");
            if(pretty){
                print(" ");
            }
            printValue(entry.item);
        }
        leave();
        if(!once){
            indent();
        }
        print("}");
    }

    "Prints an `Array`"
    shared default void printArray(Array o){
        print("[");
        enter();
        variable Boolean once = true; 
        for(elem in o){
            if(once){
                once = false;
            }else{
                print(",");
            }
            indent();
            printValue(elem);
        }
        leave();
        if(!once){
            indent();
        }
        print("]");
    }

    "Prints a `String`"
    shared default void printString(String s){
        print("\"");
        for(c in s){
            if(c == '"'){
                print("\\" + "\"");
            }else if(c == '\\'){
                print("\\\\");
            }else if(c == '/'){
                print("\\" + "/");
            }else if(c == 8.character){
                print("\\" + "b");
            }else if(c == 12.character){
                print("\\" + "f");
            }else if(c == 10.character){
                print("\\" + "n");
            }else if(c == 13.character){
                print("\\" + "r");
            }else if(c == 9.character){
                print("\\" + "t");
            }else{
                print(c.string);
            }
        }
        print("\"");
    }
    
    "Prints an `Integer|Float`"
    shared default void printNumber(Integer|Float n) {
        print(formatNumber(n));
    }

    "Prints a `Boolean`"
    shared default void printBoolean(Boolean v)
            => print(v.string);

    "Prints `null`"
    shared default void printNull()
            => print("null");
    
    "Prints a JSON value"
    shared default void printValue(Value val){
        switch(val)
        case (is String){
            printString(val);
        }
        case (is Integer){
            printNumber(val);
        }
        case (is Float){
            printNumber(val);
        }
        case (is Boolean){
            printBoolean(val);
        }
        case (is Object){
            printObject(val);
        }
        case (is Array){
            printArray(val);
        }
        case (is Null){
            printNull();
        }
    }
    
}

"Formats a `Number`, handling infinity and undefined Floats."
String formatNumber(Integer|Float n) {
    String s;
    if (is Float n) {
        if (n.infinite || n.undefined) {
            s = "null";
        } else {
            s = n.string;
        }
    } else {
        s = n.string;
    }
    return s;
}

Comparison compareKeys(String->Value x, String->Value y)
        => x.key<=>y.key;