A Gleam codegen library in Gleam

Initial Commit

Signed-off-by: Naomi Roberts <mia@naomieow.xyz>

lesbian.skin 0c5e1794

0/0
Waiting for spindle ...
+6
.gitignore
··· 1 + *.beam 2 + *.ez 3 + /build 4 + erl_crash.dump 5 + .envrc 6 + .direnv/
+9
.tangled/workflows/test.yaml
··· 1 + when: 2 + - event: ["push", "pull-request"] 3 + branch: main 4 + 5 + engine: "nixery" 6 + 7 + steps: 8 + - test: 9 + command: "nix run .#test"
+56
README.md
··· 1 + # stare 2 + 3 + [![Package Version](https://img.shields.io/hexpm/v/stare)](https://hex.pm/packages/stare) 4 + [![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/stare/) 5 + 6 + ```sh 7 + gleam add stare@1 8 + ``` 9 + ```gleam 10 + import gleam/io 11 + import stare 12 + 13 + pub fn main() -> Nil { 14 + let assert Ok(code) = stare.module( 15 + imports: [ 16 + stare.import_( 17 + module: ["gleam", "io"], 18 + types: [], 19 + constructors: [], 20 + functions: [], 21 + alias: None, 22 + ), 23 + ], 24 + functions: [ 25 + stare.function( 26 + name: "main", 27 + public: True, 28 + parameters: [], 29 + return_type: Some(type_.nil()), 30 + statements: [ 31 + stare.function_call( 32 + module: Some("io"), 33 + function: "println", 34 + arguments: [stare.value(value.string("Hello World!"))], 35 + ), 36 + ], 37 + ), 38 + ], 39 + ) 40 + io.println(code) 41 + // import gleam/io 42 + // 43 + // pub fn main() -> Nil { 44 + // io.println("Hello World!") 45 + //} 46 + } 47 + ``` 48 + 49 + Further documentation can be found at <https://hexdocs.pm/stare>. 50 + 51 + ## Development 52 + 53 + ```sh 54 + gleam run # Run the project 55 + gleam test # Run the tests 56 + ```
+27
flake.lock
··· 1 + { 2 + "nodes": { 3 + "nixpkgs": { 4 + "locked": { 5 + "lastModified": 1757034884, 6 + "narHash": "sha256-PgLSZDBEWUHpfTRfFyklmiiLBE1i1aGCtz4eRA3POao=", 7 + "owner": "NixOS", 8 + "repo": "nixpkgs", 9 + "rev": "ca77296380960cd497a765102eeb1356eb80fed0", 10 + "type": "github" 11 + }, 12 + "original": { 13 + "owner": "NixOS", 14 + "ref": "nixpkgs-unstable", 15 + "repo": "nixpkgs", 16 + "type": "github" 17 + } 18 + }, 19 + "root": { 20 + "inputs": { 21 + "nixpkgs": "nixpkgs" 22 + } 23 + } 24 + }, 25 + "root": "root", 26 + "version": 7 27 + }
+43
flake.nix
··· 1 + { 2 + inputs = { 3 + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; 4 + }; 5 + 6 + outputs = {nixpkgs, ...}: let 7 + lib = nixpkgs.lib; 8 + supportedSystems = ["x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin"]; 9 + forEachSupportedSystem = f: 10 + lib.genAttrs supportedSystems (system: 11 + f { 12 + pkgs = import nixpkgs {inherit system;}; 13 + }); 14 + in { 15 + devShells = forEachSupportedSystem ({pkgs}: { 16 + default = pkgs.mkShell { 17 + packages = with pkgs; [ 18 + gleam 19 + erlang_28 20 + beam28Packages.rebar3 21 + ]; 22 + }; 23 + }); 24 + apps = forEachSupportedSystem ({pkgs}: let 25 + runtimeInputs = with pkgs; [ 26 + gleam 27 + erlang_28 28 + beam28Packages.rebar3 29 + ]; 30 + in { 31 + test = { 32 + type = "app"; 33 + program = "${(pkgs.writeShellApplication { 34 + inherit runtimeInputs; 35 + name = "test"; 36 + text = '' 37 + ${pkgs.gleam}/bin/gleam test 38 + ''; 39 + })}/bin/test"; 40 + }; 41 + }); 42 + }; 43 + }
+21
gleam.toml
··· 1 + name = "stare" 2 + version = "1.0.0" 3 + 4 + # Fill out these fields if you intend to generate HTML documentation or publish 5 + # your project to the Hex package manager. 6 + # 7 + # description = "" 8 + # licences = ["Apache-2.0"] 9 + # repository = { type = "github", user = "", repo = "" } 10 + # links = [{ title = "Website", href = "" }] 11 + # 12 + # For a full reference of all the available options, you can have a look at 13 + # https://gleam.run/writing-gleam/gleam-toml/. 14 + 15 + [dependencies] 16 + gleam_stdlib = ">= 0.44.0 and < 2.0.0" 17 + shellout = ">= 1.7.0 and < 2.0.0" 18 + simplifile = ">= 2.3.0 and < 3.0.0" 19 + 20 + [dev-dependencies] 21 + gleeunit = ">= 1.0.0 and < 2.0.0"
+16
manifest.toml
··· 1 + # This file was generated by Gleam 2 + # You typically do not need to edit this file 3 + 4 + packages = [ 5 + { name = "filepath", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "B06A9AF0BF10E51401D64B98E4B627F1D2E48C154967DA7AF4D0914780A6D40A" }, 6 + { name = "gleam_stdlib", version = "0.63.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "5E216C7D5E8BE22359C9D7DAA2CFBD66039BC12565542F34CD033C5BB57071ED" }, 7 + { name = "gleeunit", version = "1.6.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "FDC68A8C492B1E9B429249062CD9BAC9B5538C6FBF584817205D0998C42E1DAC" }, 8 + { name = "shellout", version = "1.7.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "shellout", source = "hex", outer_checksum = "1BDC03438FEB97A6AF3E396F4ABEB32BECF20DF2452EC9A8C0ACEB7BDDF70B14" }, 9 + { name = "simplifile", version = "2.3.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "0A868DAC6063D9E983477981839810DC2E553285AB4588B87E3E9C96A7FB4CB4" }, 10 + ] 11 + 12 + [requirements] 13 + gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" } 14 + gleeunit = { version = ">= 1.0.0 and < 2.0.0" } 15 + shellout = { version = ">= 1.7.0 and < 2.0.0" } 16 + simplifile = { version = ">= 2.3.0 and < 3.0.0" }
+233
src/stare.gleam
··· 1 + import gleam/list 2 + import gleam/option.{type Option} 3 + import gleam/result 4 + import gleam/string 5 + import gleam/string_tree 6 + import shellout 7 + import simplifile 8 + import stare/type_ 9 + import stare/value 10 + 11 + @internal 12 + pub opaque type Module { 13 + Module(imports: List(Import), functions: List(Function)) 14 + } 15 + 16 + pub fn module( 17 + imports imports: List(Import), 18 + functions functions: List(Function), 19 + ) -> Module { 20 + Module(imports:, functions:) 21 + } 22 + 23 + @internal 24 + pub opaque type Import { 25 + Import( 26 + module: List(String), 27 + types: List(String), 28 + constructors: List(String), 29 + functions: List(String), 30 + alias: Option(String), 31 + ) 32 + } 33 + 34 + pub fn import_( 35 + module module: List(String), 36 + types types: List(String), 37 + constructors constructors: List(String), 38 + functions functions: List(String), 39 + alias alias: Option(String), 40 + ) -> Import { 41 + Import(module:, types:, constructors:, functions:, alias:) 42 + } 43 + 44 + @internal 45 + pub opaque type Parameter { 46 + Parameter(name: String, label: Option(String), type_: Option(type_.Type)) 47 + } 48 + 49 + pub fn parameter( 50 + label label: Option(String), 51 + name name: String, 52 + type_ type_: Option(type_.Type), 53 + ) -> Parameter { 54 + Parameter(name:, label:, type_:) 55 + } 56 + 57 + @internal 58 + pub opaque type Statement { 59 + FunctionCall( 60 + module: Option(String), 61 + function_name: String, 62 + arguments: List(Statement), 63 + ) 64 + Raw(value.Value) 65 + AddInts(value.Value, value.Value) 66 + AddFloats(value.Value, value.Value) 67 + } 68 + 69 + pub fn add_ints(left left: Int, right right: Int) -> Statement { 70 + AddInts(value.int(left), value.int(right)) 71 + } 72 + 73 + pub fn add_floats(left left: Float, right right: Float) -> Statement { 74 + AddFloats(value.float(left), value.float(right)) 75 + } 76 + 77 + pub fn value(value value: value.Value) -> Statement { 78 + Raw(value) 79 + } 80 + 81 + pub fn function_call( 82 + module module: Option(String), 83 + function function_name: String, 84 + arguments arguments: List(Statement), 85 + ) -> Statement { 86 + FunctionCall(module:, function_name:, arguments:) 87 + } 88 + 89 + @internal 90 + pub opaque type Function { 91 + Function( 92 + name: String, 93 + public: Bool, 94 + params: List(Parameter), 95 + type_: Option(type_.Type), 96 + statements: List(Statement), 97 + ) 98 + } 99 + 100 + pub fn function( 101 + name name: String, 102 + public public: Bool, 103 + parameters params: List(Parameter), 104 + return_type type_: Option(type_.Type), 105 + statements statements: List(Statement), 106 + ) -> Function { 107 + Function(name:, public:, params:, type_:, statements:) 108 + } 109 + 110 + pub fn generate_import(import_ import_: Import) -> String { 111 + let Import(module:, types:, constructors:, functions:, alias:) = import_ 112 + let i = 113 + "import " 114 + <> list.fold(module, "", fn(acc, part) { acc <> part <> "/" }) 115 + |> string.drop_end(1) 116 + 117 + let i = case types, constructors, functions { 118 + [], [], [] -> i 119 + _, _, _ -> { 120 + i 121 + <> ".{" 122 + <> list.fold(types, "", fn(acc, type_) { acc <> "type " <> type_ <> "," }) 123 + <> list.fold(constructors, "", fn(acc, constructor) { 124 + acc <> constructor <> "," 125 + }) 126 + <> list.fold(functions, "", fn(acc, function) { acc <> function <> "," }) 127 + <> "}" 128 + } 129 + } 130 + case alias { 131 + option.None -> i 132 + option.Some(alias) -> i <> " as " <> alias 133 + } 134 + } 135 + 136 + pub fn generate_statement(statement statement: Statement) -> String { 137 + case statement { 138 + FunctionCall(module:, function_name:, arguments: _) -> { 139 + string_tree.new() 140 + |> string_tree.append(case module { 141 + option.None -> "" 142 + option.Some(module) -> module <> "." 143 + }) 144 + |> string_tree.append(function_name) 145 + |> string_tree.append("(") 146 + |> string_tree.append( 147 + list.fold(statement.arguments, "", fn(acc, arg) { 148 + acc <> generate_statement(arg) <> "," 149 + }), 150 + ) 151 + |> string_tree.append(")") 152 + |> string_tree.to_string() 153 + } 154 + Raw(value) -> value.to_string(value) 155 + AddInts(left, right) -> 156 + value.to_string(left) <> "+" <> value.to_string(right) 157 + AddFloats(left, right) -> 158 + value.to_string(left) <> "+." <> value.to_string(right) 159 + } 160 + } 161 + 162 + pub fn generate_function(function function: Function) -> String { 163 + string_tree.new() 164 + |> string_tree.append(case function.public { 165 + False -> "" 166 + True -> "pub " 167 + }) 168 + |> string_tree.append("fn ") 169 + |> string_tree.append(function.name <> "(") 170 + |> string_tree.append(") ") 171 + |> string_tree.append(case function.type_ { 172 + option.None -> "" 173 + option.Some(type_) -> "-> " <> type_.to_string(type_:) <> " " 174 + }) 175 + |> string_tree.append("{\n") 176 + |> string_tree.append_tree( 177 + function.statements 178 + |> list.fold(string_tree.new(), fn(acc, statement) { 179 + string_tree.append(acc, generate_statement(statement) <> "\n") 180 + }), 181 + ) 182 + |> string_tree.append("}") 183 + |> string_tree.to_string() 184 + } 185 + 186 + pub fn generate(module: Module) -> String { 187 + let imports = 188 + module.imports 189 + |> list.fold(string_tree.new(), fn(acc, import_) { 190 + string_tree.append(acc, generate_import(import_:) <> "\n") 191 + }) 192 + 193 + let functions = 194 + module.functions 195 + |> list.fold(string_tree.new(), fn(acc, function) { 196 + string_tree.append(acc, generate_function(function:) <> "\n") 197 + }) 198 + 199 + string_tree.new() 200 + |> string_tree.append_tree(imports) 201 + |> string_tree.append("\n") 202 + |> string_tree.append_tree(functions) 203 + |> string_tree.to_string() 204 + } 205 + 206 + pub fn format(code code: String) -> Result(String, Nil) { 207 + use _ <- result.try( 208 + simplifile.create_directory_all("./build/tmp/") 209 + |> result.replace_error(Nil), 210 + ) 211 + use _ <- result.try( 212 + simplifile.write(to: "./build/tmp/format.gleam", contents: code) 213 + |> result.replace_error(Nil), 214 + ) 215 + use _ <- result.try( 216 + shellout.command( 217 + run: "gleam", 218 + with: ["format", "./build/tmp/format.gleam"], 219 + in: ".", 220 + opt: [], 221 + ) 222 + |> result.replace_error(Nil), 223 + ) 224 + use code <- result.try( 225 + simplifile.read(from: "./build/tmp/format.gleam") 226 + |> result.replace_error(Nil), 227 + ) 228 + use _ <- result.try( 229 + simplifile.delete("./build/tmp/format.gleam") 230 + |> result.replace_error(Nil), 231 + ) 232 + Ok(code) 233 + }
+40
src/stare/type_.gleam
··· 1 + @internal 2 + pub type Constructor 3 + 4 + @internal 5 + pub opaque type Type { 6 + CustomType( 7 + name: String, 8 + public: Bool, 9 + opaque_: Bool, 10 + constructors: List(Constructor), 11 + ) 12 + NilType 13 + StringType 14 + } 15 + 16 + @internal 17 + pub fn to_string(type_ type_: Type) -> String { 18 + case type_ { 19 + CustomType(name:, public: _, opaque_: _, constructors: _) -> name 20 + NilType -> "Nil" 21 + StringType -> "String" 22 + } 23 + } 24 + 25 + pub fn nil() -> Type { 26 + NilType 27 + } 28 + 29 + pub fn string() -> Type { 30 + StringType 31 + } 32 + 33 + pub fn custom( 34 + name name: String, 35 + public public: Bool, 36 + opaque_ opaque_: Bool, 37 + constructors constructors: List(Constructor), 38 + ) -> Type { 39 + CustomType(name:, public:, opaque_:, constructors:) 40 + }
+36
src/stare/value.gleam
··· 1 + import gleam/float 2 + import gleam/int 3 + 4 + @internal 5 + pub opaque type Value { 6 + NilValue 7 + StringValue(String) 8 + IntValue(Int) 9 + FloatValue(Float) 10 + } 11 + 12 + @internal 13 + pub fn to_string(value value: Value) -> String { 14 + case value { 15 + NilValue -> "Nil" 16 + StringValue(v) -> "\"" <> v <> "\"" 17 + IntValue(v) -> int.to_string(v) 18 + FloatValue(v) -> float.to_string(v) 19 + } 20 + } 21 + 22 + pub fn nil() -> Value { 23 + NilValue 24 + } 25 + 26 + pub fn string(string string: String) -> Value { 27 + StringValue(string) 28 + } 29 + 30 + pub fn int(int int: Int) -> Value { 31 + IntValue(int) 32 + } 33 + 34 + pub fn float(float float: Float) -> Value { 35 + FloatValue(float) 36 + }
+170
test/stare_test.gleam
··· 1 + import gleam/option.{None, Some} 2 + import gleeunit 3 + import stare 4 + import stare/type_ 5 + import stare/value 6 + 7 + pub fn main() -> Nil { 8 + gleeunit.main() 9 + } 10 + 11 + pub fn integer_addition_test() { 12 + let module = 13 + stare.module( 14 + imports: [ 15 + stare.import_( 16 + module: ["gleam", "io"], 17 + types: [], 18 + constructors: [], 19 + functions: [], 20 + alias: None, 21 + ), 22 + stare.import_( 23 + module: ["gleam", "int"], 24 + types: [], 25 + constructors: [], 26 + functions: [], 27 + alias: None, 28 + ), 29 + ], 30 + functions: [ 31 + stare.function( 32 + name: "main", 33 + public: True, 34 + parameters: [], 35 + return_type: None, 36 + statements: [ 37 + stare.function_call( 38 + module: Some("io"), 39 + function: "println", 40 + arguments: [ 41 + stare.function_call( 42 + module: Some("int"), 43 + function: "to_string", 44 + arguments: [stare.add_ints(1, 2)], 45 + ), 46 + ], 47 + ), 48 + ], 49 + ), 50 + ], 51 + ) 52 + let assert Ok(code) = 53 + module 54 + |> stare.generate() 55 + |> stare.format() 56 + 57 + let file = 58 + "import gleam/int 59 + import gleam/io 60 + 61 + pub fn main() { 62 + io.println(int.to_string(1 + 2)) 63 + } 64 + " 65 + 66 + assert file == code 67 + } 68 + 69 + pub fn float_addition_test() { 70 + let module = 71 + stare.module( 72 + imports: [ 73 + stare.import_( 74 + module: ["gleam", "io"], 75 + types: [], 76 + constructors: [], 77 + functions: [], 78 + alias: None, 79 + ), 80 + stare.import_( 81 + module: ["gleam", "float"], 82 + types: [], 83 + constructors: [], 84 + functions: [], 85 + alias: None, 86 + ), 87 + ], 88 + functions: [ 89 + stare.function( 90 + name: "main", 91 + public: True, 92 + parameters: [], 93 + return_type: None, 94 + statements: [ 95 + stare.function_call( 96 + module: Some("io"), 97 + function: "println", 98 + arguments: [ 99 + stare.function_call( 100 + module: Some("float"), 101 + function: "to_string", 102 + arguments: [stare.add_floats(1.1, 2.2)], 103 + ), 104 + ], 105 + ), 106 + ], 107 + ), 108 + ], 109 + ) 110 + let assert Ok(code) = 111 + module 112 + |> stare.generate() 113 + |> stare.format() 114 + 115 + let file = 116 + "import gleam/float 117 + import gleam/io 118 + 119 + pub fn main() { 120 + io.println(float.to_string(1.1 +. 2.2)) 121 + } 122 + " 123 + 124 + assert file == code 125 + } 126 + 127 + pub fn generate_hello_world_test() { 128 + let module = 129 + stare.module( 130 + imports: [ 131 + stare.import_( 132 + module: ["gleam", "io"], 133 + types: [], 134 + constructors: [], 135 + functions: [], 136 + alias: None, 137 + ), 138 + ], 139 + functions: [ 140 + stare.function( 141 + name: "main", 142 + public: True, 143 + parameters: [], 144 + return_type: Some(type_.nil()), 145 + statements: [ 146 + stare.function_call( 147 + module: Some("io"), 148 + function: "println", 149 + arguments: [stare.value(value.string("Hello World!"))], 150 + ), 151 + ], 152 + ), 153 + ], 154 + ) 155 + 156 + let assert Ok(code) = 157 + module 158 + |> stare.generate() 159 + |> stare.format() 160 + 161 + let file = 162 + "import gleam/io 163 + 164 + pub fn main() -> Nil { 165 + io.println(\"Hello World!\") 166 + } 167 + " 168 + 169 + assert file == code 170 + }