More Sieve parser improvements after doing 'if not' test

This commit is contained in:
djmaze 2021-01-14 16:38:26 +01:00
parent 2786d0d959
commit 0aafce4214
4 changed files with 153 additions and 130 deletions

View file

@ -4,6 +4,8 @@
(Sieve => { (Sieve => {
const Grammar = Sieve.Grammar, Command = Grammar.Command;
/** /**
* https://tools.ietf.org/html/rfc5228#section-3.1 * https://tools.ietf.org/html/rfc5228#section-3.1
* Usage: * Usage:
@ -11,23 +13,19 @@
* elsif <test2: test> <block2: block> * elsif <test2: test> <block2: block>
* else <block3: block> * else <block3: block>
*/ */
class Conditional class Conditional extends Command
{ {
constructor(identifier = 'if') constructor(identifier = 'if')
{ {
this.identifier = identifier; super(identifier);
this.test = null, this.test = null;
this.commands = [];
} }
toString() toString()
{ {
return this.identifier return this.identifier
+ ('else' !== this.identifier ? ' ' + this.test : '') + ('else' !== this.identifier ? ' ' + this.test : '')
+ (this.commands.length + ' ' + this.commands;
? ' {\r\n\t' + Sieve.arrayToString(this.commands, ';\r\n\t') + ';\r\n}'
: ';'
);
} }
/* /*
public function pushArguments(array $args): void public function pushArguments(array $args): void
@ -41,23 +39,24 @@ class Conditional
/** /**
* https://tools.ietf.org/html/rfc5228#section-3.2 * https://tools.ietf.org/html/rfc5228#section-3.2
*/ */
class Require /* extends Array*/ class Require extends Command
{ {
constructor() constructor()
{ {
this.capabilities = new Sieve.Grammar.StringList(); super('require');
this.capabilities = new Grammar.StringList();
} }
toString() toString()
{ {
return 'require ' + this.capabilities.toString(); return 'require ' + this.capabilities.toString() + ';';
} }
pushArguments(args) pushArguments(args)
{ {
if (args[0] instanceof Sieve.Grammar.StringList) { if (args[0] instanceof Grammar.StringList) {
this.capabilities = args[0]; this.capabilities = args[0];
} else if (args[0] instanceof Sieve.Grammar.QuotedString) { } else if (args[0] instanceof Grammar.QuotedString) {
this.capabilities.push(args[0]); this.capabilities.push(args[0]);
} }
} }
@ -66,30 +65,36 @@ class Require /* extends Array*/
/** /**
* https://tools.ietf.org/html/rfc5228#section-3.3 * https://tools.ietf.org/html/rfc5228#section-3.3
*/ */
class Stop class Stop extends Command
{ {
constructor()
{
super('stop');
}
toString() toString()
{ {
return 'stop'; return 'stop;';
} }
} }
/** /**
* https://tools.ietf.org/html/rfc5228#section-4.1 * https://tools.ietf.org/html/rfc5228#section-4.1
*/ */
class FileInto class FileInto extends Command
{ {
// const REQUIRE = 'fileinto'; // const REQUIRE = 'fileinto';
constructor() constructor()
{ {
super('fileinto');
// QuotedString / MultiLine // QuotedString / MultiLine
this._mailbox = new Sieve.Grammar.QuotedString(); this._mailbox = new Grammar.QuotedString();
} }
toString() toString()
{ {
return 'fileinto ' + this._mailbox; return 'fileinto ' + this._mailbox + ';';
} }
get mailbox() get mailbox()
@ -104,7 +109,7 @@ class FileInto
pushArguments(args) pushArguments(args)
{ {
if (args[0] instanceof Sieve.Grammar.StringType) { if (args[0] instanceof Grammar.StringType) {
this._mailbox = args[0]; this._mailbox = args[0];
} }
} }
@ -113,17 +118,18 @@ class FileInto
/** /**
* https://tools.ietf.org/html/rfc5228#section-4.2 * https://tools.ietf.org/html/rfc5228#section-4.2
*/ */
class Redirect class Redirect extends Command
{ {
constructor() constructor()
{ {
super('redirect');
// QuotedString / MultiLine // QuotedString / MultiLine
this._address = new Sieve.Grammar.QuotedString(); this._address = new Grammar.QuotedString();
} }
toString() toString()
{ {
return 'redirect ' + this.address; return 'redirect ' + this.address + ';';
} }
get address() get address()
@ -140,22 +146,32 @@ class Redirect
/** /**
* https://tools.ietf.org/html/rfc5228#section-4.3 * https://tools.ietf.org/html/rfc5228#section-4.3
*/ */
class Keep class Keep extends Command
{ {
constructor()
{
super('keep');
}
toString() toString()
{ {
return 'keep'; return 'keep;';
} }
} }
/** /**
* https://tools.ietf.org/html/rfc5228#section-4.4 * https://tools.ietf.org/html/rfc5228#section-4.4
*/ */
class Discard class Discard extends Command
{ {
constructor()
{
super('discard');
}
toString() toString()
{ {
return 'discard'; return 'discard;';
} }
} }

View file

@ -49,34 +49,24 @@ class Comment extends StringType
*/ */
class Command class Command
{ {
constructor(identifier, text = null) constructor(identifier)
{ {
this.identifier = identifier; this.identifier = identifier;
this.arguments = []; this.arguments = [];
if (text) { this.commands = new Commands;
this.text = text;
}
} }
toString() toString()
{ {
let result = this.identifier; let result = this.identifier;
if ('anyof' === result || 'allof' === result) { if (this.arguments.length) {
result += ' (\r\n\t' + Sieve.arrayToString(this.commands, ',\r\n\t') + '\r\n)'; result += Sieve.arrayToString(this.arguments, ' ');
} else {
this.arguments.forEach(arg => {
if (Array.isArray(arg)) {
result += ' [' + arg.join(',') + ']';
} else {
result += ' ' + arg;
} }
}); if (this.commands.length) {
if (this.commands) { result += ' ' + this.commands.toString();
result += ' {\r\n\t' + Sieve.arrayToString(this.commands, ';\r\n\t') + '\r\n}';
} else { } else {
result += ';'; result += ';';
} }
}
return result; return result;
} }
@ -96,24 +86,22 @@ class Command
} }
} }
/*
class Commands extends Array class Commands extends Array
{ {
toString() toString()
{ {
return this.length return this.length
? '{\r\n\t' + Sieve.arrayToString(this, ';\r\n\t') + ';\r\n}' ? '{\r\n\t' + Sieve.arrayToString(this, '\r\n\t') + '\r\n}'
: ';'; : '{}';
} }
push(value) push(value)
{ {
if (value instanceof Command) { if (value instanceof Command || value instanceof Comment) {
super.push(value); super.push(value);
} }
} }
} }
*/
class BracketComment extends Comment class BracketComment extends Comment
{ {
@ -223,7 +211,7 @@ class MultiLine extends StringType
/** /**
* https://tools.ietf.org/html/rfc5228#section-2.9 * https://tools.ietf.org/html/rfc5228#section-5
*/ */
class Test class Test
{ {
@ -250,15 +238,15 @@ class Test
*/ */
class TestList extends Array class TestList extends Array
{ {
constructor(identifier) constructor()
{ {
super(); super();
this.identifier = identifier; // allof / anyof
} }
toString() toString()
{ {
if (1 < this.length) { if (1 < this.length) {
// return '(\r\n\t' + Sieve.arrayToString(this, ',\r\n\t') + '\r\n)';
return '(' + this.join(', ') + ')'; return '(' + this.join(', ') + ')';
} }
return this.length ? this[0] : ''; return this.length ? this[0] : '';
@ -266,34 +254,13 @@ class TestList extends Array
push(value) push(value)
{ {
/* if (!(value instanceof Test)) {
if (!(value instanceof Command)) { throw 'Not an instanceof Test';
value = new Command($value);
} }
*/
super.push(value); super.push(value);
} }
} }
TestList.fromString = list => {
let test,
obj = new TestList,
regex = RegExp('(?:^\\s*|\\s*,\\s*)(' + RegEx.IDENTIFIER + ')((?:\\s+' + RegEx.ARGUMENT + ')*)', 'g');
list = list.replace(/^[\r\n\t(]+/, '');
while ((test = regex.exec(list))) {
let command = new Sieve.Commands.Command(test[1]);
obj.push(command);
/*
if (\preg_match_all('@\\s+(' . RegEx.ARGUMENT . ')@', $test[2], $args, PREG_SET_ORDER)) {
foreach ($args as $arg) {
$command->arguments[] = $arg;
}
}
*/
}
return obj;
}
Sieve.Grammar = { Sieve.Grammar = {
Command: Command, Command: Command,
BracketComment: BracketComment, BracketComment: BracketComment,

View file

@ -106,31 +106,32 @@ Sieve.parseScript = script => {
// new_command = new Sieve.Grammar.Command(value); // new_command = new Sieve.Grammar.Command(value);
} }
if (command instanceof Sieve.Commands.Conditional && !command.test) { if (new_command instanceof Sieve.Grammar.Test) {
if (new_command instanceof Sieve.Grammar.TestList) { if (command instanceof Sieve.Commands.Conditional || command instanceof Sieve.Tests.Not) {
levels.push(new_command); // if/elsif/else new_command
} else { // not new_command
new_command = new Sieve.Grammar.Test(value);
}
command.test = new_command; command.test = new_command;
} else if (command.tests instanceof Sieve.Grammar.TestList) {
// allof/anyof .tests[] new_command
command.tests.push(new_command);
} else { } else {
if (levels.length) { error('Test not allowed here');
let cmd = levels.last(); }
if (cmd instanceof Sieve.Grammar.TestList) { } else if (command) {
cmd.push(new_command); if (command.commands) {
command.commands.push(new_command);
} else { } else {
cmd.commands.push(new_command); console.dir(command);
error('commands not allowed');
} }
} else { } else {
tree.push(new_command); tree.push(new_command);
} }
if (new_command instanceof Sieve.Commands.Conditional || new_command instanceof Sieve.Grammar.TestList) {
levels.push(new_command); levels.push(new_command);
}
}
command = new_command; command = new_command;
break; } break; }
// Arguments
case T_TAG: case T_TAG:
command command
? args.push(value.toLowerCase()) ? args.push(value.toLowerCase())
@ -157,6 +158,7 @@ Sieve.parseScript = script => {
: error('Number must be command argument'); : error('Number must be command argument');
break; break;
// Comments
case T_BRACKET_COMMENT: case T_BRACKET_COMMENT:
(command ? command.commands : tree).push( (command ? command.commands : tree).push(
new Sieve.Grammar.BracketComment(value.substr(2, value.length-4)) new Sieve.Grammar.BracketComment(value.substr(2, value.length-4))
@ -174,48 +176,52 @@ Sieve.parseScript = script => {
command || tree.push(value.trim()); command || tree.push(value.trim());
break; break;
// Command end
case T_SEMICOLON: case T_SEMICOLON:
command || error('Semicolon not at end of command'); command || error('Semicolon not at end of command');
pushArgs(); pushArgs();
// levels.pop(); levels.pop();
command = levels.last(); command = levels.last();
break; break;
// Command block
case T_BLOCK_START: case T_BLOCK_START:
pushArgs();
// https://tools.ietf.org/html/rfc5228#section-2.9 // https://tools.ietf.org/html/rfc5228#section-2.9
// Action commands do not take tests or blocks // Action commands do not take tests or blocks
levels.length || error('Block start not part of control command'); while (command && !(command instanceof Sieve.Commands.Conditional)) {
command || error('Block start not at end of command arguments'); levels.pop();
pushArgs();
command = levels.last(); command = levels.last();
}
command || error('Block start not part of control command');
break; break;
case T_BLOCK_END: case T_BLOCK_END:
levels.length || error('Block end has no matching block start'); (command instanceof Sieve.Commands.Conditional) || error('Block end has no matching block start');
levels.pop(); levels.pop();
command = levels.last(); command = levels.last();
break; break;
// anyof / allof // anyof / allof ( ... , ... )
case T_LEFT_PARENTHESIS: case T_LEFT_PARENTHESIS:
(levels.last() instanceof Sieve.Grammar.TestList)
|| error('Test start not part of test-list');
command || error('Not inside command');
pushArgs(); pushArgs();
while (command && !(command.tests instanceof Sieve.Grammar.TestList)) {
levels.pop();
command = levels.last(); command = levels.last();
}
command || error('Test start not part of anyof/allof test');
break; break;
case T_RIGHT_PARENTHESIS: case T_RIGHT_PARENTHESIS:
(levels.last() instanceof Sieve.Grammar.TestList)
|| error('Test end not part of test-list');
pushArgs(); pushArgs();
levels.pop(); levels.pop();
command = levels.last(); command = levels.last();
(command.tests instanceof Sieve.Grammar.TestList) || error('Test end not part of test-list');
break; break;
case T_COMMA: case T_COMMA:
// Must be inside PARENTHESIS aka test-list
(levels.last() instanceof Sieve.Grammar.TestList)
|| error('Comma not part of test-list');
pushArgs(); pushArgs();
levels.pop();
command = levels.last(); command = levels.last();
// Must be inside PARENTHESIS aka test-list
(command.tests instanceof Sieve.Grammar.TestList) || error('Comma not part of test-list');
break; break;
case T_UNKNOWN: case T_UNKNOWN:

View file

@ -4,18 +4,23 @@
(Sieve => { (Sieve => {
const Grammar = Sieve.Grammar,
Test = Grammar.Test,
StringList = Grammar.StringList;
/** /**
* https://tools.ietf.org/html/rfc5228#section-5.1 * https://tools.ietf.org/html/rfc5228#section-5.1
*/ */
class Address class Address extends Test
{ {
constructor() constructor()
{ {
super('address');
this.comparator = 'i;ascii-casemap'; this.comparator = 'i;ascii-casemap';
this.address_part = ':all'; // :localpart | :domain | :all this.address_part = ':all'; // :localpart | :domain | :all
this.match_type = ':is'; this.match_type = ':is';
this.header_list = new Sieve.Grammar.StringList; this.header_list = new StringList;
this.key_list = new Sieve.Grammar.StringList; this.key_list = new StringList;
} }
toString() toString()
@ -37,37 +42,50 @@ class Address
/** /**
* https://tools.ietf.org/html/rfc5228#section-5.2 * https://tools.ietf.org/html/rfc5228#section-5.2
*/ */
class AllOf extends Sieve.Grammar.TestList class AllOf extends Test
{ {
constructor() constructor()
{ {
super('allof'); super('allof');
this.tests = new Grammar.TestList;
}
toString()
{
return 'allof' + this.tests;
} }
} }
/** /**
* https://tools.ietf.org/html/rfc5228#section-5.3 * https://tools.ietf.org/html/rfc5228#section-5.3
*/ */
class AnyOf extends Sieve.Grammar.TestList class AnyOf extends Test
{ {
constructor() constructor()
{ {
super('anyof'); super('anyof');
this.tests = new Grammar.TestList;
}
toString()
{
return 'anyof' + this.tests;
} }
} }
/** /**
* https://tools.ietf.org/html/rfc5228#section-5.4 * https://tools.ietf.org/html/rfc5228#section-5.4
*/ */
class Envelope class Envelope extends Test
{ {
constructor() constructor()
{ {
super('envelope');
this.comparator = 'i;ascii-casemap'; this.comparator = 'i;ascii-casemap';
this.address_part = ':all'; // :localpart | :domain | :all this.address_part = ':all'; // :localpart | :domain | :all
this.match_type = ':is'; this.match_type = ':is';
this.envelope_part = new Sieve.Grammar.StringList; this.envelope_part = new StringList;
this.key_list = new Sieve.Grammar.StringList; this.key_list = new StringList;
} }
toString() toString()
@ -89,16 +107,17 @@ class Envelope
/** /**
* https://tools.ietf.org/html/rfc5228#section-5.5 * https://tools.ietf.org/html/rfc5228#section-5.5
*/ */
class Exists class Exists extends Test
{ {
constructor() constructor()
{ {
this.header_names = new Sieve.Grammar.StringList; super('exists');
this.header_names = new StringList;
} }
toString() toString()
{ {
return "exists {this.header_names}"; return 'exists ' + this.header_names;
} }
pushArguments(/*args*/) pushArguments(/*args*/)
@ -110,7 +129,7 @@ class Exists
/** /**
* https://tools.ietf.org/html/rfc5228#section-5.6 * https://tools.ietf.org/html/rfc5228#section-5.6
*/ */
class False class False extends Test
{ {
toString() toString()
{ {
@ -121,15 +140,16 @@ class False
/** /**
* https://tools.ietf.org/html/rfc5228#section-5.7 * https://tools.ietf.org/html/rfc5228#section-5.7
*/ */
class Header class Header extends Test
{ {
constructor() constructor()
{ {
super('header');
this.comparator = 'i;ascii-casemap'; this.comparator = 'i;ascii-casemap';
this.address_part = ':all'; // :localpart | :domain | :all this.address_part = ':all'; // :localpart | :domain | :all
this.match_type = ':is'; this.match_type = ':is';
this.header_names = new Sieve.Grammar.StringList; this.header_names = new StringList;
this.key_list = new Sieve.Grammar.StringList; this.key_list = new StringList;
} }
toString() toString()
@ -146,7 +166,7 @@ class Header
args.forEach((arg, i) => { args.forEach((arg, i) => {
if (':is' === arg || ':contains' === arg || ':matches' === arg) { if (':is' === arg || ':contains' === arg || ':matches' === arg) {
this.match_type = arg; this.match_type = arg;
} else if (arg instanceof Sieve.Grammar.StringList || arg instanceof Sieve.Grammar.StringType) { } else if (arg instanceof StringList || arg instanceof Grammar.StringType) {
this[args[i+1] ? 'header_names' : 'key_list'] = arg; this[args[i+1] ? 'header_names' : 'key_list'] = arg;
// (args[i+1] ? this.header_names : this.key_list) = arg; // (args[i+1] ? this.header_names : this.key_list) = arg;
} }
@ -157,11 +177,12 @@ class Header
/** /**
* https://tools.ietf.org/html/rfc5228#section-5.8 * https://tools.ietf.org/html/rfc5228#section-5.8
*/ */
class Not class Not extends Test
{ {
constructor() constructor()
{ {
this.test = new Sieve.Grammar.Test; super('not');
this.test = new Test;
} }
toString() toString()
@ -178,10 +199,11 @@ class Not
/** /**
* https://tools.ietf.org/html/rfc5228#section-5.9 * https://tools.ietf.org/html/rfc5228#section-5.9
*/ */
class Size class Size extends Test
{ {
constructor() constructor()
{ {
super('size');
this.mode = ':over'; // :under this.mode = ':over'; // :under
this.limit = 0; this.limit = 0;
} }
@ -196,13 +218,24 @@ class Size
args.forEach(arg => { args.forEach(arg => {
if (':over' === arg || ':under' === arg) { if (':over' === arg || ':under' === arg) {
this.mode = arg; this.mode = arg;
} else if (arg instanceof Sieve.Grammar.Number) { } else if (arg instanceof Grammar.Number) {
this.limit = arg; this.limit = arg;
} }
}); });
} }
} }
/**
* https://tools.ietf.org/html/rfc5228#section-5.10
*/
class True extends Test
{
toString()
{
return 'true';
}
}
Sieve.Tests = { Sieve.Tests = {
Address: Address, Address: Address,
AllOf: AllOf, AllOf: AllOf,
@ -212,7 +245,8 @@ Sieve.Tests = {
False: False, False: False,
Header: Header, Header: Header,
Not: Not, Not: Not,
Size: Size Size: Size,
True: True
}; };
})(this.Sieve); })(this.Sieve);