changeset 1:8de269c09617

Outputting a summary of Marginal Revolution RSS feed
author Adam Chlipala <adam@chlipala.net>
date Tue, 11 Jan 2011 13:17:44 -0500
parents ad85b8813e8a
children 2ec84d349838
files src/ur/feed.ur src/ur/feed.urs src/ur/lib.urp tests/mr.ur
diffstat 4 files changed, 269 insertions(+), 4 deletions(-) [+]
line wrap: on
line diff
--- a/src/ur/feed.ur	Tue Jan 11 10:31:48 2011 -0500
+++ b/src/ur/feed.ur	Tue Jan 11 13:17:44 2011 -0500
@@ -1,3 +1,240 @@
 task initialize = fn () => FeedFfi.init
 
-val fetch = FeedFfi.fetch
+datatype pattern internal output =
+         Transducer of {Initial : internal,
+                        EnterTag : {Tag : string, Attrs : list (string * string), Cdata : option string} -> internal -> option internal,
+                        ExitTag : internal -> option internal,
+                        Finished : internal -> option output}
+
+con tagInternal (attrs :: {Unit}) = option {Attrs : $(mapU string attrs), Cdata : option string}
+
+fun tag [attrs ::: {Unit}] (fl : folder attrs) (name : string) (attrs : $(mapU string attrs))
+    : pattern (tagInternal attrs) {Attrs : $(mapU string attrs), Cdata : option string} =
+    Transducer {Initial = None,
+                EnterTag = fn tinfo state =>
+                              case state of
+                                  Some _ => None
+                                | None =>
+                                  if tinfo.Tag <> name then
+                                      None
+                                  else
+                                      case @foldUR [string] [fn r => option $(mapU string r)]
+                                            (fn [nm ::_] [r ::_] [[nm] ~ r] aname ro =>
+                                                case ro of
+                                                    None => None
+                                                  | Some r =>
+                                                    case List.assoc aname tinfo.Attrs of
+                                                        None => None
+                                                      | Some v => Some ({nm = v} ++ r))
+                                            (Some {}) fl attrs of
+                                          None => None
+                                        | Some vs => Some (Some {Attrs = vs, Cdata = tinfo.Cdata}),
+                ExitTag = Some,
+                Finished = fn x => x}
+
+datatype status a = Initial | Failed | Matched of a
+
+con childrenInternal (parent :: Type) (children :: {Type}) = option (parent * int * $(map status children))
+
+fun children [parentI ::: Type] [parent ::: Type] [children ::: {(Type * Type)}]
+             ((Transducer parent) : pattern parentI parent) (children : $(map (fn (i, d) => pattern i d) children)) (fl : folder children)
+    : pattern (childrenInternal parentI (map fst children)) (parent * $(map snd children)) =
+    Transducer {Initial = None,
+                EnterTag = fn tinfo state =>
+                              case state of
+                                  None =>
+                                  (case parent.EnterTag tinfo parent.Initial of
+                                       None => None
+                                     | Some pstate => Some (Some (pstate, 1, @map0 [status] (fn [t ::_] => Initial)
+                                                                              (@@Folder.mp [fst] [_] fl))))
+                                | Some (pstate, depth, cstates) =>
+                                  Some (Some (pstate,
+                                              depth+1,
+                                              @map2 [fn (i, d) => pattern i d] [fn (i, d) => status i] [fn (i, d) => status i]
+                                               (fn [p] ((Transducer ch) : pattern p.1 p.2) (cstate : status p.1) =>
+                                                   case cstate of
+                                                       Failed => Failed
+                                                     | Initial =>
+                                                       (case ch.EnterTag tinfo ch.Initial of
+                                                            None => Failed
+                                                          | Some v => Matched v)
+                                                     | v => v)
+                                               fl children cstates)),
+                ExitTag = fn state =>
+                             case state of
+                                 None => None
+                               | Some (pstate, depth, cstates) =>
+                                 case (if depth = 1 then
+                                           parent.ExitTag pstate
+                                       else
+                                           Some pstate) of
+                                     None => None
+                                   | Some pstate =>
+                                     if depth = 1 then
+                                         Some (Some (pstate, 0, cstates))
+                                     else
+                                         case @foldR2 [fn (i, d) => pattern i d] [fn (i, d) => status i]
+                                               [fn cs => option $(map (fn (i, d) => status i) cs)]
+                                               (fn [nm ::_] [p ::_] [r ::_] [[nm] ~ r] ((Transducer ch) : pattern p.1 p.2) (cstate : status p.1) acc =>
+                                                   case acc of
+                                                       None => None
+                                                     | Some acc =>
+                                                       case cstate of
+                                                           Matched cstate =>
+                                                           (case ch.ExitTag cstate of
+                                                                None => None
+                                                              | Some cstate' => Some ({nm = Matched cstate'} ++ acc))
+                                                         | _ => Some ({nm = Initial} ++ acc))
+                                               (Some {}) fl children cstates of
+                                             None => None
+                                           | Some cstates =>
+                                             Some (Some (pstate, depth-1, cstates)),
+                Finished = fn state =>
+                              case state of
+                                  Some (pstate, 0, cstates) =>
+                                  (case parent.Finished pstate of
+                                       None => None
+                                     | Some pdata =>
+                                       case @foldR2 [fn (i, d) => pattern i d] [fn (i, d) => status i] [fn cs => option $(map snd cs)]
+                                             (fn [nm ::_] [p ::_] [r ::_] [[nm] ~ r] ((Transducer ch) : pattern p.1 p.2) (cstate : status p.1) acc =>
+                                                 case acc of
+                                                     None => None
+                                                   | Some acc =>
+                                                     case cstate of
+                                                         Initial => None
+                                                       | Failed => None
+                                                       | Matched cstate =>
+                                                         case ch.Finished cstate of
+                                                             None => None
+                                                           | Some cdata => Some ({nm = cdata} ++ acc))
+                                             (Some {}) fl children cstates of
+                                           None => None
+                                         | Some cdata => Some (pdata, cdata))
+                                | _ => None}
+
+fun app [internal ::: Type] [data ::: Type] ((Transducer p) : pattern internal data) (f : data -> transaction {}) (url : string) : transaction {} =
+    let
+        fun recur xml state =
+            case String.split xml #"<" of
+                None => return ()
+              | Some (_, xml) =>
+                if xml <> "" && String.sub xml 0 = #"/" then
+                    case String.split xml #"\x3E" of
+                        None => return ()
+                      | Some (_, xml) =>
+                        case p.ExitTag state of
+                            None => recur xml p.Initial
+                          | Some state =>
+                            case p.Finished state of
+                                 None => recur xml state
+                               | Some data =>
+                                 f data;
+                                 recur xml p.Initial
+                else if xml <> "" && String.sub xml 0 = #"?" then
+                    case String.split xml #"\x3E" of
+                        None => return ()
+                      | Some (_, xml) => recur xml state
+                else if xml <> "" && String.sub xml 0 = #"!" then
+                    if String.length xml >= 3 && String.sub xml 1 = #"-" && String.sub xml 2 = #"-" then
+                        let
+                            fun skipper xml =
+                                case String.split xml #"-" of
+                                    None => xml
+                                  | Some (_, xml) =>
+                                    if String.length xml >= 2 && String.sub xml 0 = #"-" && String.sub xml 1 = #"\x3E" then
+                                        String.suffix xml 2
+                                    else
+                                        skipper xml
+                        in
+                            recur (skipper (String.suffix xml 3)) state
+                        end
+                    else
+                        case String.split xml #"]" of
+                            None => return ()
+                          | Some (_, xml) =>
+                            case String.split xml #"\x3E" of
+                                None => return ()
+                              | Some (_, xml) => recur xml state
+                else
+                    case String.msplit {Needle = " >/", Haystack = xml} of
+                        None => return ()
+                      | Some (tagName, ch, xml) =>
+                        let
+                            fun readAttrs ch xml acc =
+                                case ch of
+                                    #"\x3E" => (xml, acc, False)
+                                  | #"/" =>
+                                    (case String.split xml #"\x3E" of
+                                         None => (xml, acc, True)
+                                       | Some (_, xml) => (xml, acc, True))
+                                  | _ =>
+                                    if String.length xml >= 2 && Char.isSpace (String.sub xml 0) then
+                                        readAttrs (String.sub xml 0) (String.suffix xml 1) acc
+                                    else if xml <> "" && String.sub xml 0 = #"\x3E" then
+                                        (String.suffix xml 1, acc, False)
+                                    else if xml <> "" && String.sub xml 0 = #"/" then
+                                        (case String.split xml #"\x3E" of
+                                             None => (xml, acc, True)
+                                           | Some (_, xml) => (xml, acc, True))
+                                    else
+                                        case String.split xml #"=" of
+                                            None => (xml, acc, False)
+                                          | Some (aname, xml) =>
+                                            if xml = "" || String.sub xml 0 <> #"\"" then
+                                                (xml, (aname, "") :: acc, False)
+                                            else
+                                                case String.split (String.suffix xml 1) #"\"" of
+                                                    None => (xml, (aname, "") :: acc, False)
+                                                  | Some (value, xml) =>
+                                                    if xml = "" then
+                                                        (xml, (aname, value) :: acc, False)
+                                                    else
+                                                        readAttrs (String.sub xml 0) (String.suffix xml 1) ((aname, value) :: acc)
+
+                            val (xml, attrs, ended) = readAttrs ch xml []
+
+                            fun skipSpaces xml =
+                                if xml <> "" && Char.isSpace (String.sub xml 0) then
+                                    skipSpaces (String.suffix xml 1)
+                                else
+                                    xml
+
+                            val xml = skipSpaces xml
+
+                            val (xml, cdata) =
+                                if ended then
+                                    (xml, None)
+                                else if String.isPrefix {Prefix = "<![CDATA[", Full = xml} then
+                                    let
+                                        fun skipper xml acc =
+                                            case String.split xml #"]" of
+                                                None => (acc ^ xml, None)
+                                              | Some (pre, xml) =>
+                                                if String.length xml >= 2 && String.sub xml 0 = #"]" && String.sub xml 1 = #"\x3E" then
+                                                    (String.suffix xml 2, Some (acc ^ pre))
+                                                else
+                                                    skipper xml (acc ^ "]" ^ pre)
+                                    in
+                                        skipper (String.suffix xml 9) ""
+                                    end
+                                else
+                                    case String.split xml #"<" of
+                                        None => (xml, None)
+                                      | Some (cdata, xml) => ("<" ^ xml, Some cdata)
+                        in
+                            case p.EnterTag {Tag = tagName, Attrs = attrs, Cdata = cdata} state of
+                                None => recur xml p.Initial
+                              | Some state =>
+                                case (if ended then p.ExitTag state else Some state) of
+                                    None => recur xml p.Initial
+                                  | Some state =>
+                                    case p.Finished state of
+                                         None => recur xml state
+                                       | Some data =>
+                                         f data;
+                                         recur xml p.Initial
+                        end
+    in
+        xml <- FeedFfi.fetch url;
+        recur xml p.Initial
+    end
--- a/src/ur/feed.urs	Tue Jan 11 10:31:48 2011 -0500
+++ b/src/ur/feed.urs	Tue Jan 11 13:17:44 2011 -0500
@@ -1,1 +1,14 @@
-val fetch : string -> transaction string
+con pattern :: Type -> Type -> Type
+
+con tagInternal :: {Unit} -> Type
+
+val tag : attrs ::: {Unit} -> folder attrs -> string -> $(mapU string attrs)
+          -> pattern (tagInternal attrs) {Attrs : $(mapU string attrs), Cdata : option string}
+
+con childrenInternal :: Type -> {Type} -> Type
+
+val children : parentI ::: Type -> parent ::: Type -> children ::: {(Type * Type)}
+               -> pattern parentI parent -> $(map (fn (i, d) => pattern i d) children) -> folder children
+               -> pattern (childrenInternal parentI (map fst children)) (parent * $(map snd children))
+
+val app : internal ::: Type -> data ::: Type -> pattern internal data -> (data -> transaction {}) -> string -> transaction {}
--- a/src/ur/lib.urp	Tue Jan 11 10:31:48 2011 -0500
+++ b/src/ur/lib.urp	Tue Jan 11 13:17:44 2011 -0500
@@ -4,4 +4,7 @@
 effectful FeedFfi.init
 benignEffectful FeedFfi.fetch
 
+$/char
+$/string
+$/list
 feed
--- a/tests/mr.ur	Tue Jan 11 10:31:48 2011 -0500
+++ b/tests/mr.ur	Tue Jan 11 13:17:44 2011 -0500
@@ -1,5 +1,17 @@
 fun main () =
-    txt <- Feed.fetch "http://feeds.feedburner.com/marginalrevolution/hCQh";
+    Feed.app (Feed.children
+                  (Feed.tag "item" {1 = "rdf:about"})
+                  (Feed.tag "title" {}, Feed.tag "content:encoded" {}))
+             (fn ({Attrs = {1 = about}, ...},
+                  ({Cdata = title, ...}, {Cdata = content, ...})) =>
+                 debug ("URL: " ^ about);
+                 (case title of
+                      None => return ()
+                    | Some title => debug ("Title: " ^ title));
+                  case content of
+                      None => return ()
+                    | Some content => debug ("Content: " ^ content))
+             "http://feeds.feedburner.com/marginalrevolution/hCQh";
     return <xml>
-      {[txt]}
+      See stdout.
     </xml>