1#
2#   YAML parser for Tcl.
3#
4#   See http://www.yaml.org/spec/1.1/
5#
6#   yaml.tcl,v 0.3.5 2009-05-24 11:52:34 KATO Kanryu(kanryu6@users.sourceforge.net)
7#
8#   It is published with the terms of tcllib's BSD-style license.
9#   See the file named license.terms.
10#
11# It currently supports a very limited subsection of the YAML spec.
12#
13#
14
15if {$::tcl_version < 8.5} {
16    package require dict
17}
18
19package provide yaml 0.3.5
20package require cmdline
21package require huddle
22
23
24namespace eval ::yaml {
25    namespace export load setOptions dict2dump list2dump
26    variable data
27    array set data {}
28
29    # fixed value groups for some yaml-types.
30    variable fixed
31
32    # a plane scalar is worked for matching and converting to the specific type.
33    # proc some_command {value} {
34    #   return [list !!type $treatmented-value]
35    #     or
36    #   return ""
37    # }
38    variable parsers
39
40    # scalar/collection treatment for matched specific yaml-tag
41    # proc some_composer {type value} {
42    #   return [list 1 $result-type $treatmented-value]
43    #     or
44    #   return ""
45    # }
46    variable composer
47
48    variable defaults
49    array set defaults {
50        isfile   0
51        validate 0
52        types {timestamp int float null true false}
53        composer {
54            !!binary ::yaml::_composeBinary
55        }
56        parsers {
57            timestamp ::yaml::_parseTimestamp
58        }
59        shorthands {
60            !! {tag:yaml.org,2002:}
61        }
62        fixed {
63            null:Value  ""
64            null:Group  {null "" ~}
65            true:Value  1
66            true:Group  {true on + yes y}
67            false:Value 0
68            false:Group {false off - no n}
69        }
70    }
71
72    variable _dumpIndent   2
73    variable _dumpWordWrap 40
74
75    variable opts [lrange [::cmdline::GetOptionDefaults {
76        {file             {input is filename}}
77        {stream           {input is stream}}
78        {m.arg        ""  {fixed-modifiers bulk setting(null/true/false)}}
79        {m:null.arg   ""  {null modifier setting(default {"" {null "" ~}})}}
80        {m:true.arg   ""  {true modifier setting(default {1 {true on + yes y}})}}
81        {m:false.arg  ""  {false modifier setting(default {0 {false off - no n}})}}
82        {types.arg    ""  {modifier list setting(default {nop timestamp integer null true false})}}
83        {validate         {to validate the input(not dumped tcl content)}}
84    } result] 2 end] ;# Remove ? and help.
85
86    variable errors
87    array set errors {
88        TAB_IN_PLAIN        {Tabs can be used only in comments, and in quoted "..." '...'.}
89        AT_IN_PLAIN         {Reserved indicators {@} can't start a plain scalar.}
90        BT_IN_PLAIN         {Reserved indicators {`} can't start a plain scalar.}
91        SEQEND_NOT_IN_SEQ   {There is a flow-sequence end '\]' not in flow-sequence [v, ...].}
92        MAPEND_NOT_IN_MAP   {There is a flow-mapping end '\}' not in flow-mapping {k: v, ...}.}
93        ANCHOR_NOT_FOUND    {Could not find the anchor-name(current-version, "after refering" is not supported)}
94        MALFORM_D_QUOTE     {Double quote "..." parsing error. end of quote is missing?}
95        MALFORM_S_QUOTE     {Single quote '...' parsing error. end of quote is missing?}
96        TAG_NOT_FOUND       {The "$p1" handle wasn't declared.}
97        INVALID_MERGE_KEY   {merge-key "<<" is not impremented in not mapping scope(e.g. in sequence).}
98        MALFORMED_MERGE_KEY {malformed merge-key "<<" using.}
99    }
100}
101
102
103####################
104# Public APIs
105####################
106
107proc ::yaml::yaml2dict {args} {
108    _getOption $args
109
110    set result [_parseBlockNode]
111    if {$yaml::data(validate)} {
112        set result [string map "{\n} {\\n}" $result]
113    }
114    return [huddle strip $result]
115}
116
117proc ::yaml::yaml2huddle {args} {
118    _getOption $args
119
120    set result [_parseBlockNode]
121    if {$yaml::data(validate)} {
122        set result [string map "{\n} {\\n}" $result]
123    }
124    return $result
125}
126
127proc ::yaml::setOptions {argv} {
128    variable defaults
129    array set options [_imp_getOptions argv]
130    array set defaults [array get options]
131}
132
133# Dump TCL List to YAML
134#
135
136proc ::yaml::list2yaml {list {indent 2} {wordwrap 40}} {
137    return [huddle2yaml [eval huddle list $list] $indent $wordwrap]
138}
139
140proc ::yaml::dict2yaml {dict {indent 2} {wordwrap 40}} {
141    return [huddle2yaml [eval huddle create $dict] $indent $wordwrap]
142}
143
144proc ::yaml::huddle2yaml {huddle {indent 2} {wordwrap 40}} {
145    set yaml::_dumpIndent   $indent
146    set yaml::_dumpWordWrap $wordwrap
147
148    # Start at the base of the array and move through it.
149    set out [join [list "---\n" [_imp_huddle2yaml $huddle] "\n"] ""]
150    return $out
151}
152
153
154####################
155# Option Setting
156####################
157
158proc ::yaml::_getOption {argv} {
159    variable data
160    variable parsers
161    variable fixed
162    variable composer
163
164    # default setting
165    array set options [_imp_getOptions argv]
166
167    array set fixed    $options(fixed)
168    array set parsers  $options(parsers)
169    array set composer $options(composer)
170    array set data [list validate $options(validate) types $options(types)]
171    set isfile $options(isfile)
172
173    foreach {buffer} $argv break
174    if {$isfile} {
175        set fd [open $buffer r]
176        set buffer [read $fd]
177        close $fd
178    }
179    set data(buffer) $buffer
180    set data(start)  0
181    set data(length) [string length $buffer]
182    set data(current) 0
183    set data(finished) 0
184}
185
186proc ::yaml::_imp_getOptions {{argvvar argv}} {
187    upvar 1 $argvvar argv
188
189    variable defaults
190    variable opts
191    array set options [array get defaults]
192
193    # default setting
194    array set fixed $options(fixed)
195
196    # parse argv
197    set argc [llength $argv]
198    while {[set err [::cmdline::getopt argv $opts opt arg]]} {
199        if {$err eq -1} break
200        switch -- $opt {
201            "file" {
202                set options(isfile) 1
203            }
204            "stream" {
205                set options(isfile) 0
206            }
207            "m" {
208                array set options(fixed) $arg
209            }
210            "validate" {
211                set options(validate) 1
212            }
213            "types" {
214                set options(types) $arg
215            }
216            default {
217                if {[regexp {m:(\w+)} $opt nop type]} {
218                    if {$arg eq ""} {
219                        set fixed(${type}:Group) ""
220                    } else {
221                        foreach {value group} $arg {
222                            set fixed(${type}:Value) $value
223                            set fixed(${type}:Group) $group
224                        }
225                    }
226                }
227            }
228        }
229    }
230    set options(fixed) [array get fixed]
231    return [array get options]
232}
233
234#########################
235# Scalar/Block Composers
236#########################
237proc ::yaml::_composeTags {tag value} {
238    if {$tag eq ""} {return $value}
239    set value [huddle strip $value]
240    if {$tag eq "!!str"} {
241        set pair [list $tag $value]
242    } elseif {[info exists yaml::composer($tag)]} {
243        set pair [$yaml::composer($tag) $value]
244    } else {
245        error [_getErrorMessage TAG_NOT_FOUND $tag]
246    }
247    return  [eval huddle wrap $pair]
248}
249
250proc ::yaml::_composeBinary {value} {
251    package require base64
252    return [list !!binary [::base64::decode $value]]
253}
254
255proc ::yaml::_composePlain {value} {
256    if {[huddle type $value] ne "plain"} {return $value}
257    set value [huddle strip $value]
258    set pair [_toType $value]
259    return  [eval huddle wrap $pair]
260}
261
262proc ::yaml::_toType {value} {
263    if {$value eq ""} {return [list !!str ""]}
264
265    set lowerval [string tolower $value]
266    foreach {type} $yaml::data(types) {
267        if {[info exists yaml::parsers($type)]} {
268            set pair [$yaml::parsers($type) $value]
269            if {$pair ne ""} {return $pair}
270            continue
271        }
272        switch -- $type {
273            int {
274                # YAML 1.1
275                if {[regexp {^-?\d[\d,]*\d$|^\d$} $value]} {
276                    regsub -all "," $value "" integer
277                    return [list !!int $integer]
278                }
279            }
280            float {
281                # don't run before "integer"
282                regsub -all "," $value "" val
283                if {[string is double $val]} {
284                    return [list !!float $val]
285                }
286            }
287            default {
288                # !!null !!true !!false
289                if {[info exists yaml::fixed($type:Group)] \
290                 && [lsearch $yaml::fixed($type:Group) $lowerval] >= 0} {
291                    set value $yaml::fixed($type:Value)
292                    return [list !!$type $value]
293                }
294            }
295        }
296    }
297
298    # the others
299    return [list !!str $value]
300}
301
302####################
303# Block Node parser
304####################
305proc ::yaml::_parseBlockNode {{status ""} {indent -1}} {
306    variable data
307    set prev {}
308    set result {}
309    set scalar 0
310    set pos 0
311    set tag ""
312    while {1} {
313        if {$data(finished) == 1} {
314            break
315        }
316        _skipSpaces 1
317        set type [_getc]
318        set current [_getCurrent]
319        if {$type eq "-"} {
320            set cc "[_getc][_getc]"
321            if {"$type$cc" eq "---" && $current == 0} {
322                set result {}
323                continue
324            } else {
325                _ungetc 2
326
327                # [Spec]
328                # Since people perceive the�g-�hindicator as indentation,
329                # nested block sequences may be indented by one less space
330                # to compensate, except, of course,
331                # if nested inside another block sequence.
332                incr current
333            }
334        }
335        if {$type eq "."} {
336            set cc "[_getc][_getc]"
337            if {"$type$cc" eq "..." && $current == 0} {
338                set data(finished) 1
339                break
340            } else {
341                _ungetc 2
342
343#                 # [Spec]
344#                 # Since people perceive the�g-�hindicator as indentation,
345#                 # nested block sequences may be indented by one less space
346#                 # to compensate, except, of course,
347#                 # if nested inside another block sequence.
348#                 incr current
349            }
350        }
351        if {$type eq ""  || $current <= $indent} { ; # end document
352            _ungetc
353            break
354        }
355        switch -- $type {
356            "-" { ; # block sequence entry
357                set pos $current
358                # [196]      l-block-seq-entry(n,c)
359                foreach {scalar value} [_parseSubBlock $pos "SEQUENCE"] break
360            }
361            "?" { ; # mapping key
362                foreach {scalar nop} [_parseSubBlock $pos ""] break
363            }
364            ":" { ; # mapping value
365                if {$current < $pos} {set pos [expr {$current+1}]}
366                foreach {scalar value} [_parseSubBlock $pos "MAPPING"] break
367            }
368            "|" { ; # literal block scalar
369                set value [_parseBlockScalar $indent "\n"]
370            }
371            ">" { ; # folded block scalar
372                set value [_parseBlockScalar $indent " "]
373            }
374            "<" { ; # mergeing
375                set c [_getc]
376                if {"$type$c" eq "<<"} {
377                    set pos [_getCurrent]
378                    _skipSpaces 1
379                    set c [_getc]
380                    if {$c ne ":"} {error [_getErrorMessage INVALID_MERGE_KEY]}
381                    if {$status ne "" && $status ne "MAPPING"} {error [_getErrorMessage INVALID_MERGE_KEY]}
382                    set status "MAPPING"
383                    foreach {result prev} [_mergeExpandedAliases $result $pos $prev] break
384                } else {
385                    _ungetc
386                    set scalar 1
387                }
388            }
389            "&" { ; # node's anchor property
390                set anchor [_getToken]
391            }
392            "*" { ; # alias node
393                set alias [_getToken]
394                if {$yaml::data(validate)} {
395                    set status "ALIAS"
396                    set value *$alias
397                } else {
398                    set value [_getAnchor $alias]
399                }
400            }
401            "!" { ; # node's tag
402                _ungetc
403                set tag [_getToken]
404            }
405            "%" { ; # directive line
406                _getLine
407            }
408            default {
409                if {[regexp {^[\[\]\{\}\"']$} $type]} {
410                    set pos [expr {1 + $current}]
411                    _ungetc
412                    set value [_parseFlowNode]
413                } else {
414                    set scalar 1
415                }
416            }
417        }
418        if {$scalar} {
419            set pos [_getCurrent]
420            _ungetc
421            set value [_parseScalarNode $type "BLOCK" $pos]
422            set value [_composeTags $tag $value]
423            set tag ""
424            set scalar 0
425        }
426        if {[info exists value]} {
427            if {$status eq "NODE"} {return $value}
428            foreach {result prev} [_pushValue $result $prev $status $value "BLOCK"] break
429            unset value
430        }
431    }
432    if {$status eq "SEQUENCE"} {
433        set result [eval huddle sequence $result]
434    } elseif {$status eq "MAPPING"} {
435        if {[llength $prev] == 2} {
436            set result [_set_huddle_mapping $result $prev]
437        }
438    } else {
439        if {[info exists prev]} {
440            set result $prev
441        }
442        set result [lindex $result 0]
443        set result [_composePlain $result]
444        if {![huddle isHuddle $result]} {
445            set result [huddle wrap !!str $result]
446        }
447    }
448    if {$tag ne ""} {
449        set result [_composeTags $tag $result]
450        unset tag
451    }
452    if {[info exists anchor]} {
453        _setAnchor $anchor $result
454        unset anchor
455    }
456    return $result
457}
458
459proc ::yaml::_mergeExpandedAliases {result pos prev} {
460    if {$result eq ""} {set result [huddle mapping]}
461    if {$prev ne ""} {
462        if {[llength $prev] < 2} {error [_getErrorMessage MALFORMED_MERGE_KEY]}
463        set result [_set_huddle_mapping $result $prev]
464        set prev {}
465    }
466
467    set value [_parseBlockNode "" $pos]
468    if {[huddle type $value] eq "list"} {
469        set len [huddle llength $value]
470        for {set i 0} {$i < $len} {incr i} {
471            set sub [huddle get $value $i]
472            set result [huddle combine $result $sub]
473        }
474        unset sub len
475    } else {
476        set result [huddle combine $result $value]
477    }
478    return [list $result $prev]
479}
480
481
482proc ::yaml::_parseSubBlock {pos statusnew} {
483    upvar 1 status status
484    set scalar 0
485    set value ""
486    if {[_next_is_blank]} {
487        if {$statusnew ne ""} {
488            set status $statusnew
489            set value [_parseBlockNode "" $pos]
490        }
491    } else {
492        _ungetc
493        set scalar 1
494    }
495    return [list $scalar $value]
496}
497
498proc ::yaml::_set_huddle_mapping {result prev} {
499    foreach {key val} $prev break
500    set val [_composePlain $val]
501    if {[huddle isHuddle $key]} {
502        set key [huddle strip $key]
503    }
504    if {$result eq ""} {
505        set result [huddle mapping $key $val]
506    } else {
507        huddle append result $key $val
508    }
509    return $result
510}
511
512
513# remove duplications with saving key order
514proc ::yaml::_remove_duplication {dict} {
515    array set tmp $dict
516    array set tmp2 {}
517    foreach {key nop} $dict {
518        if {[info exists tmp2($key)]} continue
519        lappend result $key $tmp($key)
520        set tmp2($key) 1
521    }
522    return $result
523}
524
525
526# literal "|" (line separator is "\n")
527# folding ">" (line separator is " ")
528proc ::yaml::_parseBlockScalar {base separator} {
529    foreach {explicit chomping} [_parseBlockIndicator] break
530
531    set idch [string repeat " " $explicit]
532    set sep $separator
533    foreach {indent c line} [_getLine] break
534    if {$indent < $base} {return ""}
535    # the first line, NOT ignored comment (as a normal-string)
536    set first $indent
537    set value $line
538    set stop 0
539
540    while {![_eof]} {
541        set pos [_getpos]
542        foreach {indent c line} [_getLine] break
543        if {$line eq ""} {
544            regsub " " $sep "" sep
545            append sep "\n"
546            continue
547        }
548        if {$c eq "#"} {
549            # skip comments
550            continue
551        }
552        if {$indent <= $base} {
553            set stop 1
554            break
555        }
556        append value $sep[string repeat " " [expr {$indent - $first}]]$line
557        set sep $separator
558    }
559    if {[info exists pos] && $stop} {_setpos $pos}
560    switch -- $chomping {
561        "strip" {
562        }
563        "keep" {
564            append value $sep
565        }
566        "clip" {
567            append value "\n"
568        }
569    }
570    return [huddle wrap !!str $value]
571}
572
573# in {> |}
574proc ::yaml::_parseBlockIndicator {} {
575    set chomping "clip"
576    set explicit 0
577    while {1} {
578        set type [_getc]
579        if {[regexp {[1-9]} $type digit]} { ; # block indentation
580            set explicit $digit
581        } elseif {$type eq "-"} {   ; # strip chomping
582            set chomping "strip"
583        } elseif {$type eq "+"} {   ; # keep chomping
584            set chomping "keep"
585        } else {
586            _ungetc
587            break
588        }
589    }
590    # Note: skipped after the indicator
591    _getLine
592    return [list $explicit $chomping]
593}
594
595# [162]    ns-plain-multi(n,c)
596proc ::yaml::_parsePlainScalarInBlock {base {loop 0}} {
597    if {$loop == 5} { return }
598    variable data
599    set start $data(start)
600    set reStr {(?:[^:#\t \n]*(?::[^\t \n]+)*(?:#[^\t \n]+)* *)*[^:#\t \n]*}
601    set result [_getFoldedString $reStr]
602
603    set result [string trim $result]
604    set c [_getc 0]
605    if {$c eq "\n" || $c eq "#"} { ; # multi-line
606        set lb ""
607        while {1} {
608            set fpos [_getpos]
609            foreach {indent nop line} [_getLine] break
610            if {[_eof]} {break}
611
612            if {$line ne "" && [string index $line 0] ne "#"} {
613                break
614            }
615            append lb "\n"
616        }
617        set lb [string range $lb 1 end]
618        if {!$yaml::data(finished)} {
619            _setpos $fpos
620        }
621        if {$start == $data(start)} {
622            return $result
623        }
624        if {$base <= $indent} {
625            if {$lb eq ""} {
626                set lb " "
627            }
628            set subs [_parsePlainScalarInBlock $base [expr {$loop+1}]]
629           if {$subs ne ""} {
630                append result "$lb$subs"
631            }
632        }
633    }
634    return $result
635}
636
637####################
638# Flow Node parser
639####################
640proc ::yaml::_parseFlowNode {{status ""}} {
641    set scalar 0
642    set result {}
643    set tag ""
644    set prev {}
645    while {1} {
646        _skipSpaces 1
647        set type [_getc]
648        switch -- $type {
649            "" {
650                break
651            }
652            "?" -
653            ":" { ; # mapping value
654                if {[_next_is_blank]} {
655                    set value [_parseFlowNode "NODE"]
656                } else {
657                    set scalar 1
658                }
659            }
660            "," { ; # ends a flow collection entry
661                if {$status eq"NODE"} {
662                    _ungetc
663                    return $value
664                }
665            }
666            "\{" { ; # starts a flow mapping
667                set value [_parseFlowNode "MAPPING"]
668            }
669            "\}" { ; # ends a flow mapping
670                if {$status ne "MAPPING"}  {error [_getErrorMessage MAPEND_NOT_IN_MAP] }
671                return $result
672            }
673            "\[" { ; # starts a flow sequence
674                 set value [_parseFlowNode "SEQUENCE"]
675            }
676            "\]" { ; # ends a flow sequence
677                if {$status ne "SEQUENCE"} {error [_getErrorMessage SEQEND_NOT_IN_SEQ] }
678                set result [eval huddle sequence $result]
679                return $result
680            }
681            "&" { ; # node's anchor property
682                set anchor [_getToken]
683            }
684            "*" { ; # alias node
685                set alias [_getToken]
686                set value [_getAnchor $alias]
687            }
688            "!" { ; # node's tag
689                _ungetc
690                set tag [_getToken]
691            }
692            "%" { ; # directive line
693                _ungetc
694                _parseDirective
695            }
696            default {
697                set scalar 1
698            }
699        }
700        if {$scalar} {
701            _ungetc
702            set value [_parseScalarNode $type "FLOW"]
703            set value [_composeTags $tag $value]
704            set tag ""
705            set scalar 0
706        }
707        if {[info exists value]} {
708            if {[info exists anchor]} {
709                _setAnchor $anchor $value
710                unset anchor
711            }
712            if {$status eq "" || $status eq "NODE"} {return $value}
713            foreach {result prev} [_pushValue $result $prev $status $value "FLOW"] break
714            unset value
715        }
716    }
717    return $result
718}
719
720proc ::yaml::_pushValue {result prev status value scope} {
721    switch -- $status {
722        "SEQUENCE" {
723            lappend result [_composePlain $value]
724        }
725        "MAPPING" {
726            if {$scope eq "BLOCK"} {
727                if {[llength $prev] == 2} {
728                    set result [_set_huddle_mapping $result $prev]
729                    set prev [list $value]
730                } else {
731                    lappend prev $value
732                }
733            } else {
734                lappend prev $value
735                if {[llength $prev] == 2} {
736                    set result [_set_huddle_mapping $result $prev]
737                    set prev ""
738                }
739            }
740        }
741        default {
742            if {$scope eq "BLOCK"} {lappend prev $value}
743        }
744    }
745    return [list $result $prev]
746}
747
748proc ::yaml::_parseScalarNode {type scope {pos 0}} {
749    set tag !!str
750    switch -- $type {
751        \" { ; # surrounds a double-quoted flow scalar
752            set value [_parseDoubleQuoted]
753        }
754        {'} { ; # surrounds a single-quoted flow scalar
755            set value [_parseSingleQuoted]
756        }
757        "\t" {error [_getErrorMessage TAB_IN_PLAIN] }
758        "@"  {error [_getErrorMessage AT_IN_PLAIN] }
759        "`"  {error [_getErrorMessage BT_IN_PLAIN] }
760        default {
761            # Plane Scalar
762            if       {$scope eq "FLOW"} {
763                set value [_parsePlainScalarInFlow]
764            } elseif {$scope eq "BLOCK"} {
765                set value [_parsePlainScalarInBlock $pos]
766            }
767            set tag !!plain
768        }
769    }
770    return [huddle wrap $tag $value]
771}
772
773# [time scanning at JST]
774# 2001-12-15T02:59:43.1Z       => 1008385183
775# 2001-12-14t21:59:43.10-05:00 => 1008385183
776# 2001-12-14 21:59:43.10 -5    => 1008385183
777# 2001-12-15 2:59:43.10        => 1008352783
778# 2002-12-14                   => 1039791600
779proc ::yaml::_parseTimestamp {scalar} {
780    if {![regexp {^\d\d\d\d-\d\d-\d\d} $scalar]} {return ""}
781    set datestr  {\d\d\d\d-\d\d-\d\d}
782    set timestr  {\d\d?:\d\d:\d\d}
783    set timezone {Z|[-+]\d\d?(?::\d\d)?}
784
785    set canonical [subst -nobackslashes -nocommands {^($datestr)[Tt ]($timestr)\.\d+ ?($timezone)?$}]
786    set dttm [subst -nobackslashes -nocommands {^($datestr)(?:[Tt ]($timestr))?$}]
787    if {$::tcl_version < 8.5} {
788        if {[regexp $canonical $scalar nop dt tm zone]} {
789            # Canonical
790            if {$zone eq ""} {
791                return [list !!timestamp [clock scan "$dt $tm"]]
792            } elseif {$zone eq "Z"} {
793                return [list !!timestamp [clock scan "$dt $tm" -gmt 1]]
794            }
795            if {[regexp {^([-+])(\d\d?)$} $zone nop sign d]} {set zone [format "$sign%02d:00" $d]}
796            regexp {^([-+]\d\d):(\d\d)} $zone nop h m
797            set m [expr {$h > 0 ? $h*60 + $m : $h*60 - $m}]
798            return [list !!timestamp [clock scan "[expr {-$m}] minutes" -base [clock scan "$dt $tm" -gmt 1]]]
799        } elseif {[regexp $dttm $scalar nop dt tm]} {
800            if {$tm ne ""} {
801                return [list !!timestamp [clock scan "$dt $tm"]]
802            } else {
803                return [list !!timestamp [clock scan $dt]]
804            }
805        }
806    } else {
807        if {[regexp $canonical $scalar nop dt tm zone]} {
808            # Canonical
809            if {$zone ne ""} {
810                if {[regexp {^([-+])(\d\d?)$} $zone nop sign d]} {set zone [format "$sign%02d:00" $d]}
811                return [list !!timestamp [clock scan "$dt $tm $zone" -format {%Y-%m-%d %k:%M:%S %Z}]]
812            } else {
813                return [list !!timestamp [clock scan "$dt $tm"       -format {%Y-%m-%d %k:%M:%S}]]
814            }
815        } elseif {[regexp $dttm $scalar nop dt tm]} {
816            if {$tm ne ""} {
817                return [list !!timestamp [clock scan "$dt $tm" -format {%Y-%m-%d %k:%M:%S}]]
818            } else {
819                return [list !!timestamp [clock scan $dt       -format {%Y-%m-%d}]]
820            }
821        }
822    }
823    return ""
824}
825
826
827proc ::yaml::_parseDirective {} {
828    variable data
829    variable shorthands
830
831    set directive [_getToken]
832
833    if {[regexp {^%YAML} $directive]} {
834        # YAML directive
835        _skipSpaces
836        set version [_getToken]
837        set data(YAMLVersion) $version
838        if {![regexp {^\d\.\d$} $version]}   { error [_getErrorMessage ILLEGAL_YAML_DIRECTIVE] }
839    } elseif {[regexp {^%TAG} $directive]} {
840        # TAG directive
841        _skipSpaces
842        set handle [_getToken]
843        if {![regexp {^!$|^!\w*!$} $handle]} { error [_getErrorMessage ILLEGAL_YAML_DIRECTIVE] }
844
845        _skipSpaces
846        set prefix [_getToken]
847        if {![regexp {^!$|^!\w*!$} $prefix]} { error [_getErrorMessage ILLEGAL_YAML_DIRECTIVE] }
848        set shorthands(handle) $prefix
849    }
850}
851
852proc ::yaml::_parseTagHandle {} {
853    set token [_getToken]
854
855    if {[regexp {^(!|!\w*!)(.*)} $token nop handle named]} {
856        # shorthand or non-specific Tags
857        switch -- $handle {
858            ! { ;       # local or non-specific Tags
859            }
860            !! { ;      # yaml Tags
861            }
862            default { ; # shorthand Tags
863
864            }
865        }
866        if {![info exists prefix($handle)]} { error [_getErrorMessage TAG_NOT_FOUND] }
867    } elseif {[regexp {^!<(.+)>} $token nop uri]} {
868        # Verbatim Tags
869        if {![regexp {^[\w:/]$} $token nop uri]} { error [_getErrorMessage ILLEGAL_TAG_HANDLE] }
870    } else {
871        error [_getErrorMessage ILLEGAL_TAG_HANDLE]
872    }
873
874    return "!<$prefix($handle)$named>"
875}
876
877
878proc ::yaml::_parseDoubleQuoted {} {
879    # capture quoted string with backslash sequences
880    set reStr {(?:(?:\")(?:[^\\\"]*(?:\\.[^\\\"]*)*)(?:\"))}
881    set result [_getFoldedString $reStr]
882    if {$result eq ""} { error [_getErrorMessage MALFORM_D_QUOTE] }
883
884    # [116] nb-double-multi-line
885    regsub -all {[ \t]*\n[\t ]*} $result "\r" result
886    regsub -all {([^\r])\r} $result {\1 } result
887    regsub -all { ?\r} $result "\n" result
888    # [112] s-s-double-escaped(n)
889    # is not impremented.(specification ???)
890
891    # chop off outer ""s and substitute backslashes
892    # This does more than the RFC-specified backslash sequences,
893    # but it does cover them all
894    set chopped [subst -nocommands -novariables \
895        [string range $result 1 end-1]]
896    return $chopped
897}
898
899proc ::yaml::_parseSingleQuoted {} {
900    set reStr {(?:(?:')(?:[^']*(?:''[^']*)*)(?:'))}
901    set result [_getFoldedString $reStr]
902    if {$result eq ""} { error [_getErrorMessage MALFORM_S_QUOTE] }
903
904    # [126] nb-single-multi-line
905    regsub -all {[ \t]*\n[\t ]*} $result "\r" result
906    regsub -all {([^\r])\r} $result {\1 } result
907    regsub -all { ?\r} $result "\n" result
908
909    regsub -all {''} [string range $result 1 end-1] {'} chopped
910
911    return $chopped
912}
913
914
915# [155]     nb-plain-char-in
916proc ::yaml::_parsePlainScalarInFlow {} {
917    set sep {\t \n,\[\]\{\}}
918    set reStr {(?:[^$sep:#]*(?::[^$sep]+)*(?:#[^$sep]+)* *)*[^$sep:#]*}
919    set reStr [subst -nobackslashes -nocommands $reStr]
920    set result [_getFoldedString $reStr]
921    set result [string trim $result]
922
923    if {[_getc 0] eq "#"} {
924        _getLine
925        set result "$result [_parsePlainScalarInFlow]"
926    }
927    return $result
928}
929
930####################
931# Generic parser
932####################
933proc ::yaml::_getFoldedString {reStr} {
934    variable data
935
936    set buff [string range $data(buffer) $data(start) end]
937    regexp $reStr $buff token
938    if {![info exists token]} {return}
939
940    set len [string length $token]
941    if {[string first "\n" $token] >= 0} { ; # multi-line
942        set data(current) [expr {$len - [string last "\n" $token]}]
943    } else {
944        incr data(current) $len
945    }
946    incr data(start) $len
947
948    return $token
949}
950
951# get a space separated token
952proc ::yaml::_getToken {} {
953    variable data
954
955    set reStr {^[^ \t\n,]+}
956    set result [_getFoldedString $reStr]
957    return $result
958}
959
960proc ::yaml::_skipSpaces {{commentSkip 0}} {
961    variable data
962
963    while {1} {
964        set ch [string index $data(buffer) $data(start)]
965        incr data(start)
966        switch -- $ch {
967            " " {
968                incr data(current)
969                continue
970            }
971            "\n" {
972                set data(current) 0
973                continue
974            }
975            "\#" {
976                if {$commentSkip} {
977                    _getLine
978                    continue
979                }
980            }
981        }
982        break
983    }
984    incr data(start) -1
985}
986
987# get a line of stream(line-end trimed)
988# (cannot _ungetc)
989proc ::yaml::_getLine {{scrolled 1}} {
990    variable data
991
992    set pos [string first "\n" $data(buffer) $data(start)]
993    if {$pos == -1} {
994        set pos $data(length)
995    }
996    set line [string range $data(buffer) $data(start) [expr {$pos-1}]]
997    if {$line eq "..." && $data(current) == 0} {
998        set data(finished) 1
999    }
1000    regexp {^( *)(.*)} $line nop space result
1001    if {$scrolled} {
1002        set data(start) [expr {$pos + 1}]
1003        set data(current) 0
1004    }
1005    if {$line == "" && $data(start) == $data(length)} {
1006        set data(finished) 1
1007    }
1008    return [list [string length $space] [string index $result 0] $result]
1009}
1010
1011proc ::yaml::_getCurrent {} {
1012    variable data
1013    return [expr {$data(current) ? $data(current)-1 : 0}]
1014}
1015
1016proc ::yaml::_getLineNum {} {
1017    variable data
1018    set prev [string range $data(buffer) 0 $data(start)]
1019    return [llength [split $prev "\n"]]
1020}
1021
1022proc ::yaml::_getc {{scrolled 1}} {
1023    variable data
1024
1025    set result [string index $data(buffer) $data(start)]
1026    if {$scrolled} {
1027        incr data(start)
1028        if {$result eq "\n"} {
1029            set data(current) 0
1030        } else {
1031            incr data(current)
1032        }
1033    }
1034    return $result
1035}
1036
1037proc ::yaml::_eof {} {
1038    variable data
1039    return [expr {$data(finished) || $data(start) == $data(length)}]
1040}
1041
1042
1043proc ::yaml::_getpos {} {
1044    variable data
1045    return $data(start)
1046}
1047
1048proc ::yaml::_setpos {pos} {
1049    variable data
1050    set data(start) $pos
1051}
1052
1053proc ::yaml::_ungetc {{len 1}} {
1054    variable data
1055    incr data(start) [expr {-$len}]
1056    incr data(current) [expr {-$len}]
1057    if {$data(current) < 0} {
1058        set prev [string range $data(buffer) 0 $data(start)]
1059        if {[string index $prev end] eq "\n"} {set prev [string replace $prev end end a]}
1060        set data(current) [expr {$data(start) - [string last "\n" $prev] - 1}]
1061    }
1062}
1063
1064proc ::yaml::_next_is_blank {} {
1065    set c [_getc 0]
1066    if {$c eq " " || $c eq "\n"} {
1067        return 1
1068    } else {
1069        return 0
1070    }
1071}
1072
1073proc ::yaml::_setAnchor {anchor value} {
1074    variable data
1075    set data(anchor:$anchor) $value
1076}
1077
1078proc ::yaml::_getAnchor {anchor} {
1079    variable data
1080    if {![info exists data(anchor:$anchor)]} {error [_getErrorMessage ANCHOR_NOT_FOUND]}
1081    return  $data(anchor:$anchor)
1082}
1083
1084proc ::yaml::_getErrorMessage {ID {p1 ""}} {
1085    set num [_getLineNum]
1086    if {$p1 != ""} {
1087        return "line($num): [subst -nobackslashes -nocommands $yaml::errors($ID)]"
1088    } else {
1089        return "line($num): $yaml::errors($ID)"
1090    }
1091}
1092
1093# Finds and returns the indentation of a YAML line
1094proc ::yaml::_getIndent {line} {
1095    set match [regexp -inline -- {^\s{1,}} " $line"]
1096    return [expr {[string length $match] - 3}]
1097}
1098
1099
1100################
1101## Dumpers    ##
1102################
1103
1104proc ::yaml::_imp_huddle2yaml {data {offset ""}} {
1105    set nextoff "$offset[string repeat { } $yaml::_dumpIndent]"
1106    switch -- [huddle type $data] {
1107        "string" {
1108            set data [huddle strip $data]
1109            return [_dumpScalar $data $offset]
1110        }
1111        "list" {
1112            set inner {}
1113            set len [huddle llength $data]
1114            for {set i 0} {$i < $len} {incr i} {
1115                set sub [huddle get $data $i]
1116                set sep [expr {[huddle type $sub] eq "string" ? " " : "\n"}]
1117                lappend inner [join [list $offset - $sep [_imp_huddle2yaml $sub $nextoff]] ""]
1118            }
1119            return [join $inner "\n"]
1120        }
1121        "dict" {
1122            set inner {}
1123            foreach {key} [huddle keys $data] {
1124                set sub [huddle get $data $key]
1125                set sep [expr {[huddle type $sub] eq "string" ? " " : "\n"}]
1126                lappend inner [join [list $offset $key: $sep [_imp_huddle2yaml $sub $nextoff]] ""]
1127            }
1128            return [join $inner "\n"]
1129        }
1130        default {
1131            return $data
1132        }
1133    }
1134}
1135
1136proc ::yaml::_dumpScalar {value offset} {
1137    if {   [string first "\n" $value] >= 0
1138        || [string first ": " $value] >= 0
1139        || [string first "- " $value] >= 0} {
1140        return [_doLiteralBlock $value $offset]
1141    } else {
1142        return [_doFolding $value $offset]
1143    }
1144}
1145
1146# Creates a literal block for dumping
1147proc ::yaml::_doLiteralBlock {value offset} {
1148    if {[string index $value end] eq "\n"} {
1149        set newValue "|"
1150        set value [string range $value 0 end-1]
1151    } else {
1152        set newValue "|-"
1153    }
1154    set exploded [split $value "\n"]
1155
1156    set value [string trimright $value]
1157    foreach {line} $exploded {
1158        set newValue "$newValue\n$offset[string trim $line]"
1159    }
1160    return $newValue
1161}
1162
1163# Folds a string of text, if necessary
1164proc ::yaml::_doFolding {value offset} {
1165    variable _dumpWordWrap
1166    # Don't do anything if wordwrap is set to 0
1167    if {$_dumpWordWrap == 0} {
1168        return $value
1169    }
1170
1171    if {[string length $value] > $_dumpWordWrap} {
1172        set wrapped [_simple_justify $value $_dumpWordWrap "\n$offset"]
1173        set value ">\n$offset$wrapped"
1174    }
1175    return $value
1176}
1177
1178# http://wiki.tcl.tk/1774
1179proc ::yaml::_simple_justify {text width {wrap \n} {cut 0}} {
1180    set brk ""
1181    for {set result {}} {[string length $text] > $width} {
1182                set text [string range $text [expr {$brk+1}] end]
1183            } {
1184        set brk [string last " " $text $width]
1185        if { $brk < 0 } {
1186            if {$cut == 0} {
1187                append result $text
1188                return $result
1189            } else {
1190                set brk $width
1191            }
1192        }
1193        append result [string range $text 0 $brk] $wrap
1194    }
1195    return $result$text
1196}
1197
1198########################
1199## Huddle Settings    ##
1200########################
1201
1202
1203proc ::yaml::_huddle_mapping {command args} {
1204    switch -- $command {
1205        setting { ; # type definition
1206            return {
1207                type dict
1208                method {mapping}
1209                tag {!!map parent}
1210                constructor mapping
1211                str !!str
1212            }
1213        }
1214        mapping { ; # $args: all arguments after "huddle mapping"
1215            if {[llength $args] % 2} {error {wrong # args: should be "huddle mapping ?key value ...?"}}
1216            set resultL {}
1217            foreach {key value} $args {
1218                lappend resultL $key [huddle to_node $value !!str]
1219            }
1220            return [huddle wrap !!map $resultL]
1221        }
1222        default { ; # devolving to default dict-callback
1223            return [huddle call D $command $args]
1224        }
1225    }
1226}
1227
1228proc ::yaml::_huddle_sequence {command args} {
1229    switch -- $command {
1230        setting { ; # type definition
1231            return {
1232                type list
1233                method {sequence}
1234                tag {!!seq parent}
1235                constructor sequence
1236                str !!str
1237            }
1238        }
1239        sequence {
1240            set resultL {}
1241            foreach {value} $args {
1242                lappend resultL [huddle to_node $value !!str]
1243            }
1244            return [huddle wrap !!seq $resultL]
1245        }
1246        default {
1247            return [huddle call L $command $args]
1248        }
1249    }
1250}
1251
1252proc ::yaml::_makeChildType {type tag} {
1253    set procname ::yaml::_huddle_$type
1254    proc $procname {command args} [string map "@TYPE@ $type @TAG@ $tag" {
1255        switch -- $command {
1256            setting { ; # type definition
1257                return {
1258                    type @TYPE@
1259                    method {}
1260                    tag {@TAG@ child}
1261                    constructor ""
1262                    str @TAG@
1263                }
1264            }
1265            default {
1266                return [huddle call s $command $args]
1267            }
1268        }
1269    }]
1270    return $procname
1271}
1272
1273huddle addType ::yaml::_huddle_mapping
1274huddle addType ::yaml::_huddle_sequence
1275huddle addType [::yaml::_makeChildType string !!str]
1276huddle addType [::yaml::_makeChildType string !!timestamp]
1277huddle addType [::yaml::_makeChildType string !!float]
1278huddle addType [::yaml::_makeChildType string !!int]
1279huddle addType [::yaml::_makeChildType string !!null]
1280huddle addType [::yaml::_makeChildType string !!true]
1281huddle addType [::yaml::_makeChildType string !!false]
1282huddle addType [::yaml::_makeChildType string !!binary]
1283huddle addType [::yaml::_makeChildType plain !!plain]
1284
1285
1286