Skip to content

Commit

Permalink
Merge pull request #10 from ghivert/feat/try-new-type-search
Browse files Browse the repository at this point in the history
New type search
  • Loading branch information
ghivert authored Aug 1, 2024
2 parents b3312f7 + cf2c5f1 commit 335638b
Show file tree
Hide file tree
Showing 10 changed files with 333 additions and 48 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
-- migrate:up
create table search_analytics (
query text primary key,
occurences int not null default 0,
created_at timestamptz default current_timestamp not null,
updated_at timestamptz default current_timestamp not null
);

create trigger search_analytics_moddatetime
before update on search_analytics
for each row
execute procedure moddatetime (updated_at);

-- migrate:down
drop trigger search_analytics_moddatetime on search_analytics;
drop table search_analytics;
30 changes: 29 additions & 1 deletion apps/backend/db/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,18 @@ CREATE TABLE public.schema_migrations (
);


--
-- Name: search_analytics; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.search_analytics (
query text NOT NULL,
occurences integer DEFAULT 0 NOT NULL,
created_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL
);


--
-- Name: analytics analytics_foreign_id_table_name_day_key; Type: CONSTRAINT; Schema: public; Owner: -
--
Expand Down Expand Up @@ -404,6 +416,14 @@ ALTER TABLE ONLY public.schema_migrations
ADD CONSTRAINT schema_migrations_pkey PRIMARY KEY (version);


--
-- Name: search_analytics search_analytics_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.search_analytics
ADD CONSTRAINT search_analytics_pkey PRIMARY KEY (query);


--
-- Name: package_type_fun_signature_documentation; Type: INDEX; Schema: public; Owner: -
--
Expand Down Expand Up @@ -474,6 +494,13 @@ CREATE TRIGGER package_release_moddatetime BEFORE UPDATE ON public.package_relea
CREATE TRIGGER package_type_fun_signature_moddatetime BEFORE UPDATE ON public.package_type_fun_signature FOR EACH ROW EXECUTE FUNCTION public.moddatetime('updated_at');


--
-- Name: search_analytics search_analytics_moddatetime; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER search_analytics_moddatetime BEFORE UPDATE ON public.search_analytics FOR EACH ROW EXECUTE FUNCTION public.moddatetime('updated_at');


--
-- Name: package_module package_module_package_release_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--
Expand Down Expand Up @@ -539,4 +566,5 @@ INSERT INTO public.schema_migrations (version) VALUES
('20240517083006'),
('20240518232212'),
('20240521174525'),
('20240521204341');
('20240521204341'),
('20240801164720');
3 changes: 3 additions & 0 deletions apps/backend/src/backend/gleam/parse.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import glexer
import glexer/token

pub type Kind {
DiscardName
Index(String, Int)
Custom(String, List(Kind))
Function(List(Kind), Kind)
Expand Down Expand Up @@ -43,6 +44,7 @@ fn parse_name() {
use token <- chomp.take_map()
case token {
token.Name(content) -> Some(Index(content, 0))
token.DiscardName(_) -> Some(DiscardName)
_ -> None
}
}
Expand Down Expand Up @@ -128,6 +130,7 @@ fn replace_indexed(
) -> #(Kind, #(Dict(String, Int), Int)) {
let #(indexes, current) = indexes
case kind {
DiscardName -> #(DiscardName, #(indexes, current))
Index(name, _) -> {
case dict.get(indexes, name) {
Ok(value) -> #(Index("", value), #(indexes, current))
Expand Down
245 changes: 206 additions & 39 deletions apps/backend/src/backend/gleam/type_search.gleam
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
import backend/gleam/parse.{type Kind, Function}
import gleam/dict.{type Dict}
import gleam/int
import gleam/list
import gleam/option
import gleam/option.{type Option}
import gleam/pair
import gleam/result

pub type TypeSearch {
TypeSearch(entries: Dict(Kind, TypeSearch), rows: List(Int))
TypeSearch(keys: Keys, rows: List(Int))
}

pub type Keys {
Keys(keys: Dict(String, Keys), next: Option(TypeSearch))
}

pub fn empty() {
TypeSearch(dict.new(), [])
let keys = Keys(dict.new(), option.None)
TypeSearch(keys: keys, rows: [])
}

fn postpend(list: List(a), value: a) {
Expand All @@ -19,52 +26,212 @@ fn postpend(list: List(a), value: a) {
|> list.reverse
}

fn add_index(list: List(a)) {
use elem <- list.map(list)
#(elem, option.None)
}

fn do_add(searches: TypeSearch, kinds: List(#(Kind, option.Option(Int)))) {
fn update_keys(
keys: Keys,
kinds: List(Kind),
updater: fn(TypeSearch) -> TypeSearch,
) -> Keys {
case kinds {
[] -> searches
[#(kind, option.Some(id))] ->
dict.get(searches.entries, kind)
|> result.unwrap(empty())
|> fn(s: TypeSearch) {
let rows = case list.contains(s.rows, id) {
True -> s.rows
False -> [id, ..s.rows]
[] -> {
let next =
keys.next
|> option.unwrap(empty())
|> updater
|> option.Some
Keys(..keys, next: next)
}
[k, ..rest] -> {
let next = option.None
let new_keys = case k {
parse.DiscardName -> panic as "No Discard name in add"
parse.Index(_value, index) -> {
let value = int.to_string(index)
dict.update(keys.keys, value, fn(k) {
let k = option.unwrap(k, Keys(keys: dict.new(), next: next))
update_keys(k, rest, updater)
})
}
parse.Custom(value, kinds) ->
dict.update(keys.keys, value, fn(k) {
let k = option.unwrap(k, Keys(keys: dict.new(), next: next))
update_keys(k, list.append(kinds, rest), updater)
})
parse.Function(kinds, return) -> {
let kinds = postpend(kinds, return)
dict.update(keys.keys, "fn", fn(k) {
let k = option.unwrap(k, Keys(keys: dict.new(), next: next))
update_keys(k, list.append(kinds, rest), updater)
})
}
parse.Tuple(kinds) -> {
dict.update(keys.keys, "#()", fn(k) {
let k = option.unwrap(k, Keys(keys: dict.new(), next: next))
update_keys(k, list.append(kinds, rest), updater)
})
}
TypeSearch(..s, rows: rows)
}
|> dict.insert(searches.entries, kind, _)
|> fn(a) { TypeSearch(..searches, entries: a) }
[#(kind, _), ..rest] ->
dict.get(searches.entries, kind)
|> result.unwrap(empty())
|> do_add(rest)
|> dict.insert(searches.entries, kind, _)
|> fn(s) { TypeSearch(..searches, entries: s) }
Keys(..keys, keys: new_keys)
}
}
}

fn do_add(searches: TypeSearch, kinds: List(Kind), id: Int) -> TypeSearch {
case kinds {
[] -> TypeSearch(..searches, rows: [id, ..searches.rows])
[kind, ..rest] -> {
TypeSearch(
..searches,
keys: update_keys(searches.keys, [kind], do_add(_, rest, id)),
)
}
}
}

pub fn add(searches: TypeSearch, kind: Kind, id: Int) {
case kind {
Function(kinds, return_value) ->
kinds
|> add_index
|> postpend(#(return_value, option.Some(id)))
|> do_add(searches, _)
Function(kinds, return_value) -> {
let kinds = postpend(kinds, return_value)
do_add(searches, kinds, id)
}
_ -> searches
}
}

fn do_find(searches: TypeSearch, kinds: List(Kind)) {
/// Get the underlying ending Keys for a Kind, associated with its local
/// environment for free variables.
fn get_next_tree(
keys: Keys,
kind: Kind,
env: Dict(Int, String),
) -> List(#(Keys, Dict(Int, String))) {
case kind {
parse.DiscardName -> {
dict.values(keys.keys)
|> list.map(pair.new(_, env))
}
parse.Index(_value, index) -> {
case dict.get(env, index) {
Ok(content) -> {
dict.get(keys.keys, content)
|> result.map(pair.new(_, env))
|> result.map(list.wrap)
|> result.unwrap([])
}
Error(_) -> {
let existing_values = dict.values(env)
dict.keys(keys.keys)
|> list.filter(fn(a) { int.parse(a) |> result.is_ok })
|> list.filter(fn(a) { !list.contains(existing_values, a) })
|> list.flat_map(fn(a) {
get_next_tree(keys, kind, dict.insert(env, index, a))
})
}
}
}
parse.Custom(value, params) ->
case dict.get(keys.keys, value) {
Error(_) -> []
Ok(keys) -> {
use envs, kind <- list.fold(params, [#(keys, env)])
use env <- list.flat_map(envs)
let #(key, env) = env
get_next_tree(key, kind, env)
}
}
parse.Function(kinds, return) -> {
let kinds = postpend(kinds, return)
case dict.get(keys.keys, "fn") {
Error(_) -> []
Ok(keys) -> {
use envs, kind <- list.fold(kinds, [#(keys, env)])
use env <- list.flat_map(envs)
let #(key, env) = env
get_next_tree(key, kind, env)
}
}
}
parse.Tuple(kinds) -> {
case dict.get(keys.keys, "#()") {
Error(_) -> []
Ok(keys) -> {
use envs, kind <- list.fold(kinds, [#(keys, env)])
use env <- list.flat_map(envs)
let #(key, env) = env
get_next_tree(key, kind, env)
}
}
}
}
}

fn find_next_tree(
keys: Keys,
kind: Kind,
kinds: List(Kind),
env: Dict(Int, String),
) -> List(Int) {
case kind {
parse.DiscardName -> {
let values = get_next_tree(keys, kind, env)
use #(keys, env) <- list.flat_map(values)
option.map(keys.next, do_find(_, kinds, env))
|> option.unwrap([])
}
parse.Index(_value, _index) -> {
let values = get_next_tree(keys, kind, env)
use #(keys, env) <- list.flat_map(values)
option.map(keys.next, do_find(_, kinds, env))
|> option.unwrap([])
}
parse.Custom(value, params) ->
case dict.get(keys.keys, value) {
Error(_) -> []
Ok(keys) -> {
list.fold(params, [#(keys, env)], fn(acc, param) {
list.flat_map(acc, fn(a) { get_next_tree(a.0, param, a.1) })
})
|> list.flat_map(fn(val) {
let #(key, env) = val
option.map(key.next, do_find(_, kinds, env)) |> option.unwrap([])
})
}
}
parse.Function(kinds, return) -> {
let kinds = postpend(kinds, return)
case dict.get(keys.keys, "fn") {
Error(_) -> []
Ok(keys) -> {
list.fold(kinds, [#(keys, env)], fn(acc, param) {
list.flat_map(acc, fn(a) { get_next_tree(a.0, param, a.1) })
})
|> list.flat_map(fn(val) {
let #(key, env) = val
option.map(key.next, do_find(_, kinds, env)) |> option.unwrap([])
})
}
}
}
parse.Tuple(kinds) -> {
case dict.get(keys.keys, "#()") {
Error(_) -> []
Ok(keys) -> {
list.fold(kinds, [#(keys, env)], fn(acc, param) {
list.flat_map(acc, fn(a) { get_next_tree(a.0, param, a.1) })
})
|> list.flat_map(fn(val) {
let #(key, env) = val
option.map(key.next, do_find(_, kinds, env)) |> option.unwrap([])
})
}
}
}
}
}

fn do_find(searches: TypeSearch, kinds: List(Kind), env: Dict(Int, String)) {
case kinds {
[] -> Error(Nil)
[kind] -> dict.get(searches.entries, kind) |> result.map(fn(s) { s.rows })
[kind, ..rest] ->
dict.get(searches.entries, kind) |> result.then(do_find(_, rest))
[] -> searches.rows
[kind, ..rest] -> find_next_tree(searches.keys, kind, rest, env)
}
}

Expand All @@ -73,8 +240,8 @@ pub fn find(searches: TypeSearch, kind: Kind) {
Function(kinds, return_value) ->
kinds
|> postpend(return_value)
|> do_find(searches, _)
|> option.from_result
_ -> option.None
|> do_find(searches, _, dict.new())
|> Ok
_ -> Error(Nil)
}
}
5 changes: 3 additions & 2 deletions apps/backend/src/backend/gleam/type_search/state.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ fn loop(msg: Msg, state: State) -> actor.Next(Msg, State) {
Find(subject, signature) -> {
signature
|> parse.parse_function
|> result.map(type_search.find(state.search, _))
|> result.unwrap(option.None)
|> result.nil_error
|> result.then(type_search.find(state.search, _))
|> option.from_result
|> function.tap(fn(res) { process.send(subject, res) })
actor.continue(state)
}
Expand Down
Loading

0 comments on commit 335638b

Please sign in to comment.