parser.y
416 lines
| 9.8 KiB
| text/plain
|
TextLexer
|
r9346 | # | ||
# parser.y | ||||
# | ||||
# Copyright (c) 1998-2007 Minero Aoki | ||||
# | ||||
# This program is free software. | ||||
# You can distribute/modify this program under the terms of | ||||
# the GNU Lesser General Public License version 2.1. | ||||
# | ||||
class TMail::Parser | ||||
options no_result_var | ||||
rule | ||||
content : DATETIME datetime { val[1] } | ||||
| RECEIVED received { val[1] } | ||||
| MADDRESS addrs_TOP { val[1] } | ||||
| RETPATH retpath { val[1] } | ||||
| KEYWORDS keys { val[1] } | ||||
| ENCRYPTED enc { val[1] } | ||||
| MIMEVERSION version { val[1] } | ||||
| CTYPE ctype { val[1] } | ||||
| CENCODING cencode { val[1] } | ||||
| CDISPOSITION cdisp { val[1] } | ||||
| ADDRESS addr_TOP { val[1] } | ||||
| MAILBOX mbox { val[1] } | ||||
datetime : day DIGIT ATOM DIGIT hour zone | ||||
# 0 1 2 3 4 5 | ||||
# date month year | ||||
{ | ||||
t = Time.gm(val[3].to_i, val[2], val[1].to_i, 0, 0, 0) | ||||
(t + val[4] - val[5]).localtime | ||||
} | ||||
day : /* none */ | ||||
| ATOM ',' | ||||
hour : DIGIT ':' DIGIT | ||||
{ | ||||
(val[0].to_i * 60 * 60) + | ||||
(val[2].to_i * 60) | ||||
} | ||||
| DIGIT ':' DIGIT ':' DIGIT | ||||
{ | ||||
(val[0].to_i * 60 * 60) + | ||||
(val[2].to_i * 60) + | ||||
(val[4].to_i) | ||||
} | ||||
zone : ATOM | ||||
{ | ||||
timezone_string_to_unixtime(val[0]) | ||||
} | ||||
received : from by via with id for received_datetime | ||||
{ | ||||
val | ||||
} | ||||
from : /* none */ | ||||
| FROM received_domain | ||||
{ | ||||
val[1] | ||||
} | ||||
by : /* none */ | ||||
| BY received_domain | ||||
{ | ||||
val[1] | ||||
} | ||||
received_domain | ||||
: domain | ||||
{ | ||||
join_domain(val[0]) | ||||
} | ||||
| domain '@' domain | ||||
{ | ||||
join_domain(val[2]) | ||||
} | ||||
| domain DOMLIT | ||||
{ | ||||
join_domain(val[0]) | ||||
} | ||||
via : /* none */ | ||||
| VIA ATOM | ||||
{ | ||||
val[1] | ||||
} | ||||
with : /* none */ | ||||
{ | ||||
[] | ||||
} | ||||
| with WITH ATOM | ||||
{ | ||||
val[0].push val[2] | ||||
val[0] | ||||
} | ||||
id : /* none */ | ||||
| ID msgid | ||||
{ | ||||
val[1] | ||||
} | ||||
| ID ATOM | ||||
{ | ||||
val[1] | ||||
} | ||||
for : /* none */ | ||||
| FOR received_addrspec | ||||
{ | ||||
val[1] | ||||
} | ||||
received_addrspec | ||||
: routeaddr | ||||
{ | ||||
val[0].spec | ||||
} | ||||
| spec | ||||
{ | ||||
val[0].spec | ||||
} | ||||
received_datetime | ||||
: /* none */ | ||||
| ';' datetime | ||||
{ | ||||
val[1] | ||||
} | ||||
addrs_TOP : addrs | ||||
| group_bare | ||||
| addrs commas group_bare | ||||
addr_TOP : mbox | ||||
| group | ||||
| group_bare | ||||
retpath : addrs_TOP | ||||
| '<' '>' { [ Address.new(nil, nil) ] } | ||||
addrs : addr | ||||
{ | ||||
val | ||||
} | ||||
| addrs commas addr | ||||
{ | ||||
val[0].push val[2] | ||||
val[0] | ||||
} | ||||
addr : mbox | ||||
| group | ||||
mboxes : mbox | ||||
{ | ||||
val | ||||
} | ||||
| mboxes commas mbox | ||||
{ | ||||
val[0].push val[2] | ||||
val[0] | ||||
} | ||||
mbox : spec | ||||
| routeaddr | ||||
| addr_phrase routeaddr | ||||
{ | ||||
val[1].phrase = Decoder.decode(val[0]) | ||||
val[1] | ||||
} | ||||
group : group_bare ';' | ||||
group_bare: addr_phrase ':' mboxes | ||||
{ | ||||
AddressGroup.new(val[0], val[2]) | ||||
} | ||||
| addr_phrase ':' { AddressGroup.new(val[0], []) } | ||||
addr_phrase | ||||
: local_head { val[0].join('.') } | ||||
| addr_phrase local_head { val[0] << ' ' << val[1].join('.') } | ||||
routeaddr : '<' routes spec '>' | ||||
{ | ||||
val[2].routes.replace val[1] | ||||
val[2] | ||||
} | ||||
| '<' spec '>' | ||||
{ | ||||
val[1] | ||||
} | ||||
routes : at_domains ':' | ||||
at_domains: '@' domain { [ val[1].join('.') ] } | ||||
| at_domains ',' '@' domain { val[0].push val[3].join('.'); val[0] } | ||||
spec : local '@' domain { Address.new( val[0], val[2] ) } | ||||
| local { Address.new( val[0], nil ) } | ||||
local: local_head | ||||
| local_head '.' { val[0].push ''; val[0] } | ||||
local_head: word | ||||
{ val } | ||||
| local_head dots word | ||||
{ | ||||
val[1].times do | ||||
val[0].push '' | ||||
end | ||||
val[0].push val[2] | ||||
val[0] | ||||
} | ||||
domain : domword | ||||
{ val } | ||||
| domain dots domword | ||||
{ | ||||
val[1].times do | ||||
val[0].push '' | ||||
end | ||||
val[0].push val[2] | ||||
val[0] | ||||
} | ||||
dots : '.' { 0 } | ||||
| dots '.' { val[0] + 1 } | ||||
word : atom | ||||
| QUOTED | ||||
| DIGIT | ||||
domword : atom | ||||
| DOMLIT | ||||
| DIGIT | ||||
commas : ',' | ||||
| commas ',' | ||||
msgid : '<' spec '>' | ||||
{ | ||||
val[1] = val[1].spec | ||||
val.join('') | ||||
} | ||||
keys : phrase { val } | ||||
| keys ',' phrase { val[0].push val[2]; val[0] } | ||||
phrase : word | ||||
| phrase word { val[0] << ' ' << val[1] } | ||||
enc : word | ||||
{ | ||||
val.push nil | ||||
val | ||||
} | ||||
| word word | ||||
{ | ||||
val | ||||
} | ||||
version : DIGIT '.' DIGIT | ||||
{ | ||||
[ val[0].to_i, val[2].to_i ] | ||||
} | ||||
ctype : TOKEN '/' TOKEN params opt_semicolon | ||||
{ | ||||
[ val[0].downcase, val[2].downcase, decode_params(val[3]) ] | ||||
} | ||||
| TOKEN params opt_semicolon | ||||
{ | ||||
[ val[0].downcase, nil, decode_params(val[1]) ] | ||||
} | ||||
params : /* none */ | ||||
{ | ||||
{} | ||||
} | ||||
| params ';' TOKEN '=' QUOTED | ||||
{ | ||||
val[0][ val[2].downcase ] = ('"' + val[4].to_s + '"') | ||||
val[0] | ||||
} | ||||
| params ';' TOKEN '=' TOKEN | ||||
{ | ||||
val[0][ val[2].downcase ] = val[4] | ||||
val[0] | ||||
} | ||||
cencode : TOKEN | ||||
{ | ||||
val[0].downcase | ||||
} | ||||
cdisp : TOKEN params opt_semicolon | ||||
{ | ||||
[ val[0].downcase, decode_params(val[1]) ] | ||||
} | ||||
opt_semicolon | ||||
: | ||||
| ';' | ||||
atom : ATOM | ||||
| FROM | ||||
| BY | ||||
| VIA | ||||
| WITH | ||||
| ID | ||||
| FOR | ||||
end | ||||
---- header | ||||
# | ||||
# parser.rb | ||||
# | ||||
# Copyright (c) 1998-2007 Minero Aoki | ||||
# | ||||
# This program is free software. | ||||
# You can distribute/modify this program under the terms of | ||||
# the GNU Lesser General Public License version 2.1. | ||||
# | ||||
require 'tmail/scanner' | ||||
require 'tmail/utils' | ||||
---- inner | ||||
include TextUtils | ||||
def self.parse( ident, str, cmt = nil ) | ||||
str = special_quote_address(str) if ident.to_s =~ /M?ADDRESS/ | ||||
new.parse(ident, str, cmt) | ||||
end | ||||
def self.special_quote_address(str) #:nodoc: | ||||
# Takes a string which is an address and adds quotation marks to special | ||||
# edge case methods that the RACC parser can not handle. | ||||
# | ||||
# Right now just handles two edge cases: | ||||
# | ||||
# Full stop as the last character of the display name: | ||||
# Mikel L. <mikel@me.com> | ||||
# Returns: | ||||
# "Mikel L." <mikel@me.com> | ||||
# | ||||
# Unquoted @ symbol in the display name: | ||||
# mikel@me.com <mikel@me.com> | ||||
# Returns: | ||||
# "mikel@me.com" <mikel@me.com> | ||||
# | ||||
# Any other address not matching these patterns just gets returned as is. | ||||
case | ||||
# This handles the missing "" in an older version of Apple Mail.app | ||||
# around the display name when the display name contains a '@' | ||||
# like 'mikel@me.com <mikel@me.com>' | ||||
# Just quotes it to: '"mikel@me.com" <mikel@me.com>' | ||||
when str =~ /\A([^"].+@.+[^"])\s(<.*?>)\Z/ | ||||
return "\"#{$1}\" #{$2}" | ||||
# This handles cases where 'Mikel A. <mikel@me.com>' which is a trailing | ||||
# full stop before the address section. Just quotes it to | ||||
# '"Mikel A." <mikel@me.com>' | ||||
when str =~ /\A(.*?\.)\s(<.*?>)\s*\Z/ | ||||
return "\"#{$1}\" #{$2}" | ||||
else | ||||
str | ||||
end | ||||
end | ||||
MAILP_DEBUG = false | ||||
def initialize | ||||
self.debug = MAILP_DEBUG | ||||
end | ||||
def debug=( flag ) | ||||
@yydebug = flag && Racc_debug_parser | ||||
@scanner_debug = flag | ||||
end | ||||
def debug | ||||
@yydebug | ||||
end | ||||
def parse( ident, str, comments = nil ) | ||||
@scanner = Scanner.new(str, ident, comments) | ||||
@scanner.debug = @scanner_debug | ||||
@first = [ident, ident] | ||||
result = yyparse(self, :parse_in) | ||||
comments.map! {|c| to_kcode(c) } if comments | ||||
result | ||||
end | ||||
private | ||||
def parse_in( &block ) | ||||
yield @first | ||||
@scanner.scan(&block) | ||||
end | ||||
def on_error( t, val, vstack ) | ||||
raise TMail::SyntaxError, "parse error on token #{racc_token2str t}" | ||||
end | ||||