diff --git a/src/compiler/program.gleam b/src/compiler/program.gleam index 4c3bb7f..58fd89d 100644 --- a/src/compiler/program.gleam +++ b/src/compiler/program.gleam @@ -5,17 +5,19 @@ //// filesystem. //// It does not write to the filesystem. -import compiler/python import errors +import filepath import glance import gleam/dict import gleam/list import gleam/result -import pprint import simplifile pub type GleamProgram { - GleamProgram(modules: dict.Dict(String, glance.Module)) + GleamProgram( + source_directory: String, + modules: dict.Dict(String, glance.Module), + ) } pub type CompiledProgram { @@ -24,9 +26,22 @@ pub type CompiledProgram { /// Load the entry_point file and recursively load and parse any modules it ///returns. -pub fn load_program(entry_point: String) -> Result(GleamProgram, errors.Error) { - GleamProgram(modules: dict.new()) - |> load_module(entry_point) +pub fn load_program( + source_directory: String, +) -> Result(GleamProgram, errors.Error) { + source_directory + |> simplifile.is_directory + |> result.map_error(errors.FileOrDirectoryNotFound(source_directory, _)) + |> result.try(fn(_) { find_entrypoint(source_directory) }) + |> result.try(load_module(GleamProgram(source_directory, dict.new()), _)) +} + +pub fn find_entrypoint(source_directory: String) -> Result(String, errors.Error) { + let base_name = filepath.base_name(source_directory) + let entrypoint = base_name <> ".gleam" + simplifile.is_file(filepath.join(source_directory, entrypoint)) + |> result.replace(entrypoint) + |> result.map_error(errors.FileOrDirectoryNotFound(entrypoint, _)) } /// Parse the module and add it to the program's modules, if it can be parsed. @@ -35,12 +50,12 @@ fn load_module( program: GleamProgram, module_path: String, ) -> Result(GleamProgram, errors.Error) { - pprint.debug(module_path) case dict.get(program.modules, module_path) { Ok(_) -> Ok(program) Error(_) -> { let module_result = module_path + |> filepath.join(program.source_directory, _) |> simplifile.read |> result.map_error(errors.FileReadError(module_path, _)) |> result.try(parse(_, module_path)) @@ -81,9 +96,8 @@ fn add_module( module_path: String, module_contents: glance.Module, ) -> GleamProgram { - GleamProgram(modules: dict.insert( - program.modules, - module_path, - module_contents, - )) + GleamProgram( + ..program, + modules: dict.insert(program.modules, module_path, module_contents), + ) } diff --git a/src/errors.gleam b/src/errors.gleam index e602719..e91b337 100644 --- a/src/errors.gleam +++ b/src/errors.gleam @@ -3,16 +3,23 @@ import internal/errors as internal import simplifile pub type Error { - FileReadError(module: String, error: simplifile.FileError) - FileWriteError(module: String, error: simplifile.FileError) + FileOrDirectoryNotFound(path: String, error: simplifile.FileError) + FileReadError(path: String, error: simplifile.FileError) + FileWriteError(path: String, error: simplifile.FileError) + DeleteError(path: String, error: simplifile.FileError) + MkdirError(path: String, error: simplifile.FileError) GlanceParseError(error: glance.Error, module: String, contents: String) } pub fn format_error(error: Error) -> String { case error { + FileOrDirectoryNotFound(filename, _) -> + "File or directory not found " <> filename FileReadError(filename, simplifile.Enoent) -> "File not found " <> filename FileReadError(filename, _) -> "Unable to read " <> filename FileWriteError(filename, _) -> "Unable to write " <> filename + DeleteError(filename, _) -> "Unable to delete " <> filename + MkdirError(filename, _) -> "Unable to mkdir " <> filename GlanceParseError(error, filename, contents) -> internal.format_glance_error(error, filename, contents) } diff --git a/src/macabre.gleam b/src/macabre.gleam index 2bc0b09..a3b2507 100644 --- a/src/macabre.gleam +++ b/src/macabre.gleam @@ -2,56 +2,47 @@ import argv import compiler import compiler/program import errors +import filepath +import gleam/dict import gleam/io import gleam/result -import gleam/string -import internal/errors as error_functions import output -import pprint -import simplifile pub fn usage(message: String) -> Nil { io.println("Usage: macabre \n\n" <> message) } -fn compile_module(filename: String) -> Result(Nil, String) { - simplifile.read(filename) - |> result.replace_error("Unable to read '" <> filename <> "'") - |> result.try(fn(content) { - content - |> compiler.compile - |> result.map_error(error_functions.format_glance_error( - _, - filename, - content, - )) - }) - |> result.try(output.write(_, output.replace_extension(filename))) +fn write_program( + program: program.CompiledProgram, + build_directory: String, +) -> Result(Nil, errors.Error) { + build_directory + |> output.delete + |> result.try(fn(_) { output.create_directory(build_directory) }) + |> result.try(fn(_) { output.write_prelude_file(build_directory) }) |> result.try(fn(_) { - // TODO: eventually, this has to be output to a base directory, - // not one copy per module. - filename - |> output.replace_file("gleam_builtins.py") - |> output.write_prelude_file + dict.fold(program.modules, Ok(Nil), fn(state, name, module) { + result.try(state, fn(_) { + build_directory + |> filepath.join(name) + |> output.replace_extension() + |> output.write(module, _) + }) + }) }) } pub fn main() { case argv.load().arguments { [] -> usage("Not enough arguments") - [input] -> - case string.ends_with(input, ".gleam") { - False -> usage(input <> ":" <> " Not a gleam input file") - True -> { - input - |> program.load_program - |> result.map(compiler.compile_program) - |> result.try(output.write_program(_, "build")) - |> result.map_error(output.write_error) - |> result.unwrap_both - // both nil - } - } + [directory] -> + directory + |> program.load_program + |> result.map(compiler.compile_program) + |> result.try(write_program(_, filepath.join(directory, "build/"))) + |> result.map_error(output.write_error) + |> result.unwrap_both + // both nil [_, _, ..] -> usage("Too many arguments") } } diff --git a/src/output.gleam b/src/output.gleam index a34278e..e1b7efa 100644 --- a/src/output.gleam +++ b/src/output.gleam @@ -1,4 +1,3 @@ -import compiler/program import errors import filepath import gleam/io @@ -11,7 +10,7 @@ pub fn write(contents: String, filename: String) -> Result(Nil, errors.Error) { |> result.map_error(errors.FileWriteError(filename, _)) } -fn replace_extension(filename: String) -> String { +pub fn replace_extension(filename: String) -> String { filename |> filepath.strip_extension <> ".py" } @@ -33,17 +32,12 @@ pub fn compile_result(filename: String, result: Result(Nil, String)) -> Nil { } } -pub fn write_prelude_file(filepath: String) -> Result(Nil, String) { - filepath - |> simplifile.write(python_prelude.gleam_builtins) - |> result.replace_error("Unable to write prelude") -} +pub fn write_prelude_file(build_directory: String) -> Result(Nil, errors.Error) { + let full_path = filepath.join(build_directory, "gleam_builtins.py") -pub fn write_program( - program: program.CompiledProgram, - directory: String, -) -> Result(Nil, errors.Error) { - todo + full_path + |> simplifile.write(python_prelude.gleam_builtins) + |> result.map_error(errors.FileWriteError(full_path, _)) } pub fn write_error(error: errors.Error) -> Nil { @@ -51,3 +45,12 @@ pub fn write_error(error: errors.Error) -> Nil { |> errors.format_error |> io.println } + +pub fn delete(path: String) -> Result(Nil, errors.Error) { + simplifile.delete_all([path]) |> result.map_error(errors.DeleteError(path, _)) +} + +pub fn create_directory(path) -> Result(Nil, errors.Error) { + simplifile.create_directory(path) + |> result.map_error(errors.MkdirError(path, _)) +}