Created
October 8, 2017 11:06
-
-
Save gfldex/70526780997e802c62fca04df8b8a841 to your computer and use it in GitHub Desktop.
This file contains 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
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