Last active
August 29, 2015 14:11
-
-
Save gamefreak/f698ea7cc6b46b3001ff to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
(* | |
ENBF + JavaScript RegEx | |
*) | |
start = ws, expression, ws | |
digit = "0"|"1"|"2"|"3"|"4"|"5"|"6"|"7"|"8"|"9"; | |
integer = "0" | (digit - "0") {digit}; | |
signed integer = "0" | ["-"] (digit - "0") {digit}; | |
whitespace = " "|"\t"|"\r"|"\n"; | |
(* 0 or more *) | |
ws = {whitespace}; | |
(* 1 or more *) | |
WS = whitespace, {whitespace}; | |
name literal = /A-Za-z0-9_/+ | |
(* | |
a sequence of word characters [A-Za-z0-9_] and asterisks. | |
consecutive asterisks are not allowed | |
*) | |
glob = /((\*(?!\*))|\w+)+/ | |
expression = or expression; | |
or expression = and expression {WS, "OR", WS, or expression }; | |
and expression = predicate {WS, "AND", WS, and expression }; | |
predicate = "(", ws, expression, ws, ")" | |
| "NOT", WS, predicate | |
| subreddit name predicate | |
| subreddit glob predicate | |
| score predicate | |
| current day predicate | |
| domain predicate | |
| nsfw predicate | |
| post type predicate | |
| listing type predicate; | |
subreddit keyword = "subreddit" | "sub" | "sr"; | |
subreddit name predicate = subreddit keyword, WS, "IS", WS, "/r/", name literal; | |
subreddit glob predicate = subreddit keyword, WS, "MATCHES", WS, "/r/", glob; | |
score predicate = "score", WS, "IS", WS, ("AT LEAST"|"AT MOST"), WS, signed integer; | |
current day predicate = "today", WS, "IS A", WS, day of week, {ws, ",", ws, day of week}; | |
day of week = "sunday"|"monday"|"tuesday"|"wednesday"|"thursday"|"friday"|"saturday" | |
|"weekday"|"weekend"; | |
domain predicate = "domain", WS, "IS", WS, domain pattern; | |
(* | |
domain name | |
must have at a minimum of 2 parts (such as imgur.com) | |
optionally prefixable with "*." to match all subdomains | |
*) | |
domain pattern = ["*."], domain part, ".", domain part, {"." domain_part} | |
domain part = /[A-Za-z0-9\-]+/; | |
nswf predicate = "is", WS, ["n"]"sfw"; | |
comment count predicate = "post", WS, "HAS", WS, ("AT LEAST" | "AT MOST"), WS, integer, WS, "comments; | |
post type predicate = "is", WS, ("text"|"link"), WS, "post"; | |
listing type predicate = "browsing", WS, "/", ("/r/", glob|"m"|"u"), "/", glob | |
listing type pattern = "/r/", glob | |
| "/u/", glob, ["/m/", glob] | |
| "/me/m/", glob; | |
poster_predicate = "user", WS, "IS", WS, ("friend"|"moderator"|"admin"|"me"|"OP" | "/u/", glob_pattern); | |
*/ | |
{ | |
function getInfo() { | |
return { | |
//subreddit that the post was made to | |
subreddit: function() { | |
return "/r/enhancement"; | |
}, | |
score: function() { | |
return 25; | |
}, | |
now: function() { | |
return new Date(); | |
}, | |
domain: function() { | |
return "i.imgur.com"; | |
}, | |
isNSFW: function() { | |
return false; | |
}, | |
commentCount: function() { | |
return 13; | |
}, | |
postType: function() { | |
//link or text | |
return "link"; | |
}, | |
browsing: function() { | |
return "/r/enhancement" | |
} | |
}; | |
} | |
function define(clazz, iMethods, cMethods) { | |
function makeProps(methods) { | |
if (!methods) return {}; | |
var properties = {}; | |
Object.keys(methods).forEach(function(name) { | |
properties[name] = { | |
value: methods[name], | |
enumerable: false | |
}; | |
}); | |
return properties; | |
} | |
Object.defineProperties(clazz.prototype, makeProps(iMethods)); | |
Object.defineProperties(clazz, makeProps(cMethods)); | |
} | |
var concat = Array.prototype.concat.bind([]); | |
function nestRight(array, constr) { | |
return array.reduceRight(function(right, left) { | |
return new constr(left, right); | |
}) | |
} | |
function Glob(patt) { | |
this.pattern = new RegExp("^"+pattern.replace(/\*/g, "\w*")+"$"); | |
} | |
define(Glob, { | |
test: function(string) { | |
return this.pattern.test(string); | |
}, | |
toString: function() { | |
return this.pattern.toString(); | |
} | |
}) | |
function NOTExpression(expr) { | |
this.expr = expr; | |
} | |
define(NOTExpression, { | |
evaluate: function(itemInfo) { | |
return !this.expr.evaluate(itemInfo); | |
}, | |
toString: function() { | |
return "(NOT "+this.expr+")"; | |
} | |
}); | |
function ANDExpression(left, right) { | |
this.left = left; | |
this.right = right; | |
} | |
define(ANDExpression, { | |
evaluate: function(info) { | |
return this.left.evaluate(info) && this.right.evaluate(info); | |
}, | |
toString: function() { | |
return "("+this.left + " AND " + this.right + ")"; | |
} | |
}); | |
function ORExpression(left, right) { | |
this.left = left; | |
this.right = right; | |
} | |
define(ORExpression, { | |
evaluate: function(info) { | |
return this.left.evaluate(info) || this.right.evaluate(info); | |
}, | |
toString: function() { | |
return "("+this.left + " OR " + this.right + ")"; | |
} | |
}); | |
function SubredditPredicate(name) { | |
this.name = name; | |
} | |
define(SubredditPredicate, { | |
evaluate: function(itemInfo) { | |
return itemInfo.subreddit() === this.name; | |
}, | |
toString: function() { | |
return "(subreddit == " + this.name + ")"; | |
} | |
}); | |
function SubredditMatchPredicate(pattern) { | |
this.pattern = new RegExp("^"+pattern.replace(/\*{2,}/g, ".*")+"$", "i"); | |
} | |
define(SubredditMatchPredicate, { | |
evaluate: function(itemInfo) { | |
return this.pattern.test(itemInfo.subreddit()); | |
}, | |
toString: function() { | |
return "(subreddit IS LIKE " + this.pattern + ")"; | |
} | |
}); | |
function ScorePredicate(op, value) { | |
switch (op) { | |
case "AT LEAST": this.op = ">="; break; | |
case "AT MOST": this.op = "<="; break; | |
default: throw new RangeError("Invalid score comparison"); | |
} | |
this.value = value; | |
} | |
define(ScorePredicate, { | |
evaluate: function(itemInfo) { | |
switch (this.op) { | |
case ">=": return itemInfo.score() >= this.value; | |
case "<=": return itemInfo.score() <= this.value; | |
default: throw new RangeError("Unimplemented operator "+this.op+" for scores"); | |
} | |
}, | |
toString: function() { | |
return "(score " + this.op + " " + this.value + ")"; | |
} | |
}) | |
function CurrentDayPredicate(days) { | |
this.days = days; | |
} | |
CurrentDayPredicate.dayToNumber = function(day) { | |
switch (dow) { | |
case "sunday": return 0; | |
case "monday": return 1; | |
case "tuesday": return 2; | |
case "wednesday": return 3; | |
case "thursday": return 4; | |
case "friday": return 5; | |
case "saturday": return 6; | |
case "weekday": return [1,2,3,4,5]; | |
case "weekend": return [0,6]; | |
} | |
} | |
define(CurrentDayPredicate, { | |
dayNumbers: function() { | |
return this.days.reduce(function(p, c) { | |
return p.concat(CurrentDayPredicate.dayToNumber(c)); | |
}, []); | |
}, | |
evaluate: function(itemInfo) { | |
var dow = itemInfo.now().getDay(); | |
return this.dayNumbers.indexOf(dow) !== -1; | |
}, | |
toString: function() { | |
return "(today IS A " + this.days.join(",") + ")"; | |
} | |
}) | |
//TODO: Finalize matching algorithm | |
function DomainPredicate(pattern) { | |
pattern = pattern.replace(/\./g, "\\."); | |
pattern = pattern.replace(/\*/, ".*"); | |
this.pattern = new RegExp("^"+pattern+"$", "i"); | |
} | |
define(DomainPredicate, { | |
evaluate: function(itemInfo) { | |
return this.pattern.test(itemInfo.domain()); | |
}, | |
toString: function() { | |
return "(domain matches "+this.pattern+")"; | |
} | |
}) | |
function NSFWPredicate(shouldBeNSFW) { | |
this.shouldBeNSFW = shouldBeNSFW; | |
} | |
define(NSFWPredicate, { | |
evaluate: function(itemInfo) { | |
return this.shouldBeNSFW == itemInfo.isNSFW() | |
}, | |
toString: function() { | |
return "(is "+(this.shouldBeNSFW?"NSFW":"SFW")+")"; | |
} | |
}) | |
function CommentCountPredicate(op, count) { | |
switch (op) { | |
case "AT LEAST": this.op = ">="; break; | |
case "AT MOST": this.op = "<="; break; | |
default: throw new RangeError("Invalid comment count comparison"); | |
} | |
this.count = count; | |
} | |
define(CommentCountPredicate, { | |
evaluate: function(itemInfo) { | |
switch (this.op) { | |
case ">=": return itemInfo.commentCount() >= this.count; | |
case "<=": return itemInfo.commentCount() <= this.count; | |
default: throw new RangeError("Unimplemented operator "+this.op+" for scores"); | |
} | |
}, | |
toString: function() { | |
return "(comment count " + this.op + " " + this.count + ")"; | |
} | |
}) | |
function PostTypePredicate(type) { | |
if (["link", "text"].indexOf(type) == -1) throw new RangeError("invalid post type"); | |
this.type = type; | |
} | |
define(PostTypePredicate, { | |
evaluate: function(itemInfo) { | |
return this.type === itemInfo.postType(); | |
}, | |
toString: function() { | |
return "(is " + this.type +" post)" ; | |
} | |
}) | |
function ListingTypePredicate() { | |
throw new Error("Unimplemented"); | |
} | |
define(ListingTypePredicate, { | |
evaluate: function(itemInfo) { | |
throw new Error("Unimplemented"); | |
}, | |
toString: function() { | |
throw new Error("Unimplemented"); | |
} | |
}) | |
function UserClassificaitionPredicate() { | |
throw new Error("Unimplemented"); | |
} | |
define(UserClassificaitionPredicate, { | |
evaluate: function(itemInfo) { | |
throw new Error("Unimplemented"); | |
}, | |
toString: function() { | |
throw new Error("Unimplemented"); | |
} | |
}) | |
function UserGlobPredicate() { | |
throw new Error("Unimplemented"); | |
} | |
define(UserGlobPredicate, { | |
evaluate: function(itemInfo) { | |
throw new Error("Unimplemented"); | |
}, | |
toString: function() { | |
throw new Error("Unimplemented"); | |
} | |
}) | |
} | |
start = _* expression:expression _* | |
{return expression} | |
expression = or_expr | |
_ = " " | |
name_literal = $([A-Za-z0-9_]+) | |
glob_pattern = $(([A-Za-z0-9_]+/"*" !"*")+) | |
signed_integer 'signed integer' = $("0"/"-"?[1-9][0-9]*) {return parseInt(text(), 10)} | |
integer 'integer' = $("0"/[1-9][0-9]*) {return parseInt(text(), 10)} | |
or_expr = first:and_expr rest:(_+ "OR" _+ and_expr:and_expr {return and_expr})* | |
{return nestRight(concat(first, rest), ORExpression) } | |
and_expr = first:predicate rest:(_+ "AND" _+ predicate:predicate {return predicate})*{return nestRight(concat(first, rest), ANDExpression) } | |
// "predicate" | |
predicate | |
= nested_predicate | |
/ negation_predicate | |
/ subreddit_name_predicate | |
/ subreddit_glob_predicate | |
/ score_predicate | |
/ current_day_predicate | |
/ domain_predicate | |
/ nsfw_predicate | |
/ comment_count_predicate | |
/ post_type_predicate | |
/ listing_type_predicate | |
/ poster_predicate | |
nested_predicate "(<predicate>)" | |
= "(" _* expression:expression _* ")" | |
{return expression} | |
negation_predicate "NOT <predicate>" | |
= "NOT" _+ predicate:predicate | |
{return new NOTExpression(predicate)} | |
//Matching the subreddit | |
subreddit_name_predicate "subreddit IS /r/<name>" | |
= subreddit_keyword _+ "IS" _+ "/r/" name:name_literal | |
{return new SubredditPredicate(name) } | |
subreddit_glob_predicate "subreddit MATCHES /r/<glob>" | |
= subreddit_keyword _+ "MATCHES" _+ "/r/" pattern:glob_pattern | |
{return new SubredditMatchPredicate(pattern) } | |
subreddit_keyword = "subreddit"/"sr"/"sub" | |
//matching scores | |
score_predicate "score IS AT LEAST/MOST <number>" | |
= "score" _+ "IS" _+ op:("AT LEAST"/"AT MOST") _+ score:signed_integer | |
{return new ScorePredicate(op, score); } | |
//Matching the current day of the week | |
current_day_predicate "today IS A monday,friday,weekend,..." | |
= "today" _+ "IS A" _+ days:day_list {return new CurrentDayPredicate(days)} | |
day_list = first:day_of_week rest:day_list_rest* {return concat(first, rest)} | |
day_list_rest = _* "," _* day:day_of_week {return day} | |
day_of_week = "sunday"/"monday"/"tuesday"/"wednesday"/"thursday"/"friday"/"saturday" | |
/"weekday"/"weekend" | |
//Match the domain | |
domain_predicate "domain IS *.example.com" | |
= "domain" _+ "IS" _+ pattern:domain_pattern {return new DomainPredicate(pattern);} | |
domain_pattern = $ ("*."? domain_part ("." domain_part)+) | |
domain_part = [A-Za-z0-9\-]+ | |
nsfw_predicate "is sfw/nsfw" | |
= "is" _+ flag:("nsfw"/"sfw") {return new NSFWPredicate(flag==="nsfw")} | |
comment_count_predicate "post HAS AT LEAST/MOST <number> comments" | |
= "post" _+ "HAS" _+ op:("AT LEAST" / "AT MOST") _+ count:integer _+ "comments" | |
{return new CommentCountPredicate(op, count)} | |
post_type_predicate "is link/text post" | |
= "is" _+ type:("link"/"text") _+ "post" {return new PostTypePredicate(type)} | |
listing_type_predicate "listing /u/username|/r/subreddit|/u/username/m/multireddit|/me/m/multireddit|front page (glob patterns allowed)" | |
= "browsing" _+ pattern:$( | |
"front page" | |
/ "/r/" glob_pattern | |
/ "/u/" glob_pattern ("/m/" glob_pattern)? | |
/ "/me/m/" glob_pattern | |
) {return new ListingTypePredicate(pattern)} | |
poster_predicate | |
= "user" _+ "IS" _+ condition:( | |
cat:("friend"/"moderator"/"admin"/"me"/"OP") | |
{return new UserClassificaitionPredicate(cat)} | |
/ glob:$("/u/" glob_pattern) | |
{return new UserGlobPredicate(glob)} | |
) {return condition} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment