From 673bc3ae528ec4518d55037652a47e98dfeab2c9 Mon Sep 17 00:00:00 2001 From: Peter Harpending Date: Sat, 11 Oct 2025 17:24:11 -0600 Subject: [PATCH] caching works correctly --- src/fd_cache.erl | 120 +++++++++++++++++++++++++++++++++++++++ src/fd_client.erl | 38 +++++++++---- src/fd_sup.erl | 8 ++- src/wfc_eval.erl | 5 ++ src/wfc_eval_context.erl | 18 ++++-- 5 files changed, 172 insertions(+), 17 deletions(-) create mode 100644 src/fd_cache.erl diff --git a/src/fd_cache.erl b/src/fd_cache.erl new file mode 100644 index 0000000..a35fb9a --- /dev/null +++ b/src/fd_cache.erl @@ -0,0 +1,120 @@ +% @doc storing map #{cookie := Context} +-module(fd_cache). + +-behavior(gen_server). + +-export([ + start_link/0, + query/1, set/2, unset/1, + %%--- + %% everything below here runs in process context + %%-- + %% gen_server callbacks + init/1, handle_call/3, handle_cast/2, handle_info/2, + code_change/3, terminate/2 +]). + +-include("$zx_include/zx_logger.hrl"). + +-type context() :: wfc_eval_context:context(). + +-record(s, + {cookies = #{} :: #{Cookie :: binary() := context()}}). +% -type state() :: #s{}. + + +%%-------------------------------- +%% api (runs in context of caller) +%%-------------------------------- + +-spec start_link() -> {ok, pid()} | {error, term()}. +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, none, []). + + + +-spec query(Cookie) -> {ok, Context} | error + when Cookie :: binary(), + Context :: context(). + +query(Cookie) -> + gen_server:call(?MODULE, {query, Cookie}). + + + +-spec set(Cookie, Context) -> ok + when Cookie :: binary(), + Context :: context(). + +set(Cookie, Context) -> + gen_server:cast(?MODULE, {set, Cookie, Context}). + + +-spec unset(Cookie) -> ok + when Cookie :: binary(). + +unset(Cookie) -> + gen_server:cast(?MODULE, {unset, Cookie}). + + +%%---------------------- +%% gen-server bs +%%---------------------- + + + +init(none) -> + log(info, "starting fd_cache"), + InitState = #s{}, + {ok, InitState}. + + +handle_call({query, Cookie}, _, State) -> + Result = do_query(Cookie, State), + {reply, Result, State}; +handle_call(Unexpected, From, State) -> + tell("~tp: unexpected call from ~tp: ~tp", [?MODULE, Unexpected, From]), + {noreply, State}. + + +handle_cast({set, Cookie, Context}, State) -> + NewState = do_set(Cookie, Context, State), + {noreply, NewState}; +handle_cast({unset, Cookie}, State) -> + NewState = do_unset(Cookie, State), + {noreply, NewState}; +handle_cast(Unexpected, State) -> + tell("~tp: unexpected cast: ~tp", [?MODULE, Unexpected]), + {noreply, State}. + + +handle_info(Unexpected, State) -> + tell("~tp: unexpected info: ~tp", [?MODULE, Unexpected]), + {noreply, State}. + + +code_change(_, State, _) -> + {ok, State}. + +terminate(_, _) -> + ok. + + +%%--------------------- +%% doers +%%--------------------- + +do_set(Cookie, Context, State = #s{cookies = Cookies}) -> + NewCookies = maps:put(Cookie, Context, Cookies), + NewState = State#s{cookies = NewCookies}, + NewState. + + +do_unset(Cookie, State = #s{cookies = Cookies}) -> + NewCookies = maps:remove(Cookie, Cookies), + NewState = State#s{cookies = NewCookies}, + NewState. + + +do_query(Cookie, _State = #s{cookies = Cookies}) -> + maps:find(Cookie, Cookies). diff --git a/src/fd_client.erl b/src/fd_client.erl index b242576..47fbfa0 100644 --- a/src/fd_client.erl +++ b/src/fd_client.erl @@ -263,34 +263,52 @@ default_css(Sock) -> http_err(Sock, 500) end. -wfcin(Sock, #request{enctype = json, body = #{"wfcin" := Input}}) -> +wfcin(Sock, #request{enctype = json, + cookies = Cookies, + body = #{"wfcin" := Input}}) -> tell("wfcin good request: ~tp", [Input]), - RespObj = + {Cookie, Ctx0} = ctx(Cookies), + {RespObj, NewCtx} = + %% FIXME: this should really be a new process try case wfc_read:expr(Input) of - %% FIXME support multiple expressions {ok, Expr, _Rest} -> - case wfc_eval:eval(Expr, wfc_eval_context:default()) of - {ok, noop, _NewContext} -> jsgud(""); - {ok, Sentence, _NewContext} -> jsgud(wfc_pp:sentence(Sentence)); - {error, Message} -> jsbad(Message) + case wfc_eval:eval(Expr, Ctx0) of + {ok, noop, Ctx1} -> {jsgud(""), Ctx1}; + {ok, Sentence, Ctx1} -> {jsgud(wfc_pp:sentence(Sentence)), Ctx1}; + {error, Message} -> {jsbad(Message), Ctx0} end; {error, Message} -> - jsbad(Message) + {jsbad(Message), Ctx0} end catch error:E:S -> ErrorMessage = unicode:characters_to_list(io_lib:format("parser crashed: ~p:~p", [E, S])), - jsbad(ErrorMessage) + {jsbad(ErrorMessage), Ctx0} end, + % update cache with new context + ok = fd_cache:set(Cookie, NewCtx), Body = zj:encode(RespObj), - Response = #response{headers = [{"content-type", "application/json"}], + Response = #response{headers = [{"content-type", "application/json"}, + {"set-cookie", ["wfc=", Cookie]}], body = Body}, respond(Sock, Response); wfcin(Sock, Request) -> tell("wfcin: bad request: ~tp", [Request]), http_err(Sock, 400). + +ctx(#{<<"wfc">> := Cookie}) -> + case fd_cache:query(Cookie) of + {ok, Context} -> {Cookie, Context}; + error -> {Cookie, wfc_eval_context:default()} + end; +ctx(_) -> + {new_cookie(), wfc_eval_context:default()}. + +new_cookie() -> + binary:encode_hex(crypto:strong_rand_bytes(8)). + jsgud(X) -> #{"ok" => true, "result" => X}. diff --git a/src/fd_sup.erl b/src/fd_sup.erl index f84c8ce..e5d753a 100644 --- a/src/fd_sup.erl +++ b/src/fd_sup.erl @@ -42,5 +42,11 @@ init([]) -> 5000, supervisor, [fd_clients]}, - Children = [Clients], + Cache = {fd_cache, + {fd_cache, start_link, []}, + permanent, + 5000, + worker, + [fd_cache]}, + Children = [Clients, Cache], {ok, {RestartStrategy, Children}}. diff --git a/src/wfc_eval.erl b/src/wfc_eval.erl index 9749a80..304bb5e 100644 --- a/src/wfc_eval.erl +++ b/src/wfc_eval.erl @@ -59,6 +59,11 @@ eval_sexp(Args = [{snowflake, <<"define">>}, {pattern, Pat}, Expr], Ctx0) -> Error -> Error end; +eval_sexp(_Args = [{snowflake, <<"undefine">>}, {pattern, Pat}], Ctx0) -> + case wfc_eval_context:undefine(Pat, Ctx0) of + {ok, NewContext} -> {ok, noop, NewContext}; + Error -> Error + end; eval_sexp([{snowflake, SF} | Args], Ctx0) -> % first evaluate the arguments individually case eval_sexp_args(Args, Ctx0, []) of diff --git a/src/wfc_eval_context.erl b/src/wfc_eval_context.erl index d686282..22e72e8 100644 --- a/src/wfc_eval_context.erl +++ b/src/wfc_eval_context.erl @@ -9,6 +9,7 @@ default/0, default_snowflakes/0, define/3, + undefine/2, resolve_pattern/2, resolve_snowflake/2 ]). @@ -33,6 +34,7 @@ default() -> default_snowflakes() -> #{<<"and">> => fun wfc:mul/1, <<"xor">> => fun wfc:add/1, + <<"implies">> => fun snf_implies/1, <<"ior">> => fun IOR([S1 | Rest]) -> case IOR(Rest) of @@ -46,10 +48,6 @@ default_snowflakes() -> fun ([S]) -> {ok, sf_not(S)}; (Bad) -> {error, wfc_utils:str("not/1: wrong number of arguments: ~p", [Bad])} end, - <<"implies">> => - fun ([A, B]) -> {ok, sf_implies(A, B)}; - (Bad) -> {error, wfc_utils:str("implies/2: wrong number of arguments: ~p", [Bad])} - end, <<"impliedby">> => fun ([A, B]) -> {ok, sf_impliedby(A, B)}; (Bad) -> {error, wfc_utils:str("impliedby/2: wrong number of arguments: ~p", [Bad])} @@ -60,6 +58,10 @@ default_snowflakes() -> end }. +snf_implies([A, B]) -> {ok, sf_implies(A, B)}; +snf_implies(Bad) -> {error, wfc_utils:str("implies/2: wrong number of arguments: ~p", [Bad])}. + + sf_ior(A, B) -> wfc_sftt:appl_ttf(fun ttf_ior/2, [A, B]). @@ -93,14 +95,18 @@ define(Pat, Sentence, Ctx = #ctx{patterns = OldPatterns}) -> NewPatterns = maps:put(Pat, Sentence, OldPatterns), {ok, Ctx#ctx{patterns = NewPatterns}}. +undefine(Pat, Ctx = #ctx{patterns = OldPatterns}) -> + NewPatterns = maps:remove(Pat, OldPatterns), + {ok, Ctx#ctx{patterns = NewPatterns}}. + resolve_pattern(Pat, Ctx = #ctx{patterns = Patterns}) -> case maps:find(Pat, Patterns) of - error -> {error, wfc_utils:str("wfc_eval_context:resolve_pattern: not found: ~w; context: ~w", [Pat, Ctx])}; + error -> {error, wfc_utils:str("wfc_eval_context:resolve_pattern: not found: ~s; context: ~w", [Pat, Ctx])}; Result -> Result end. resolve_snowflake(SF, Ctx = #ctx{snowflakes = Snowflakes}) -> case maps:find(SF, Snowflakes) of - error -> {error, wfc_utils:str("wfc_eval_context:resolve_snowflake: not found: ~w; context: ~w", [SF, Ctx])}; + error -> {error, wfc_utils:str("wfc_eval_context:resolve_snowflake: not found: ~s; context: ~w", [SF, Ctx])}; Result -> Result end.