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

Add type search #5

Merged
merged 6 commits into from
Jul 6, 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
2 changes: 2 additions & 0 deletions apps/backend/gleam.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ verl = ">= 1.1.1 and < 2.0.0"
wisp = "~> 0.14"
cors_builder = ">= 1.0.0 and < 2.0.0"
decipher = ">= 1.2.0 and < 2.0.0"
chomp = ">= 0.1.0 and < 1.0.0"
glexer = ">= 1.0.1 and < 2.0.0"

[dev-dependencies]
gleeunit = "~> 1.0"
Expand Down
6 changes: 5 additions & 1 deletion apps/backend/manifest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ packages = [
{ name = "aws4_request", version = "0.1.1", build_tools = ["gleam"], requirements = ["gleam_crypto", "gleam_http", "gleam_stdlib"], otp_app = "aws4_request", source = "hex", outer_checksum = "90B1DB6E2A7F0396CD4713850B14B3A910331B5BA76D051E411D1499AAA2EA9A" },
{ name = "backoff", version = "1.1.6", build_tools = ["rebar3"], requirements = [], otp_app = "backoff", source = "hex", outer_checksum = "CF0CFFF8995FB20562F822E5CC47D8CCF664C5ECDC26A684CBE85C225F9D7C39" },
{ name = "birl", version = "1.6.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "ranger"], otp_app = "birl", source = "hex", outer_checksum = "976CFF85D34D50F7775896615A71745FBE0C325E50399787088F941B539A0497" },
{ name = "chomp", version = "0.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "chomp", source = "hex", outer_checksum = "C87304897B4D4DEA69420DB2FF88B087673AAE9EC09CA8A0FBF4675F605767C2" },
{ name = "cors_builder", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_stdlib", "mist", "wisp"], otp_app = "cors_builder", source = "hex", outer_checksum = "951B5B648E958BD6181A6EED98BCA4EEB302B83DC7DCE2954B3462114209EC43" },
{ name = "decipher", version = "1.2.0", build_tools = ["gleam"], requirements = ["birl", "gleam_json", "gleam_stdlib", "stoiridh_version"], otp_app = "decipher", source = "hex", outer_checksum = "9F1B5C6FF0D798046E4E0EF87D09DD729324CB72BD7F0D4152B797324D51223E" },
{ name = "dot_env", version = "0.5.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "simplifile"], otp_app = "dot_env", source = "hex", outer_checksum = "AF5C972D6129F67AF3BB00134AB2808D37111A8D61686CFA86F3ADF652548982" },
Expand All @@ -25,6 +26,7 @@ packages = [
{ name = "gleam_pgo", version = "0.7.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "pgo"], source = "local", path = "../../packages/pgo" },
{ name = "gleam_stdlib", version = "0.36.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "C0D14D807FEC6F8A08A7C9EF8DFDE6AE5C10E40E21325B2B29365965D82EB3D4" },
{ name = "gleeunit", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "72CDC3D3F719478F26C4E2C5FED3E657AC81EC14A47D2D2DEBB8693CA3220C3B" },
{ name = "glexer", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glexer", source = "hex", outer_checksum = "BD477AD657C2B637FEF75F2405FAEFFA533F277A74EF1A5E17B55B1178C228FB" },
{ name = "glisten", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib"], otp_app = "glisten", source = "hex", outer_checksum = "CF3A9383E9BA4A8CBAF2F7B799716290D02F2AC34E7A77556B49376B662B9314" },
{ name = "hpack_erl", version = "0.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "hpack", source = "hex", outer_checksum = "D6137D7079169D8C485C6962DFE261AF5B9EF60FBC557344511C1E65E3D95FB0" },
{ name = "logging", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "logging", source = "hex", outer_checksum = "A996064F04EF6E67F0668FD0ACFB309830B05D0EE3A0C11BBBD2D4464334F792" },
Expand All @@ -50,8 +52,9 @@ packages = [
[requirements]
aws4_request = { version = ">= 0.1.1 and < 1.0.0" }
birl = { version = "~> 1.6" }
chomp = { version = ">= 0.1.0 and < 1.0.0" }
cors_builder = { version = ">= 1.0.0 and < 2.0.0" }
decipher = { version = ">= 1.2.0 and < 2.0.0"}
decipher = { version = ">= 1.2.0 and < 2.0.0" }
dot_env = { version = "~> 0.5" }
gleam_erlang = { version = "~> 0.25" }
gleam_hexpm = { version = "~> 1.0" }
Expand All @@ -63,6 +66,7 @@ gleam_package_interface = { version = ">= 1.0.0 and < 2.0.0" }
gleam_pgo = { path = "../../packages/pgo" }
gleam_stdlib = { version = "~> 0.34 or ~> 1.0" }
gleeunit = { version = "~> 1.0" }
glexer = { version = ">= 1.0.1 and < 2.0.0"}
mist = { version = ">= 1.0.0 and < 2.0.0" }
pgo = { version = "~> 0.14" }
pprint = { version = ">= 1.0.3 and < 2.0.0" }
Expand Down
15 changes: 14 additions & 1 deletion apps/backend/src/api/signatures.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ import backend/gleam/generate/types.{
constant_to_json, function_to_json, type_alias_to_json,
type_definition_to_json,
}
import backend/gleam/type_search/state as type_search
import backend/postgres/queries
import gleam/bool
import gleam/dict
import gleam/erlang/process
import gleam/function
import gleam/json
import gleam/list
import gleam/option.{None, Some}
Expand Down Expand Up @@ -132,20 +135,30 @@ fn upsert_functions(ctx: Context, module: context.Module) {
result.all({
use #(function_name, function) <- list.map(all_functions)
use gen <- result.try(function_to_json(ctx, function_name, function))
let signature = function_to_string(function_name, function)
queries.upsert_package_type_fun_signature(
db: ctx.db,
name: function_name,
kind: queries.Function,
documentation: function.documentation,
metadata: Some(function.implementations)
|> metadata.generate(function.deprecation, _),
signature: function_to_string(function_name, function),
signature: signature,
json_signature: gen.0,
parameters: gen.1,
module_id: module.id,
deprecation: function.deprecation,
implementations: Some(function.implementations),
)
|> function.tap(fn(content) {
case ctx.type_search_subject, content {
option.Some(subject), Ok([id]) -> {
process.send(subject, type_search.Add(signature, id))
content
}
_, _ -> content
}
})
})
}

Expand Down
10 changes: 10 additions & 0 deletions apps/backend/src/backend.gleam
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import backend/config
import backend/gleam/type_search/state as type_search
import backend/postgres/postgres
import backend/router
import dot_env
Expand All @@ -25,6 +26,15 @@ pub fn main() {
logger.set_level(cnf.level)
setup.radiate()

let assert Ok(subject) = type_search.init(ctx.db)
// let assert Ok(_) =
// supervisor.start(fn(children) {
// use _ <- function.tap(children)
// supervisor.add(children, { supervisor.worker(fn(_) { Ok(subject) }) })
// })

let ctx = ctx |> config.add_type_search_subject(subject)

let assert Ok(_) =
router.handle_request(_, ctx)
|> wisp.mist_handler(secret_key_base)
Expand Down
8 changes: 8 additions & 0 deletions apps/backend/src/backend/config.gleam
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import backend/gleam/type_search/state as type_search
import gleam/erlang/os
import gleam/erlang/process.{type Subject}
import gleam/int
import gleam/option.{type Option}
import gleam/pgo
import gleam/result
import wisp
Expand All @@ -16,6 +19,7 @@ pub type Context {
hex_api_key: String,
github_token: String,
env: Environment,
type_search_subject: Option(Subject(type_search.Msg)),
)
}

Expand Down Expand Up @@ -66,3 +70,7 @@ pub fn scaleway_keys() {
let assert Ok(secret_key) = os.get_env("SCALEWAY_SECRET_KEY")
#(access_key, secret_key)
}

pub fn add_type_search_subject(context, subject) {
Context(..context, type_search_subject: option.Some(subject))
}
4 changes: 4 additions & 0 deletions apps/backend/src/backend/gleam/context.gleam
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import backend/gleam/type_search/state as type_search
import gleam/dict.{type Dict}
import gleam/erlang/process.{type Subject}
import gleam/option.{type Option}
import gleam/package_interface
import gleam/pgo
import tom
Expand All @@ -11,6 +14,7 @@ pub type Context {
/// Allow to bypass parameters relations if activated.
/// This allows to ignore internals for example.
ignore_parameters_errors: Bool,
type_search_subject: Option(Subject(type_search.Msg)),
)
}

Expand Down
166 changes: 166 additions & 0 deletions apps/backend/src/backend/gleam/parse.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import chomp.{do, return}
import chomp/lexer
import chomp/span
import gleam/dict.{type Dict}
import gleam/list
import gleam/option.{None, Some}
import gleam/pair
import gleam/result
import glexer
import glexer/token

pub type Kind {
Index(String, Int)
Custom(String, List(Kind))
Function(List(Kind), Kind)
Tuple(List(Kind))
}

fn parse_qualified_name() {
parse_upper_name()
|> chomp.then(fn(content) {
use content_ <- do(chomp.optional(parse_type_parameter()))
let assert Custom(c, _) = content
chomp.succeed(Custom(c, option.unwrap(content_, [])))
})
}

fn parse_upper_name() {
use _ <- do({
parse_name()
|> chomp.sequence(chomp.token(token.Slash))
|> chomp.optional
})
use _ <- do(chomp.optional(chomp.token(token.Dot)))
use token <- chomp.take_map()
case token {
token.UpperName(content) -> Some(Custom(content, []))
_ -> None
}
}

fn parse_name() {
use token <- chomp.take_map()
case token {
token.Name(content) -> Some(Index(content, 0))
_ -> None
}
}

fn parse_label() {
chomp.backtrackable({
use name <- do(
chomp.take_map(fn(token) {
case token {
token.Name(content) -> Some(Index(content, 0))
_ -> None
}
}),
)
use _ <- do(chomp.token(token.Colon))
return(name)
})
}

fn parse_type_parameter() {
use _ <- do(chomp.token(token.LeftParen))
use content <- do(parse_kind() |> chomp.sequence(chomp.token(token.Comma)))
use _ <- do(chomp.token(token.RightParen))
return(content)
}

fn parse_return() {
use _ <- do(chomp.token(token.RightArrow))
use content <- do(parse_kind())
return(content)
}

fn parse_tuple() {
use _ <- do(chomp.token(token.Hash))
use _ <- do(chomp.token(token.LeftParen))
use content <- do(parse_kind() |> chomp.sequence(chomp.token(token.Comma)))
use _ <- do(chomp.token(token.RightParen))
return(Tuple(content))
}

fn parse_fn() {
use _ <- do(chomp.token(token.Fn))
use _ <- do(chomp.optional(parse_name()))
use _ <- do(chomp.optional(chomp.token(token.LeftParen)))
use content <- do(
{
use _ <- do(chomp.optional(parse_label()))
parse_kind()
}
|> chomp.sequence(chomp.token(token.Comma)),
)
use _ <- do(chomp.optional(chomp.token(token.RightParen)))
use content_ <- do(parse_return())
return(Function(content, content_))
}

fn parse_kind() {
chomp.one_of([
parse_fn(),
parse_tuple(),
chomp.backtrackable(parse_qualified_name()),
parse_name(),
])
}

pub fn parse_function(input: String) {
let tokens =
input
|> glexer.new
|> glexer.lex
|> list.map(fn(elem) {
lexer.Token(span.Span({ elem.1 }.byte_offset, 0, 0, 0), "", elem.0)
})
parse_fn()
|> chomp.run(tokens, _)
|> result.map(replace_indexed(#(dict.new(), 0), _))
|> result.map(pair.first)
}

fn replace_indexed(
indexes: #(Dict(String, Int), Int),
kind: Kind,
) -> #(Kind, #(Dict(String, Int), Int)) {
let #(indexes, current) = indexes
case kind {
Index(name, _) -> {
case dict.get(indexes, name) {
Ok(value) -> #(Index(name, value), #(indexes, current))
Error(_) -> #(Index(name, current), #(
dict.insert(indexes, name, current),
current + 1,
))
}
}
Custom(name, kinds) -> {
let #(new_kinds, accs) =
list.fold(kinds, #([], #(indexes, current)), fn(acc, val) {
let res = replace_indexed(acc.1, val)
#([res.0, ..acc.0], res.1)
})
#(Custom(name, list.reverse(new_kinds)), accs)
}
Function(kinds, return_value) -> {
let #(new_kinds, accs) =
list.fold(kinds, #([], #(indexes, current)), fn(acc, val) {
let res = replace_indexed(acc.1, val)
#([res.0, ..acc.0], res.1)
})
let #(return_value, accs) = replace_indexed(accs, return_value)
#(Function(list.reverse(new_kinds), return_value), accs)
}
Tuple(kinds) -> {
let #(new_kinds, accs) =
list.fold(kinds, #([], #(indexes, current)), fn(acc, val) {
let res = replace_indexed(acc.1, val)
#([res.0, ..acc.0], res.1)
})
#(Tuple(list.reverse(new_kinds)), accs)
}
}
}
80 changes: 80 additions & 0 deletions apps/backend/src/backend/gleam/type_search.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import backend/gleam/parse.{type Kind, Function}
import gleam/dict.{type Dict}
import gleam/list
import gleam/option
import gleam/result

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

pub fn empty() {
TypeSearch(dict.new(), [])
}

fn postpend(list: List(a), value: a) {
list
|> list.reverse
|> list.prepend(value)
|> 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)))) {
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]
}
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) }
}
}

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, _)
_ -> searches
}
}

fn do_find(searches: TypeSearch, kinds: List(Kind)) {
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))
}
}

pub fn find(searches: TypeSearch, kind: Kind) {
case kind {
Function(kinds, return_value) ->
kinds
|> postpend(return_value)
|> do_find(searches, _)
|> option.from_result
_ -> option.None
}
}
Loading
Loading