Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New type search #10

Merged
merged 7 commits into from
Aug 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading