Current section

Files

Jump to
deriv src deriv.gleam
Raw

src/deriv.gleam

import gleam/option.{Some, None}
import gleam/dict.{type Dict}
import gleam/int
import gleam/result
import gleam/list
import gleam/string
import glance.{type CustomType, type Import, Import}
import gleam/regexp
import simplifile
import shellout
import tom
import deriv/types.{type File, File, type Output, Output, OutputInline, type Write, Write, type GenFunc, type Gen, Gen, type Derivation, Derivation, type DerivFieldOpts, type ModuleReader}
import deriv/parser
import deriv/json as deriv_json
import deriv/unify as deriv_unify
import deriv/util
import gleam/io
// import decode.{type Decoder}
// import gleam/json.{type Json}
// pub type A {
// A(
// b: B,
// )
// } //$ derive json(decode,encode)
// pub type B {
// B(
// x: String,
// )
// } //$ derive json(decode,encode)
// pub fn decoder_a() -> Decoder(A) {
// decode.into({
// use b <- decode.parameter
// A(b:)
// })
// |> decode.field("b", decoder_b())
// }
// pub fn decoder_b() -> Decoder(B) {
// decode.into({
// use x <- decode.parameter
// B(x:)
// })
// |> decode.field("x", decode.string)
// }
// pub fn encode_a(value: A) -> Json {
// json.object([
// #("b", encode_b(value.b)),
// ])
// }
// pub fn encode_b(value: B) -> Json {
// json.object([
// #("x", json.string(value.x)),
// ])
// }
const all_gen_funcs: List(#(String, GenFunc)) =
[
#("json", deriv_json.gen),
#("unify", deriv_unify.gen),
]
pub fn main() {
// let r1 =
// [
// "NOPE",
// "{\"var1\":\"yeet\"}",
// ]
// |> list.map(fn(str) {
// json.decode(from: str, using: d.from(mvar.d_t(), _))
// |> io.debug
// })
// panic as "DONE"
let filepaths = find_project_src_gleam_filepaths()
filepaths
|> load_files
|> gen_derivs(util.fetch_module)
|> build_writes
|> perform_file_writes
}
fn find_project_src_gleam_filepaths() -> List(String) {
let assert Ok(output) = shellout.command(in: ".", run: "find", with: ["src", "-name", "*.gleam"], opt: [])
output
|> string.trim
|> string.split("\n")
}
// GEN DERIVS
fn load_files(filepaths: List(String)) -> List(File) {
filepaths
|> list.index_map(fn(path, idx) {
read_file(path, idx)
})
}
fn project_name() -> String {
let assert Ok(output) = shellout.command(in: ".", run: "cat", with: ["gleam.toml"], opt: [])
let assert Ok(config) = tom.parse(output)
case tom.get_string(config, ["name"]) {
Error(_) -> panic as "Cannot determine project name from `gleam.toml`"
Ok(name) -> name
}
}
pub fn gen_derivs(
files: List(File),
module_reader: ModuleReader,
) -> List(Gen) {
let gen_funcs = all_gen_funcs |> dict.from_list
let project_derivs_module_name = project_name() <> "/derivs"
let type_inline_gens =
files
|> list.filter(fn(file) {
file.module != project_derivs_module_name
})
|> list.map(fn(file) {
let file = File(..file, idx: None)
file
|> parse_types_and_derivations
|> list.flat_map(gen_type_derivs(_, file, gen_funcs, module_reader))
})
|> list.flatten
|> list.map(fn(gen) {
Gen(..gen, meta: dict.from_list([#("source", "inline")]))
})
let derivs_file =
list.find(files, fn(file) {
file.module == project_derivs_module_name
})
let derivs_file_import_gens =
case derivs_file {
Ok(file) -> derivs_file_import_gens(file, files, gen_funcs)
_ -> []
}
|> list.map(fn(gen) {
Gen(..gen, meta: dict.from_list([#("source", "import")]))
})
[
type_inline_gens,
derivs_file_import_gens,
]
|> list.flatten
}
fn read_file(filepath: String, idx: Int) -> File {
let assert Ok(src) = simplifile.read(filepath)
let module = file_path_to_gleam_module_str(filepath)
File(module: , src:, idx: Some(idx+1))
}
fn file_path_to_gleam_module_str(path: String) -> String {
let assert Ok(leading_src_slash) = regexp.from_string("^src[/]")
let assert Ok(trailing_dot_gleam) = regexp.from_string("[.]gleam$")
path
|> regexp.replace(each: leading_src_slash, in: _, with: "")
|> regexp.replace(each: trailing_dot_gleam, in: _, with: "")
}
fn gleam_module_str_to_file_path(module: String) -> String {
"src/" <> module <> ".gleam"
}
fn parse_types_and_derivations(file: File) -> List(#(CustomType, List(Derivation), DerivFieldOpts)) {
let parsed =
case glance.module(file.src) {
Error(err) -> {
io.debug(file)
io.debug(err)
panic
}
Ok(x) -> x
}
parsed.custom_types
|> list.map(fn(ct) { ct.definition })
|> list.map(parser.parse_type_with_derivations(_, file.src))
|> result.values
}
fn gen_type_derivs(
x: #(CustomType, List(Derivation), DerivFieldOpts),
file: File,
gen_funcs: Dict(String, GenFunc),
module_reader: ModuleReader,
) -> List(Gen) {
let #(type_, derivs, field_opts) = x
derivs
|> list.map(fn(d) {
case dict.get(gen_funcs, d.name) {
Error(_) -> Error(Nil)
Ok(f) -> {
Ok(f(type_, d, field_opts, file, module_reader))
}
}
})
|> result.values
}
// WRITE TO FILES
pub fn build_writes(xs: List(Gen)) -> List(Write) {
let #(inline_derivs, other_derivs) =
xs
|> list.partition(fn(gen) {
gen.meta
|> dict.get("source")
|> result.map(fn(source) { source == "inline" })
|> result.unwrap(False)
})
let same_file_writes =
inline_derivs
|> build_same_file_writes
let diff_file_writes =
other_derivs
|> build_different_file_writes
[
diff_file_writes,
same_file_writes,
]
|> list.flatten
}
pub fn build_same_file_writes(xs: List(Gen)) -> List(Write) {
xs
|> list.group(fn(gen) {
gen.file.module
})
|> dict.map_values(fn(module, gens) {
let orig_src =
gens
|> list.first
|> result.map(fn(gen) { gen.file.src })
|> fn(r) {
case r {
Ok(src) -> src
Error(_) -> panic as { "`File.src` could not be found for `" <> module <> "`" }
}
}
let output_path = gleam_module_str_to_file_path(module)
let output = OutputInline(module: module, filepath: output_path)
let funcs =
gens
|> list.flat_map(fn(gen) { gen.funcs })
|> list.map(fn(func) { #(util.func_name(func), util.func_str(func)) })
let module_imports: List(Import) = build_module_imports(gens, output)
let deriv_imports: List(Import) = list.flat_map(gens, fn(gen) { gen.imports })
let all_imports: List(Import) = [module_imports, deriv_imports] |> list.flatten
let func_src_with_imports = util.update_funcs(orig_src, funcs)
let output_src =
func_src_with_imports
|> consolidate_imports_for(all_imports)
|> string.trim
Write(
filepath: output_path,
src: output_src,
output: output,
)
})
|> dict.values
}
pub fn build_different_file_writes(xs: List(Gen)) -> List(Write) {
xs
|> list.group(fn(gen) {
Output(module: gen.file.module, deriv: gen.deriv.name)
})
|> dict.map_values(fn(output, gens) {
let output_path = output_path(output)
let output_src =
gens
|> build_output_src(output)
|> string.trim
Write(
filepath: output_path,
src: output_src,
output: output,
)
})
|> dict.values
}
fn perform_file_writes(xs: List(Write)) -> Nil {
xs
|> list.each(fn(write) {
case write.output {
Output(deriv:, ..) -> {
let dir = string.replace(write.filepath, {deriv <> ".gleam"}, "")
let assert Ok(_) = simplifile.create_directory_all(dir)
Nil
}
_ -> Nil
}
let assert Ok(_) = simplifile.write(write.filepath, write.src)
})
}
fn output_path(output: Output) -> String {
case output {
Output(module:, deriv:) ->
[
"src",
"deriv",
module,
deriv <> ".gleam",
]
|> string.join("/")
OutputInline(filepath:, ..) ->
filepath
}
}
fn build_output_src(gens: List(Gen), output: Output) -> String {
case output {
Output(..) -> {
let module_imports = build_module_imports(gens, output)
let deriv_imports = list.flat_map(gens, fn(gen) { gen.imports })
let defs = list.map(gens, fn(gen) { gen.src })
let func_src = string.join(defs, "\n\n")
let all_imports =
[
module_imports,
deriv_imports,
]
|> list.flatten
consolidate_imports_for(func_src, all_imports)
}
OutputInline(..) -> {
// TODO don't think this logic is ever run...
panic as "don't think this logic ever runs..."
// let module_imports = build_module_imports(gens, output)
// let deriv_imports = list.flat_map(gens, fn(gen) { gen.imports })
// let defs = list.map(gens, fn(gen) { gen.src })
// [
// module_imports,
// deriv_imports,
// ]
// |> string.join("\n")
// |> fn(imports) { [imports] }
// |> list.append(defs)
// |> string.join("\n\n")
}
}
}
fn build_module_imports(gens: List(Gen), _output: Output) -> List(Import) {
let files =
gens
|> list.map(fn(g) { g.file })
|> list.unique
list.flat_map(files, fn(file) {
case file.idx {
Some(idx) ->
Import(
module: file.module,
alias: Some(glance.Named("m" <> int.to_string(idx))),
unqualified_types: [],
unqualified_values: [],
)
// "import MODULE as mINDEX"
// |> string.replace(each: "MODULE", with: file.module)
// |> string.replace(each: "INDEX", with: idx |> int.to_string)
|> fn(x) { [x] }
None ->
[]
}
})
}
pub fn consolidate_imports_for(src: String, add add_imports: List(Import)) -> String {
let assert Ok(module) =
case glance.module(src) {
Error(err) -> {
io.debug(err)
io.println(src)
panic as "Failed to parse the above source with `glance.module`"
}
ok -> ok
}
let curr_imports =
module.imports
|> list.map(fn(d) { d.definition })
let new_imports =
[ curr_imports, add_imports ]
|> list.flatten
|> consolidate_imports
|> list.map(import_src)
|> string.join("\n")
|> string.trim
let src_without_imports =
src
|> string.split("\n")
|> list.reverse
|> list.take_while(fn(str) { !string.starts_with(str, "import") })
|> list.reverse
|> string.join("\n")
|> string.trim
[
new_imports,
src_without_imports,
]
|> string.join("\n\n")
}
pub fn consolidate_imports(all_imports: List(Import)) -> List(Import) {
all_imports
|> list.group(fn(i) { i.module })
|> dict.to_list
|> list.map(fn(x) {
let #(module, imports) = x
let alias =
imports
|> list.map(fn(i) { i.alias })
|> option.values
|> fn(aliases) {
case list.unique(aliases) {
[] -> None
[alias] -> Some(alias)
_ -> panic as {
io.debug(aliases)
"0 or 1 aliases allowed, but for module `" <> module <> "` multiple aliases found (see above)"
}
}
}
let unqualified_types =
imports
|> list.flat_map(fn(i) { i.unqualified_types })
|> list.unique
let unqualified_values =
imports
|> list.flat_map(fn(i) { i.unqualified_values })
|> list.unique
Import(
module:,
alias:,
unqualified_types:,
unqualified_values:,
)
})
|> list.sort(fn(a,b) { string.compare(a.module, b.module) })
}
fn unqualified_import_str(uqi: glance.UnqualifiedImport, is_type is_type: Bool) -> String {
let type_ =
case is_type {
True -> "type "
False -> ""
}
let alias =
case uqi.alias {
Some(alias) -> " as " <> alias
None -> ""
}
type_ <> uqi.name <> alias
}
fn import_src(i: Import) -> String {
let alias =
case i.alias {
Some(glance.Named(name)) -> "as " <> name
Some(glance.Discarded(name)) -> "as _" <> name
None -> ""
}
let types =
i.unqualified_types
|> list.sort(fn(a,b) { string.compare(a.name, b.name) })
|> list.map(unqualified_import_str(_, is_type: True))
let funcs =
i.unqualified_values
|> list.sort(fn(a,b) { string.compare(a.name, b.name) })
|> list.map(unqualified_import_str(_, is_type: False))
let types_and_constructors =
case list.append(types, funcs) {
[] -> ""
xs -> {
let str = string.join(xs, ", ")
".{" <> str <> "}"
}
}
let module_with_types_and_constructors =
i.module <> types_and_constructors
[
"import",
module_with_types_and_constructors,
alias,
]
|> list.filter(fn(str) { str != "" })
|> string.join(" ")
}
pub fn stop_warning() { io.debug("") }
///// ///// ///// ///// ///// /////
fn derivs_file_import_gens(
derivs_file: File,
all_files: List(File),
gen_funcs: Dict(String, GenFunc),
) -> List(Gen) {
let assert Ok(module) = glance.module(derivs_file.src)
write_suppress_warning_type_aliases_to_file(module)
module.imports
|> list.map(fn(i) { i.definition })
|> list.map(parser.parse_import_with_derivations(_, derivs_file.src))
|> result.values
|> list.flat_map(fn(x) {
let #(import_, derivs) = x
let #(file, types_and_derivs) = load_types_from_file(import_, derivs, all_files)
types_and_derivs
|> list.flat_map(gen_type_derivs(_, file, gen_funcs, util.fetch_module))
})
}
fn load_types_from_file(import_: glance.Import, derivs: List(Derivation), all_files: List(File)) -> #(File, List(#(CustomType, List(Derivation), DerivFieldOpts))) {
let assert Ok(file) = list.find(all_files, fn(f) { f.module == import_.module })
let assert Ok(module) = glance.module(file.src)
let type_names =
import_.unqualified_types
|> list.map(fn(i) { i.name })
let types_and_derivs =
module.custom_types
|> list.filter(fn(t) { list.contains(type_names, t.definition.name) })
|> list.map(fn(t) {
#(t.definition, derivs, dict.new())
})
#(file, types_and_derivs)
}
fn write_suppress_warning_type_aliases_to_file(module: glance.Module) -> Nil {
let src =
module
|> suppress_warnings_types_to_write
|> list.map(fn(t) { "pub type SuppressWarnings" <> t <> " = " <> t })
|> string.join("\n")
let project_derivs_filepath = "src/" <> project_name() <> "/derivs.gleam"
let assert Ok(_) = simplifile.append(project_derivs_filepath, src)
Nil
}
fn suppress_warnings_types_to_write(module: glance.Module) -> List(String) {
let import_types =
module.imports
|> list.flat_map(fn(i) {
i.definition.unqualified_types
|> list.map(fn(t) {
t.name
})
})
let type_aliases =
module.type_aliases
|> list.map(fn(ta) {
ta.definition.name
})
import_types
|> list.filter(fn(t) {
!list.contains(type_aliases, "SuppressWarnings" <> t)
})
}