home *** CD-ROM | disk | FTP | other *** search
Text File | 1998-11-21 | 18.8 KB | 540 lines | [TEXT/ALFA] |
- ## -*-Tcl-*- (nowrap)
- # ###################################################################
- # AEParse - Parsing functions for AEGizmo strings
- #
- # FILE: "aeparse.tcl" (formerly aevt.tcl)
- # created: 7/26/97 {6:44:05 pm}
- # last update: 11/21/98 {10:21:12 PM}
- # version: 1.1
- # Author: Jonathan Guyer
- # E-mail: <jguyer@his.com>
- # www: <http://www.his.com/~jguyer/>
- #
- # ###################################################################
- ##
-
- ##
- # Copyright (c) 1998 Jonathan Guyer
- #
- # This program is free software; you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation; either version 2 of the License, or
- # (at your option) any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program; if not, write to the Free Software
- # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- ##
-
- ##
- # Note that 'try' is used very sparingly in this code because,
- # although syntactically pleasing, it is too slow.
- ##
-
- ##
- # With the exception of aeparse::event, the parsers in this package
- # take the _name_ of a string variable as their argument and the
- # string is parsed in place. Because it will typically be used to
- # parse the output of ‘AEBuild -r’, aeparse::event takes a string
- # as its argument. Since there is no forseeable reason for
- # external code to call any parser but aeparse::event, this
- # distinction should not be a problem.
- ##
-
- namespace eval aeparse {}
-
- # ◊◊◊◊ Initialization Code ◊◊◊◊ #
-
- # Error messages from
- # <http://devworld.apple.com/dev/techsupport/insidemac/
- # AppleScriptLang/AppleScriptLang-271.html#HEADING271-0>
-
- # Many, obviously, aren't relevant
-
- # Operating System Errors
-
- set aeparse::errors(-34) {System -34 {Disk is full.}}
- set aeparse::errors(-35) {System -35 {Disk wasn't found.}}
- set aeparse::errors(-37) {System -37 {Bad name for file.}}
- set aeparse::errors(-38) {System -38 {File wasn't open.}}
- set aeparse::errors(-39) {System -39 {End of file error.}}
- set aeparse::errors(-42) {System -42 {Too many files open.}}
- set aeparse::errors(-43) {System -43 {File wasn't found.}}
- set aeparse::errors(-44) {System -44 {Disk is write protected.}}
- set aeparse::errors(-45) {System -45 {File is locked.}}
- set aeparse::errors(-46) {System -46 {Disk is locked.}}
- set aeparse::errors(-47) {System -47 {File is busy.}}
- set aeparse::errors(-48) {System -48 {Duplicate file name.}}
- set aeparse::errors(-49) {System -49 {File is already open.}}
- set aeparse::errors(-50) {System -50 {Parameter error.}}
- set aeparse::errors(-51) {System -51 {File reference number error.}}
- set aeparse::errors(-61) {System -61 {File not open with write permission.}}
- set aeparse::errors(-108) {System -108 {Out of memory.}}
- set aeparse::errors(-120) {System -120 {Folder wasn't found.}}
- set aeparse::errors(-124) {System -124 {Disk is disconnected.}}
- set aeparse::errors(-128) {System -128 {User canceled.}}
- set aeparse::errors(-192) {System -192 {A resource wasn't found.}}
- set aeparse::errors(-600) {System -600 {Application isn't running.}}
- set aeparse::errors(-601) {System -601 {Not enough room to launch application with special requirements.}}
- set aeparse::errors(-602) {System -602 {Application is not 32-bit clean.}}
- set aeparse::errors(-605) {System -605 {More memory is needed than is specified in the size resource.}}
- set aeparse::errors(-606) {System -606 {Application is background-only.}}
- set aeparse::errors(-607) {System -607 {Buffer is too small.}}
- set aeparse::errors(-608) {System -608 {No outstanding high-level event.}}
- set aeparse::errors(-609) {System -609 {Connection is invalid.}}
- set aeparse::errors(-904) {System -904 {Not enough system memory to connect to remote application.}}
- set aeparse::errors(-905) {System -905 {Remote access is not allowed.}}
- set aeparse::errors(-906) {System -906 {Program isn't running or program linking isn't enabled.}}
- set aeparse::errors(-915) {System -915 {Can't find remote machine.}}
- set aeparse::errors(-30720) {System -30720 {Invalid date and time.}}
-
- # AppleEvent Errors
-
- set aeparse::errors(-1700) {AppleEvent -1700 {Can't make some data into the expected type.}}
- set aeparse::errors(-1701) {AppleEvent -1701 {Some parameter is missing.}}
- set aeparse::errors(-1702) {AppleEvent -1702 {Some data could not be read.}}
- set aeparse::errors(-1703) {AppleEvent -1703 {Some data was the wrong type.}}
- set aeparse::errors(-1704) {AppleEvent -1704 {Some parameter was invalid.}}
- set aeparse::errors(-1705) {AppleEvent -1705 {Operation involving a list item failed.}}
- set aeparse::errors(-1706) {AppleEvent -1706 {Need a newer version of the AppleEvent manager.}}
- set aeparse::errors(-1707) {AppleEvent -1707 {Event isn't an AppleEvent.}}
- set aeparse::errors(-1708) {AppleEvent -1708 {<reference> doesn't understand the <commandName> message.}}
- set aeparse::errors(-1709) {AppleEvent -1709 {AEResetTimer was passed an invalid reply.}}
- set aeparse::errors(-1710) {AppleEvent -1710 {Invalid sending mode was passed.}}
- set aeparse::errors(-1711) {AppleEvent -1711 {User canceled out of wait loop for reply or receipt.}}
- set aeparse::errors(-1712) {AppleEvent -1712 {AppleEvent timed out.}}
- set aeparse::errors(-1713) {AppleEvent -1713 {No user interaction allowed.}}
- set aeparse::errors(-1714) {AppleEvent -1714 {Wrong keyword for a special function.}}
- set aeparse::errors(-1715) {AppleEvent -1715 {Some parameter wasn't understood.}}
- set aeparse::errors(-1716) {AppleEvent -1716 {Unknown AppleEvent address type.}}
- set aeparse::errors(-1717) {AppleEvent -1717 {The handler is not defined.}}
- set aeparse::errors(-1718) {AppleEvent -1718 {Reply has not yet arrived.}}
- set aeparse::errors(-1719) {AppleEvent -1719 {Can't get <reference>. Invalid index.}}
- set aeparse::errors(-1720) {AppleEvent -1720 {Invalid range.}}
- set aeparse::errors(-1721) {AppleEvent -1721 {<expression> doesn't match the parameters <parameterNames> for <commandName>.}}
- set aeparse::errors(-1723) {AppleEvent -1723 {Can't get <expression>. Access not allowed.}}
- set aeparse::errors(-1725) {AppleEvent -1725 {Illegal logical operator called.}}
- set aeparse::errors(-1726) {AppleEvent -1726 {Illegal comparison or logical.}}
- set aeparse::errors(-1727) {AppleEvent -1727 {Expected a reference.}}
- set aeparse::errors(-1728) {AppleEvent -1728 {Can't get <reference>.}}
- set aeparse::errors(-1729) {AppleEvent -1729 {Object counting procedure returned a negative count.}}
- set aeparse::errors(-1730) {AppleEvent -1730 {Container specified was an empty list.}}
- set aeparse::errors(-1731) {AppleEvent -1731 {Unknown object type.}}
- set aeparse::errors(-1750) {AppleEvent -1750 {Scripting component error.}}
- set aeparse::errors(-1751) {AppleEvent -1751 {Invalid script id.}}
- set aeparse::errors(-1752) {AppleEvent -1752 {Script doesn't seem to belong to AppleScript.}}
- set aeparse::errors(-1753) {AppleEvent -1753 {Script error.}}
- set aeparse::errors(-1754) {AppleEvent -1754 {Invalid selector given.}}
- set aeparse::errors(-1755) {AppleEvent -1755 {Invalid access.}}
- set aeparse::errors(-1756) {AppleEvent -1756 {Source not available.}}
- set aeparse::errors(-1757) {AppleEvent -1757 {No such dialect.}}
- set aeparse::errors(-1758) {AppleEvent -1758 {Data couldn't be read because its format is obsolete.}}
- set aeparse::errors(-1759) {AppleEvent -1759 {Data couldn't be read because its format is too new.}}
- set aeparse::errors(-1760) {AppleEvent -1760 {Recording is already on.}}
-
- # AppleEvent Registry Errors
-
- set aeparse::errors(-10000) {AERegistry -10000 {AppleEvent handler failed.}}
- set aeparse::errors(-10001) {AERegistry -10001 {A descriptor type mismatch occurred.}}
- set aeparse::errors(-10002) {AERegistry -10002 {Invalid key form.}}
- set aeparse::errors(-10003) {AERegistry -10003 {Can't set <object or data> to <object or data>. Access not allowed.}}
- set aeparse::errors(-10004) {AERegistry -10004 {A privilege violation occurred.}}
- set aeparse::errors(-10005) {AERegistry -10005 {The read operation wasn't allowed.}}
- set aeparse::errors(-10006) {AERegistry -10006 {Can't set <object or data> to <object or data>.}}
- set aeparse::errors(-10007) {AERegistry -10007 {The index of the event is too large to be valid.}}
- set aeparse::errors(-10008) {AERegistry -10008 {The specified object is a property, not an element.}}
- set aeparse::errors(-10009) {AERegistry -10009 {Can't supply the requested descriptor type for the data.}}
- set aeparse::errors(-10010) {AERegistry -10010 {The AppleEvent handler can't handle objects of this class.}}
- set aeparse::errors(-10011) {AERegistry -10011 {Couldn't handle this command because it wasn't part of the current transaction.}}
- set aeparse::errors(-10012) {AERegistry -10012 {The transaction to which this command belonged isn't a valid transaction.}}
- set aeparse::errors(-10013) {AERegistry -10013 {There is no user selection.}}
- set aeparse::errors(-10014) {AERegistry -10014 {Handler only handles single objects.}}
- set aeparse::errors(-10015) {AERegistry -10015 {Can't undo the previous AppleEvent or user action.}}
-
- # ◊◊◊◊ Grammar Rules ◊◊◊◊ #
-
- ##
- # ident ::= identchar (identchar | digit)* —Padded/truncated
- # ' character* ' to exactly 4 chars
- ##
- proc aeparse::ident {chrs} {
- upvar $chrs chars
-
- set identchar {[^][(){} \r\t\n0-9'“”«»:,@]}
- if {![regexp "^\\s*(${identchar}(${identchar}|\[0-9\])*)(.*)" $chars blah type blah chars]} {
- if {![regexp "^\\s*'(\[^'\]*)'(.*)" $chars blah type chars]} {
- error "no ident" "" {AEParse "no ident"}
- }
- }
- return [string range [format "%-4s" $type] 0 3]
- }
-
- ##
- # event ::= ident '\' ident keywordlist
- #
- # NOTE: This is the only parsing routine in this package
- # which takes a string as an argument and, thus, can
- # have the output of ‘AEBuild -r’ piped into it.
- ##
- proc aeparse::event {chars args} {
- global aecoerce::overrides aecoerce::noCoerce
-
- set opts(-all) 0
- set opts(-coerce) {}
- set opts(-noCoerce) {}
-
- getOpts {coerce noCoerce}
-
- # this call to aeparse::event is potentially
- # called by a coercion from an outer call.
- # alis -> TEXT is an example.
- catch {set savedOverrides ${aecoerce::overrides}}
- catch {set savedNoCoerce ${aecoerce::noCoerce}}
-
- set aecoerce::overrides $opts(-coerce)
- set aecoerce::noCoerce $opts(-noCoerce)
-
- if {[regexp {^([^\\]*)\\(.*)$} $chars blah class chars]} {
-
- # Make sure $class is formatted correctly
- set class [aeparse::ident class]
- set event [aeparse::ident chars]
-
- set parameters [aeparse::structure chars]
-
- aeparse::ERROR $parameters
-
- if {[string length [string trimleft $chars]] != 0} {
- set errorMsg "Unexpected extra stuff past end"
- error $errorMsg "" [list AEParse 3 $errorMsg]
- }
-
- if {$opts(-all)} {
- return [list $class $event $parameters]
- } else {
- return $parameters
- }
- } else {
- set errorMsg "Unexpected end of format string"
- error $errorMsg "" [list AEParse 2 $errorMsg]
- }
-
- catch {set aecoerce::overrides $savedOverrides}
- catch {set aecoerce::noCoerce $savedNoCoerce}
- }
-
- ##
- # obj ::= data —Single AEDesc; shortcut for (data)
- # structure —Un-coerced structure
- # ident structure —Coerced to some other type
- ##
- proc aeparse::obj {chrs} {
- upvar $chrs chars
-
- global errorCode errorMsg
-
- if {[catch {set result [aeparse::data chars]} errorMsg]} {
- if {$errorMsg == "no data"} {
- set result [aeparse::structure chars]
- } else {
- error::rethrow
- }
- } else {
- if {[lindex $result 0] == "type"} {
- set type [lindex $result 1]
- if {[catch {set data [aeparse::structure chars]} errorMsg]} {
- if {$errorMsg == "no structure"} {
- # had form 'type'('data') so attempt to coerce
- # 'data' to 'type'.
- if {[catch {set data [aecoerce::apply $result $type]} errorMsg]} {
- if {[string match {AECoerce 1700 *} $errorCode]} {
- # no coercion available
- set data $type
- set type "type"
- } else {
- error::rethrow
- }
- }
- } else {
- error::rethrow
- }
- } else {
- if {[catch {set data [aecoerce::apply $data $type]} errorMsg]} {
- if {![string match {AECoerce 1700 *} $errorCode]} {
- error::rethrow
- }
- }
- }
- set result [list $type $data]
- }
- }
- return $result
- }
-
- ##
- # structure ::= ( data ) —Single AEDesc
- # [ objectlist ] —AEList type
- # { keywordlist } —AERecord type
- ##
- proc aeparse::structure {chrs} {
- global errorMsg
-
- upvar $chrs chars
-
- if {[regexp {^\s*\((.*)} $chars blah chars]} {
- if {[catch {set result [aeparse::data chars]} errorMsg]} {
- if {$errorMsg == "no data"} {
- if {[regexp {^\s*\)(.*)} $chars blah chars]} {
- set result [list "null" ""]
- } else {
- set msg "Missing “)” after data value"
- error $msg "" [list AEParse 13 $msg]
- }
- } else {
- error::rethrow
- }
- } else {
- if {![regexp {^\s*\)(.*)} $chars blah chars]} {
- set msg "Missing “)” after data value"
- error $msg "" [list AEParse 13 $msg]
- }
- }
- } elseif {[catch {set result [aeparse::objectlist chars]} errorMsg]} {
- if {$errorMsg == "no list"} {
- if {[catch {set result [aeparse::reco chars]} errorMsg]} {
- if {$errorMsg == "no reco"} {
- error "no structure"
- } else {
- error::rethrow
- }
- }
- } else {
- error::rethrow
- }
- }
-
- return $result
- }
-
- ##
- # list ::= [ objectlist ]
- # objectlist ::= «blank» —Comma-separated list of things
- # obj [ , obj ]*
- #
- # NOTE: proc is named 'objectlist' to avoid namespace collision
- # and because the distinction is irrelevant here.
- # aeparse::objectlist expects to find the [ ] brackets.
- ##
- proc aeparse::objectlist {chrs} {
- upvar $chrs chars
-
- # set chars [string trimleft $chars]
- set result ""
- if {[regexp {^\s*\[(.*)} $chars blah chars]} {
- if {} $chars blah chars]} {
- while 1 {
- lappend result [aeparse::obj chars]
- regexp {^\s*(.)(.*)} $chars blah next chars
- if {$next == "\]"} {
- break
- } elseif {$next != ","} {
- set msg "Expected “,” or “\]”"
- error $msg "" [list AEParse 14 $msg]
- }
- }
- }
- set result [list "list" $result]
- } else {
- error "no list" "" {AEParse "no list"}
- }
- return $result
- }
-
- ##
- # keywordpair ::= ident : obj —Keyword/value pair
- ##
- proc aeparse::keywordpair {chrs} {
- global errorMsg
-
- upvar $chrs chars
-
- if {[catch {set keyword [aeparse::ident chars]} errorMsg]} {
- if {$errorMsg == "no ident"} {
- set msg "Missing keyword in record"
- error $msg "" [list AEParse 16 $msg]
- } else {
- error::rethrow
- }
- } else {
- if {[regexp {^\s*:(.*)} $chars blah chars]} {
- set value [aeparse::obj chars]
- set result [list $keyword $value]
- } else {
- set msg "Missing “:” after keyword in record"
- error $msg "" [list AEParse 17 $msg]
- }
- }
-
- return $result
- }
-
- ##
- # record ::= { keywordlist }
- # keywordlist ::= «blank» —List of said pairs
- # keywordpair [ , keywordpair ]*
- ##
- proc aeparse::reco {chrs} {
- upvar $chrs chars
-
- set result ""
- if {[regexp {^\s*\{(.*)} $chars blah chars]} {
- if {![regexp {^\s*\}(.*)} $chars blah chars]} {
- while 1 {
- lappend result [aeparse::keywordpair chars]
- regexp {^\s*(.)(.*)} $chars blah next chars
- if {$next == "\}"} {
- break
- } elseif {$next != ","} {
- set msg "Expected “,” or “\}”"
- error $msg "" [list AEParse 15 $msg]
- }
- }
- }
- set result [list "reco" $result]
- } else {
- error "no reco" "" {AEParse "no reco"}
- }
- return $result
- }
-
- ##
- # integer ::= [ - ] digit+ —Just as in C
- # string ::= “ (character)* ”
- # hexstring ::= « (hexdigit | whitespace)* » —Even no. of digits, please
- # data ::= @ —Gets appropriate data from fn param
- # integer —'shor' or 'long' unless coerced
- # ident —A 4-char type code ('type') unless coerced
- # string —Unterminated text; 'TEXT' type unless coerced
- # hexstring —Raw hex data; must be coerced to some type!
- ##
- proc aeparse::data {chrs} {
- global errorMsg
-
- upvar $chrs chars
-
- if {[regexp {^\s*@(.*)} $chars blah chars]} {
- set result [list "@" "@"]
- } elseif {[regexp {^\s*(-?[0-9]+)(.*)$} $chars blah long chars]} {
- # long or short is arbitrary for Alpha
- set result [list "long" $long]
- } elseif {[regexp {^\s*“([^”]*)”(.*)} $chars blah TEXT chars]} {
- set result [list "TEXT" $TEXT]
- } elseif {[regexp {^\s*«([0-9a-fA-F \r\t\n]*)»(.*)$} $chars blah hexd chars]} {
- set result [list "hexd" $hexd]
- } elseif {[catch {set result [list "type" [aeparse::ident chars]]} errorMsg]} {
- if {$errorMsg == "no ident"} {
- error "no data" "" {AEParse "no data"}
- } else {
- error::rethrow
- }
- }
- return $result
- }
-
- # ◊◊◊◊ Utilities ◊◊◊◊ #
-
- ##
- # -------------------------------------------------------------------------
- #
- # "aeparse::ERROR" --
- #
- # Look for error keys in 'event' and, if they exist, throw them
- # -------------------------------------------------------------------------
- ##
- proc aeparse::ERROR {event} {
- global aeparse::errors errorCode
-
- set errn 0
- set errs ""
-
- # No error for missing keywords. Rethrow everything else.
-
- if {[catch {set errn [aeparse::keywordValue "errn" $event]}]} {
- if {![string match {AEParse 16 *} $errorCode]} {
- error::rethrow
- }
- }
-
- if {[catch {set errs [aeparse::keywordValue "errs" $event]}]} {
- if {![string match {AEParse 16 *} $errorCode]} {
- error::rethrow
- }
- }
-
- if {[info exists aeparse::errors($errn)]} {
- if {[string length $errs] == 0} {
- set errs [lindex [set aeparse::errors($errn)] 2]
- }
- set errn [set aeparse::errors($errn)]
- }
-
- if {(([string length $errn] != 0) && ($errn != 0))
- || ([string length $errs] != 0)} {
- error $errs "" $errn
- }
- }
-
- ##
- # -------------------------------------------------------------------------
- #
- # "aeparse::keywordValue" --
- #
- # Return the value associated with $keyword in the parsed list
- # $keywordpairs
- # -------------------------------------------------------------------------
- ##
- proc aeparse::keywordValue {keyword record {typed 0}} {
- set keywordpairs [lindex $record 1]
-
- # Strip user supplied '' quotes, if any
- regexp "^'(.*)'$" $keyword blah keyword
- set keyword [format "%-4s" [string range $keyword 0 3]]
-
- # ??? Need to protect any special characters in $keyword
- if {[set i [lsearch -glob $keywordpairs [list $keyword *]]] >= 0} {
- set keywordpair [lindex $keywordpairs $i]
- if {$typed} {
- return [lindex $keywordpair 1]
- } else {
- return [aeparse::stripType [lindex $keywordpair 1]]
- }
- }
- set msg "Missing keyword '${keyword}' in record"
- error $msg "" [list AEParse 16 $msg]
- }
-
- proc aeparse::stripType {typeValue} {
- set result ""
-
- switch [lindex $typeValue 0] {
- "list" {
- foreach item [lindex $typeValue 1] {
- lappend result [aeparse::stripType $item]
- }
- }
- "reco" {
- # leave it alone, so that aeparse::keywordValue
- # can be used on it.
- }
- default {
- set result [lindex $typeValue 1]
- }
- }
- return $result
- }
-