+9
.tangled/workflows/test.yaml
+9
.tangled/workflows/test.yaml
+56
README.md
+56
README.md
···
1
+
# stare
2
+
3
+
[](https://hex.pm/packages/stare)
4
+
[](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
+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
+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
+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
+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
+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
+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
+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
+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
+
}