Tclon

Difference between version 5 and 6 - Previous - Next
[Sarnold]: An attempt to produce a Tcl-equivalent to [JSON], a [data format] easily parsable by Web applications.
Here we have a simple code to handle this format at Tcl-level, with a little sugar to handle types, that is required
by the [Javascript] version.
[Lars H], 2008-02-06: Do you have an example of what the data format looks like? 
Also, what is the point of doing a more Tclish format in this case? I'd expect it to be easier to teach Tcl to parse a data structure with Javascript syntax than vice versa, but on the other hand it is probably good for the mountain to come to Muhammed every once in a while too.

[Sarnold]: The data format maps Tcl's [list]. The structure of the data is separate from the data itself, due to [EIAS].
The data structure is also a [list].======
 Data structure syntax for Tclon:
======none
Data structure syntax for Tclon:

list : type ?type ...?
    | list '*' # the star matches one or more values
    | '*' # same remark
    | 'list' type # this is a list where all elements have the same type (the argument) 
 type : name | list | dict
 dict : 'dict'  # a non-formal dict (keys and values are just strings)
             | 'dict' type type; # a formal dict : second and third types are the types of keys and values, resp.
======
The data structure is a 'list' as the syntax shows.======
 Data format examples:
======none
Data format examples:

Structure:                  Data sample:
 ==========                  ============
 *                           {a b {c d e}}
 {dict}                      {{opt value opt2 value2}}
 {{dict key {a b c}}}        {{colors {red green blue} values {100 200 300}}}
======
----
'''Javascript code:'''
======none
var tclon = new Object();
tclon.tclon=function (input, types) {	    return tclon._tclon(tclon.parse(input), tclon.parse(types));
}

tclon._tclon = function (input, types) {	    if (types.length == 0 || (types.length==1 && types[0]=="*"))
		        return input;
	    var res=new Array(), i, tab, elt;
	    for (i=0; i<input.length && i<types.length; i++) {
		        var t = types[i];
		        var d = input[i];
		        if (typeof t == "string") {
			            if (t == "dict")
				                res.push(tclon.toDict(d));
			            else if (t == 'list')
				                res.push(d);
			            else if (typeof d == "Array")
				                res.push(d.join());
			            else
				                res.push(d);
		        } else if (t[0]=="list") {
			            tab = new Array();
			            for (j in d) {
				                tab.push(tclon._tclon(d[j], t[1]));
			            }
			            res.push(tab);
		        } else if (t[0]=="dict") {
			            if (t.length != 3) throw "invalid dict type";
			            res.push(tclon.toDictWithType(d,t[1],t[2]));
		        }
	    }
	    return res;
}

tclon.toDict = function (arr) {	    var dict = new Object();
	    if(arr.length%2!=0) {
		        alert(arr);
		        throw "not a dict";
	    }
	    for (var i = 0; i <arr.length;i+=2)
		        dict[arr[i]]=arr[i+1];
	    return dict;
}

tclon.toDictWithType = function (arr, ktype, vtype) {	    var dict = new Object();
	    if(arr.length%2!=0) {
		        alert(arr);
		        throw "not a dict";
	    }
	    for (var i = 0; i <arr.length;i+=2)
		        dict[tclon._tclon(arr[i], ktype)]=tclon._tclon(arr[i+1], vtype);
	    return dict;
}

tclon.StdEscapeSequences = {	    "{" : "{", "}": "}", "\"": "\"", "r": "\r", "t": "\t",
	    "a": "\a", "b": "\b", "n": "\n", "v": "\v", "f": "\f"
};

tclon.getSeq = function (str, pos) {	    var my = str.substr(pos+1, 10);
	    if (my.charAt(0)=="x")
		        return String.fromCharCode(parseInt(my.substr(1,2), 16));
	    if (my.charAt(0)=="u")
		        return String.fromCharCode(parseInt(my.substr(1,4), 16));
	    return tclon.StdEscapeSequences[my];
}

tclon.seqLen = function (str, pos) {	    var my = str.substr(pos+1, 10);
	    if (my.charAt(0)=="x")
		        return 3;
	    if (my.charAt(0)=="u")
		        return 4;
	    return 1;
}

tclon.parse = function (str) {	    var tab;
	    var res = new Array();
	    var i = 0, level = 0, work="", c = 0, inString = false;
	    res.push(new Array());
	    for (i = 0; i <str.length; i++) {
		        switch(str.charAt(i)) {
			            case "{":
				                if (inString) {
					                    work+="{";
					                    break;
				                }
				                level++;
				                res.push(new Array());
				                break;
			            case "}":
				                if (inString) {
					                    work+="}";
					                    break;
				                }
				                level--;
				                if (level < 0) throw "unmatched braces";
				                tab = res[res.length-1];
				                if (str.charAt(i-1)!="}") tab.push(work);
				                res.pop();
				                res[res.length-1].push(tab);
				                work = "";
				                break;
			            case "\\":
				                // Escape sequence
				                work+=tclon.getSeq(str, i+1);
				                i+=tclon.escapeSeq(str, i+1);
				                break;
			            case "\"":
				                inString = !instring;
				                break;
			            default:
				                if (str.charAt(i).match("[ \t\r\n\v\f]")) {
					                    if (str.charAt(i-1)!="}") res[res.length-1].push(work);
					                    work = "";
				                }	        else
					                    work+=str.charAt(i);
				                break;
		        }
	    }
	    if (str.charAt(str.length-1)!="}") res[0].push(work);
	    if (level != 0)
		        throw "unmatched braces";
	    if (inString)
		        throw "missing double-quotes";
	    return res[0];
}

alert(tclon.tclon("a {{b c} {d e}} f", "s {list {a b}} s"));
======
----
'''Tcl code:'''
======
proc tclon {data {type *}} {	    if {![llength $type] || $type eq "*"} {return $data}
	    set res ""
	    foreach t $type d $data {
		        if {[llength $t]<=1} {
			            lappend res $d
		        } elseif {[lindex $t 0] eq "dict"} {
			            set dict [list]
			            foreach {k v} $d {
				                lappend dict [tclon $k [lindex $t 1]] [tclon $v [lindex $t 2]]
			            }
			            lappend res $dict
		        } elseif {[lindex $t 0] eq "list"} {
			            set l [list]
			            foreach v $d {lappend l [tclon $v [lindex $t 1]]}
			            lappend res $l
		        } else {
			            lappend res [tclon $d $t]
		        }
	    }
	    set res
}



proc fromtclon {data {type *}} {	    if {![llength $type] || $type eq "*"} {return $data}
	    set res [list]
	    foreach t $type d $data {
		        if {[llength $t]<=1} {
			            lappend res $d
		        } elseif {[lindex $t 0] eq "dict"} {
			            set dict [list]
			            foreach {k v} $d {
				                lappend dict [fromtclon $k [lindex $t 1]] [fromtclon $v [lindex $t 2]]
			            }
			            lappend res $dict
		        } elseif {[lindex $t 0] eq "list"} {
			            set l [list]
			            foreach v $d {lappend l [fromtclon $v [lindex $t 1]]}
			            lappend res $l
		        } else {		                
			            lappend res [fromtclon $d $t]
		        }
	    }
	    set res
}

======
----
Some testing:
======
proc ? {s r} {	    if {![string equal $r [uplevel 1 $s]]} {error "assertion failed"}
}

proc validate {data type} {	    set out [fromtclon [tclon $data $type] $type]
	    if {$out ne $data} {puts "Difference:"
	    puts "1: $data"
	    puts "2: [tclon $data $type]"
	    puts "3: $out"
	    error "assertion failed"
	    }
}

? {tclon 1 int} 1
? {tclon {a b}} {a b}
? {tclon {{a b} b c} {{list s} *}} {{a b} b c}
validate 1 int
validate {a b} {s s}
validate {{a b} b c} {{list s} *}
validate {{x {1 2} y {23 4}}} {{dict key {a b}}}
======----
!!!!!!
%| [C<<categoryies>> Internet] |%
!!!!!!