Skip to content

Instantly share code, notes, and snippets.

@gamefreak
Last active August 29, 2015 14:11
Show Gist options
  • Save gamefreak/f698ea7cc6b46b3001ff to your computer and use it in GitHub Desktop.
Save gamefreak/f698ea7cc6b46b3001ff to your computer and use it in GitHub Desktop.
/*
(*
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