Skip to content

Instantly share code, notes, and snippets.

@gfldex
Created October 8, 2017 11:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gfldex/70526780997e802c62fca04df8b8a841 to your computer and use it in GitHub Desktop.
Save gfldex/70526780997e802c62fca04df8b8a841 to your computer and use it in GitHub Desktop.
use v6;
# use Grammar::Tracer;
grammar JSON {
token TOP { \s* <value> \s* }
rule object { '{' ~ '}' <pairlist> }
rule pairlist { <pair> * % \, }
rule pair { <string> ':' <value> }
rule array { '[' ~ ']' <arraylist> }
rule arraylist { <value> * % [ \, ] }
proto token value {*};
token value:sym<number> {
'-'?
[ 0 | <[1..9]> <[0..9]>* ]
[ \. <[0..9]>+ ]?
[ <[eE]> [\+|\-]? <[0..9]>+ ]?
}
token value:sym<true> { <sym> };
token value:sym<false> { <sym> };
token value:sym<null> { <sym> };
token value:sym<object> { <object> };
token value:sym<array> { <array> };
token value:sym<string> { <string> }
token string {
# see https://github.com/moritz/json/issues/25
(:ignoremark '"') ~ \" [ <str> | \\ <str=.str_escape> ]*
}
token str {
<-["\\\t\x[0A]]>+
}
token str_escape {
<["\\/bfnrt]> | 'u' <utf16_codepoint>+ % '\u'
}
token utf16_codepoint {
<.xdigit>**4
}
}
class JSON::Actions {
method TOP($/) {
make $<value>.made;
};
method object($/) {
make $<pairlist>.made.hash.item;
}
method pairlist($/) {
make $<pair>>>.made.flat;
}
method pair($/) {
make $<string>.made => $<value>.made;
}
method array($/) {
make $<arraylist>.made.item;
}
method arraylist($/) {
make [$<value>.map(*.made)];
}
method string($/) {
my $str = +@$<str> == 1
?? $<str>[0].made
!! $<str>>>.made.join;
# see https://github.com/moritz/json/issues/25
# when a combining character comes after an opening quote,
# it doesn't become part of the quoted string, because
# it's stuffed into the same grapheme as the quote.
# so we need to extract those combining character(s)
# from the match of the opening quote, and stuff it into the string.
if $0.Str ne '"' {
my @chars := $0.Str.NFC;
$str = @chars[1..*].chrs ~ $str;
}
make $str
}
method value:sym<number>($/) { make +$/.Str }
method value:sym<string>($/) { make $<string>.made }
method value:sym<true>($/) { make Bool::True }
method value:sym<false>($/) { make Bool::False }
method value:sym<null>($/) { make Any }
method value:sym<object>($/) { make $<object>.made }
method value:sym<array>($/) { make $<array>.made }
method str($/) { make ~$/ }
my %h = '\\' => "\\",
'/' => "/",
'b' => "\b",
'n' => "\n",
't' => "\t",
'f' => "\f",
'r' => "\r",
'"' => "\"";
method str_escape($/) {
if $<utf16_codepoint> {
make utf16.new( $<utf16_codepoint>.map({:16(~$_)}) ).decode();
} else {
make %h{~$/};
}
}
}
grammar WWW::Radiobrowser::JSON is JSON {
token TOP {\s* <top-array> \s* }
rule top-array { '[' ~ ']' <station-list> }
rule station-list { <station> * % ',' }
rule station { '{' ~ '}' <attribute-list> }
rule attribute-list { <attribute> * % ',' }
# rule pairlist { <pair> * % \, }
# rule pair { <string> ':' <value> }
token year { \d+ }
token month { \d ** 2 }
token day { \d ** 2 }
token hour { \d ** 2 }
token minute { \d ** 2 }
token second { \d ** 2}
token date { <year> '-' <month> '-' <day> ' ' <hour> ':' <minute> ':' <second> }
token bool { <value:true> || <value:false> }
token empty-string { '""' }
token number { <value:number> }
proto token attribute { * }
token attribute:sym<name> { '"name"' \s* ':' <string> }
token attribute:sym<bitrate> { '"bitrate"' \s* ':' \s* '"' <number> '"' }
token attribute:sym<clickcount> { '"clickcount"' \s* ':' \s* '"' <number> '"' }
token attribute:sym<lastchangetime> { '"lastchangetime"' \s* ':' \s* '"' <date> '"' }
token attribute:sym<language> { '"language"' \s* ':' \s* <string> }
token attribute:sym<votes> { '"votes"' \s* ':' \s* '"' <number> '"' }
token attribute:sym<clicktimestamp> { '"clicktimestamp"' \s* ':' \s* [ <empty-string> || '"' <date> '"' ] }
token attribute:sym<country> { '"country"' \s* ':' \s* <string> }
token attribute:sym<lastcheckoktime> { '"lastcheckoktime"' \s* ':' \s* [ <empty-string> || '"' <date> '"' ] }
token attribute:sym<id> { '"id"' \s* ':' \s* '"' <number> '"' }
token attribute:sym<codec> { '"codec"' \s* ':' \s* [ <empty-string> || <string> ] }
token attribute:sym<state> { '"state"' \s* ':' \s* <string> }
token attribute:sym<lastcheckok> { '"lastcheckok"' \s* ':' \s* '"' <bool> '"' }
token attribute:sym<clicktrend> { '"clicktrend"' \s* ':' \s* '"' <number> '"' }
token attribute:sym<ip> { '"ip"' \s* ':' \s* [ <empty-string> | <string> ] }
token attribute:sym<homepage> { '"homepage"' \s* ':' \s* <string> }
token attribute:sym<favicon> { '"favicon"' \s* ':' \s* <string> }
token attribute:sym<tags> { '"tags"' \s* ':' \s* <string> }
token attribute:sym<negativevotes> { '"negativevotes"' \s* ':' \s* '"' <number> '"' }
token attribute:sym<hls> { '"hls"' \s* ':' \s* '"' <number> '"' }
token attribute:sym<lastchecktime> { '"lastchecktime"' \s* ':' \s* '"' <date> '"' }
token attribute:sym<url> { '"url"' \s* ':' \s* <string> }
}
class WWW::Radiobrowser::JSON::Actions is JSON::Actions {
method TOP($/) {
make $<top-array>.made;
}
method top-array($/) {
make $<station-list>.made.item;
}
method station-list($/) {
make $<station>.hyper.map(*.made).flat;
}
method station($/) {
make $<attribute-list>.made.hash.item;
}
method attribute-list($/) {
make $<attribute>».made.flat;
}
method date($_) { .make: DateTime.new(.<year>.Int, .<month>.Int, .<day>.Int, .<hour>.Int, .<minute>.Int, .<second>.Num) }
method bool($_) { .make: .<value>.made ?? Bool::True !! Bool::False }
method empty-string($_) { .make: Str }
method attribute:sym<name>($/) { make 'name' => $/<string>.made; }
method attribute:sym<bitrate>($/) { make 'bitrate' => $/<number>.Int; }
method attribute:sym<clickcount>($/) { make 'clickcount' => $/<number>.Int; }
method attribute:sym<lastchangetime>($/) { make 'lastchangetime' => $/<date>.made; }
method attribute:sym<language>($/) { make 'language' => $/<string>.made; }
method attribute:sym<votes>($/) { make 'votes' => $/<number>.Int; }
method attribute:sym<clicktimestamp>($/) { make 'clicktimestamp' => $/<date>.made; }
method attribute:sym<country>($/) { make 'country' => $/<string>.made; }
method attribute:sym<lastcheckoktime>($/) { make 'lastcheckoktime' => $/<date>.made; }
method attribute:sym<id>($/) { make 'id' => $/<number>.Int; }
method attribute:sym<codec>($/) { make 'codec' => $/<string>.made; }
method attribute:sym<state>($/) { make 'state' => $/<string>.made; }
method attribute:sym<lastcheckok>($/) { make 'lastcheckok' => $/<bool>.made; }
method attribute:sym<clicktrend>($/) { make 'clicktrend' => $/<number>.Int; }
method attribute:sym<ip>($/) { make 'ip' => $/<string>.made; }
method attribute:sym<homepage>($/) { make 'homepage' => $/<string>.made; }
method attribute:sym<favicon>($/) { make 'favicon' => $/<string>.made; }
method attribute:sym<tags>($/) { make 'tags' => $/<string>.made.split(','); }
method attribute:sym<negativevotes>($/) { make 'negativevotes' => $/<number>.Int; }
method attribute:sym<hls>($/) { make 'hls' => $/<number>.Int; }
method attribute:sym<lastchecktime>($/) { make 'lastchecktime' => $/<date>.made; }
method attribute:sym<url>($/) { make 'url' => $/<string>.made; }
}
# my $json = q⟨[{"id":"89565","name":"#/g/punk radio","url":"http://cyberadio.pw:8000/stream","homepage":"http://cyberadio.pw","favicon":"http://cyberadio.pw/waterfall.gif","tags":"cyberpunk,g","country":"United States of America","state":"","language":"English","votes":"44","negativevotes":"0","codec":"MP3","bitrate":"128","hls":"0","lastcheckok":"1","lastchecktime":"2017-10-02 01:00:11","lastcheckoktime":"2017-10-02 01:00:11","clicktimestamp":"2017-10-03 10:58:10","clickcount":"5","clicktrend":"3","lastchangetime":"2017-09-10 17:48:06","ip":""},{"id":"45771","name":"2CA","url":"http://37.59.32.115/proxy/radio2ca?mp=/stream2ca.mp3","homepage":"http://www.2ca.com.au/","favicon":"http://www.2ca.com.au/templates/rt_enigma/images/logo/style6/logo.png","tags":"Oldies","country":"Australia","state":"Australian Capital Territory","language":"","votes":"2","negativevotes":"0","codec":"MP3","bitrate":"64","hls":"0","lastcheckok":"1","lastchecktime":"2017-10-03 05:10:07","lastcheckoktime":"2017-10-03 05:10:07","clicktimestamp":"2017-09-27 06:10:48","clickcount":"0","clicktrend":"0","lastchangetime":"2016-10-04 18:13:27","ip":""}]⟩;
# say JSON.parse($json, :actions(JSON::Actions.new)).made;
my @stations;
my $json;
{
$json = slurp('stations.json');
@stations = WWW::Radiobrowser::JSON.parse($json, :actions(WWW::Radiobrowser::JSON::Actions.new)).made.flat;
my $elapsed = now - ENTER now;
note "{+@stations} read and processed in {$elapsed}s with {+@stations / $elapsed} stations/s";
}
say @stations[1,*-1];
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment