HS: do→let/it chaining, single-IO fetch, fetch URL parser, IO mock
Compiler: do-blocks now compile to (let ((it cmd1)) (let ((it cmd2)) ...)) instead of (do cmd1 cmd2 ...). This chains the `it` variable through command sequences, enabling `fetch X then put it into me` pattern. Each command's result is bound to `it` for the next command. Runtime: hs-fetch simplified to single perform (io-fetch url format) instead of two-stage io-fetch + io-parse-text/json. Parser: fetch URL /path handled by reading /+ident tokens. Default fetch format changed to "text" (was "json"). Test runner: mock fetch routes with format-specific responses. io-fetch handler returns content directly based on format param. Fetch tests still need IO suspension to chain through let continuations. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -999,7 +999,23 @@
|
||||
(hs-to-sx (nth ast 1))
|
||||
(hs-to-sx (nth ast 2)))))
|
||||
((= head (quote do))
|
||||
(cons (quote do) (map hs-to-sx (rest ast))))
|
||||
(let
|
||||
((compiled (map hs-to-sx (rest ast))))
|
||||
(if
|
||||
(= (len compiled) 1)
|
||||
(first compiled)
|
||||
(let
|
||||
((last-cmd (nth compiled (- (len compiled) 1)))
|
||||
(init-cmds (reverse (rest (reverse compiled)))))
|
||||
(reduce
|
||||
(fn
|
||||
(body cmd)
|
||||
(list
|
||||
(quote let)
|
||||
(list (list (quote it) cmd))
|
||||
body))
|
||||
last-cmd
|
||||
(reverse init-cmds))))))
|
||||
((= head (quote wait)) (list (quote hs-wait) (nth ast 1)))
|
||||
((= head (quote wait-for)) (emit-wait-for ast))
|
||||
((= head (quote log))
|
||||
|
||||
@@ -1270,7 +1270,7 @@
|
||||
(let
|
||||
((url (if (nil? url-atom) url-atom (parse-arith (parse-poss url-atom)))))
|
||||
(let
|
||||
((fmt (if (match-kw "as") (let ((f (tp-val))) (adv!) f) "json")))
|
||||
((fmt (if (match-kw "as") (let ((f (tp-val))) (adv!) f) "text")))
|
||||
(list (quote fetch) url fmt)))))))
|
||||
(define
|
||||
parse-call-args
|
||||
|
||||
@@ -285,13 +285,7 @@
|
||||
hs-fetch
|
||||
(fn
|
||||
(url format)
|
||||
(let
|
||||
((response (perform (list (quote io-fetch) url))))
|
||||
(cond
|
||||
((= format "json") (perform (list (quote io-parse-json) response)))
|
||||
((= format "text") (perform (list (quote io-parse-text) response)))
|
||||
((= format "html") (perform (list (quote io-parse-html) response)))
|
||||
(true response)))))
|
||||
(perform (list "io-fetch" url (if format format "text")))))
|
||||
|
||||
(define
|
||||
hs-coerce
|
||||
|
||||
@@ -999,7 +999,23 @@
|
||||
(hs-to-sx (nth ast 1))
|
||||
(hs-to-sx (nth ast 2)))))
|
||||
((= head (quote do))
|
||||
(cons (quote do) (map hs-to-sx (rest ast))))
|
||||
(let
|
||||
((compiled (map hs-to-sx (rest ast))))
|
||||
(if
|
||||
(= (len compiled) 1)
|
||||
(first compiled)
|
||||
(let
|
||||
((last-cmd (nth compiled (- (len compiled) 1)))
|
||||
(init-cmds (reverse (rest (reverse compiled)))))
|
||||
(reduce
|
||||
(fn
|
||||
(body cmd)
|
||||
(list
|
||||
(quote let)
|
||||
(list (list (quote it) cmd))
|
||||
body))
|
||||
last-cmd
|
||||
(reverse init-cmds))))))
|
||||
((= head (quote wait)) (list (quote hs-wait) (nth ast 1)))
|
||||
((= head (quote wait-for)) (emit-wait-for ast))
|
||||
((= head (quote log))
|
||||
|
||||
@@ -1270,7 +1270,7 @@
|
||||
(let
|
||||
((url (if (nil? url-atom) url-atom (parse-arith (parse-poss url-atom)))))
|
||||
(let
|
||||
((fmt (if (match-kw "as") (let ((f (tp-val))) (adv!) f) "json")))
|
||||
((fmt (if (match-kw "as") (let ((f (tp-val))) (adv!) f) "text")))
|
||||
(list (quote fetch) url fmt)))))))
|
||||
(define
|
||||
parse-call-args
|
||||
|
||||
@@ -285,13 +285,7 @@
|
||||
hs-fetch
|
||||
(fn
|
||||
(url format)
|
||||
(let
|
||||
((response (perform (list (quote io-fetch) url))))
|
||||
(cond
|
||||
((= format "json") (perform (list (quote io-parse-json) response)))
|
||||
((= format "text") (perform (list (quote io-parse-text) response)))
|
||||
((= format "html") (perform (list (quote io-parse-html) response)))
|
||||
(true response)))))
|
||||
(perform (list "io-fetch" url (if format format "text")))))
|
||||
|
||||
(define
|
||||
hs-coerce
|
||||
|
||||
@@ -192,6 +192,8 @@ const _fetchRoutes = {
|
||||
'/test': { status: 200, body: 'yay', json: '{"foo":1}', html: '<div>yay</div>' },
|
||||
'/test-json': { status: 200, body: '{"foo":1}', json: '{"foo":1}' },
|
||||
'/404': { status: 404, body: 'the body' },
|
||||
'/number': { status: 200, body: '1.2' },
|
||||
'/users/Joe': { status: 200, body: 'Joe', json: '{"name":"Joe"}' },
|
||||
};
|
||||
function _mockFetch(url) {
|
||||
const route = _fetchRoutes[url] || _fetchRoutes['/test'];
|
||||
@@ -201,10 +203,19 @@ function _mockFetch(url) {
|
||||
globalThis._driveAsync=function driveAsync(r,d){d=d||0;if(d>500||!r||!r.suspended)return;if(_testDeadline && Date.now()>_testDeadline)throw new Error('TIMEOUT: wall clock exceeded');const req=r.request;const items=req&&(req.items||req);const op=items&&items[0];const opName=typeof op==='string'?op:(op&&op.name)||String(op);
|
||||
function doResume(v){try{const x=r.resume(v);driveAsync(x,d+1);}catch(e){}}
|
||||
if(opName==='io-sleep'||opName==='wait')doResume(null);
|
||||
else if(opName==='io-fetch'){const url=items&&items[1];doResume(_mockFetch(typeof url==='string'?url:'/test'));}
|
||||
else if(opName==='io-parse-text'){const resp=items&&items[1];doResume(resp&&resp._body?resp._body:'');}
|
||||
else if(opName==='io-parse-json'){const resp=items&&items[1];try{doResume(JSON.parse(resp&&resp._json?resp._json:'{}'));}catch(e){doResume(null);}}
|
||||
else if(opName==='io-parse-html'){const resp=items&&items[1];const frag=new El('fragment');frag.nodeType=11;frag.innerHTML=resp&&resp._html?resp._html:'';frag.textContent=frag.innerHTML.replace(/<[^>]*>/g,'');doResume(frag);}
|
||||
else if(opName==='io-fetch'){
|
||||
const url=typeof items[1]==='string'?items[1]:'/test';
|
||||
const fmt=typeof items[2]==='string'?items[2]:'text';
|
||||
const route=_fetchRoutes[url]||_fetchRoutes['/test'];
|
||||
if(fmt==='json'){try{doResume(JSON.parse(route.json||route.body||'{}'));}catch(e){doResume(null);}}
|
||||
else if(fmt==='html'){const frag=new El('fragment');frag.nodeType=11;frag.innerHTML=route.html||route.body||'';frag.textContent=frag.innerHTML.replace(/<[^>]*>/g,'');doResume(frag);}
|
||||
else if(fmt==='response')doResume({ok:(route.status||200)<400,status:route.status||200,url});
|
||||
else if(fmt==='Number'||fmt==='number')doResume(parseFloat(route.body||'0'));
|
||||
else doResume(route.body||'');
|
||||
}
|
||||
else if(opName==='io-parse-text'){const resp=items&&items[1];doResume(resp&&resp._body?resp._body:typeof resp==='string'?resp:'');}
|
||||
else if(opName==='io-parse-json'){const resp=items&&items[1];try{doResume(JSON.parse(typeof resp==='string'?resp:resp&&resp._json?resp._json:'{}'));}catch(e){doResume(null);}}
|
||||
else if(opName==='io-parse-html'){const frag=new El('fragment');frag.nodeType=11;doResume(frag);}
|
||||
else if(opName==='io-settle')doResume(null);
|
||||
else if(opName==='io-wait-event')doResume(null);
|
||||
else if(opName==='io-transition')doResume(null);
|
||||
|
||||
Reference in New Issue
Block a user