From 9a48840493bfb288754f080dc86af5f515875b47 Mon Sep 17 00:00:00 2001 From: Dusty Phillips Date: Wed, 28 Aug 2024 16:05:35 -0300 Subject: [PATCH] Support constants --- README.md | 10 ++-- src/compiler.gleam | 2 - src/compiler/generator.gleam | 8 +++ .../internal/generator/statements.gleam | 8 +++ src/compiler/internal/generator/types.gleam | 36 +------------ src/compiler/internal/transformer.gleam | 6 +++ .../internal/transformer/statements.gleam | 28 ++++++++--- src/compiler/python.gleam | 7 ++- src/compiler/transformer.gleam | 4 +- src/filesystem.gleam | 2 - test/compiler/directory_structure_tests.gleam | 2 - test/compiler/expression_test.gleam | 14 ++++++ test/compiler/types_test.gleam | 50 +++++++------------ 13 files changed, 89 insertions(+), 88 deletions(-) diff --git a/README.md b/README.md index 4cec0c6..e646f1b 100644 --- a/README.md +++ b/README.md @@ -147,7 +147,6 @@ are ) in the codebase, as of the last time that I updated this list. ### Some other things I know are missing - no let assert -- (EASY) const definitions aren't supported yet (module.constants) - type aliases aren't supported yet (module.type_aliases) - macabre_stdlib only has `io.println` - Not doing anything to avoid collision between gleam identifiers with python keywords @@ -164,10 +163,7 @@ are ) in the codebase, as of the last time that I updated this list. 1), mystr: "Foo")` in gleam - javascript seems to solve this by automatically reordering the arguments to match the input type -- (EASY) I notice that the javascript doesn't generate the wrapping class for custom - variants. I think we should make top level types for each constructor and then a type union - for all the types in a single class. -- Related: if you have a multi-variant type where the first constructor shadows - the type's name, it breaks -- (EASY) maybe call ruff or black on the files after they are output, if they are installed. + for all the types in a single class. +- (EASY) maybe call ruff or black on the files after they are output, if they are installed. (shellout is already available) - Code is very not commented +- Should be putting public types, functions, and constants in `__all__` diff --git a/src/compiler.gleam b/src/compiler.gleam index 39351dd..41786a3 100644 --- a/src/compiler.gleam +++ b/src/compiler.gleam @@ -5,7 +5,6 @@ import glance import gleam/dict import gleam/list import gleam/result -import pprint pub fn compile_module(glance_module: glance.Module) -> String { glance_module @@ -14,7 +13,6 @@ pub fn compile_module(glance_module: glance.Module) -> String { } pub fn compile_package(package: package.GleamPackage) -> package.CompiledPackage { - pprint.debug(package.modules) package.CompiledPackage( project: package.project, has_main: dict.get(package.modules, package.project.name <> ".gleam") diff --git a/src/compiler/generator.gleam b/src/compiler/generator.gleam index 96dd69d..bdab965 100644 --- a/src/compiler/generator.gleam +++ b/src/compiler/generator.gleam @@ -10,6 +10,14 @@ pub fn generate(module: python.Module) -> String { string_builder.new() |> string_builder.append(python_prelude.prelude) |> string_builder.append_builder(imports.generate_imports(module.imports)) + |> string_builder.append_builder( + internal.generate_plural( + module.constants, + statements.generate_constant, + "\n", + ) + |> internal.append_if_not_empty("\n\n"), + ) |> string_builder.append_builder(internal.generate_plural( module.custom_types, types.generate_custom_type, diff --git a/src/compiler/internal/generator/statements.gleam b/src/compiler/internal/generator/statements.gleam index e8768b8..48b6bec 100644 --- a/src/compiler/internal/generator/statements.gleam +++ b/src/compiler/internal/generator/statements.gleam @@ -61,6 +61,14 @@ pub fn generate_statement(statement: python.Statement) -> StringBuilder { } } +pub fn generate_constant(constant: python.Constant) -> StringBuilder { + string_builder.from_string(constant.name) + |> string_builder.append(" = ") + |> string_builder.append_builder(expressions.generate_expression( + constant.value, + )) +} + fn generate_cases(cases: List(python.MatchCase)) -> StringBuilder { case cases { [] -> string_builder.from_string("pass") diff --git a/src/compiler/internal/generator/types.gleam b/src/compiler/internal/generator/types.gleam index 67ee928..24abd35 100644 --- a/src/compiler/internal/generator/types.gleam +++ b/src/compiler/internal/generator/types.gleam @@ -8,37 +8,18 @@ pub fn generate_custom_type(custom_type: python.CustomType) -> StringBuilder { // empty types get discarded [] -> string_builder.new() - // we just discard the outer class if there is only one variant - [one_variant] -> { - internal.generate_plural( - custom_type.parameters, - generate_generic_var, - "\n", - ) - |> string_builder.append_builder(generate_type_variant(one_variant)) - |> string_builder.append("\n\n\n") - } - variants -> { + string_builder.from_string("// pub type " <> custom_type.name) internal.generate_plural( custom_type.parameters, generate_generic_var, "\n", ) - |> string_builder.append("class ") - |> string_builder.append(custom_type.name) - |> string_builder.append(":\n") |> string_builder.append_builder( internal.generate_plural(variants, generate_type_variant, "\n\n") - |> internal.indent(4) |> internal.append_if_not_empty("\n\n"), ) - |> string_builder.append_builder(internal.generate_plural( - custom_type.variants, - generate_variant_reassign(custom_type.name), - "\n", - )) - |> string_builder.append("\n\n\n") + |> string_builder.append("\n") } } } @@ -117,16 +98,3 @@ fn generate_type(type_: python.Type) -> StringBuilder { |> string_builder.uppercase } } - -fn generate_variant_reassign( - namespace: String, -) -> fn(python.Variant) -> StringBuilder { - fn(variant: python.Variant) -> StringBuilder { - string_builder.new() - |> string_builder.append(variant.name) - |> string_builder.append(" = ") - |> string_builder.append(namespace) - |> string_builder.append(".") - |> string_builder.append(variant.name) - } -} diff --git a/src/compiler/internal/transformer.gleam b/src/compiler/internal/transformer.gleam index 6d0095e..e1be651 100644 --- a/src/compiler/internal/transformer.gleam +++ b/src/compiler/internal/transformer.gleam @@ -16,6 +16,12 @@ pub type TransformerContext { ) } +pub const empty_context = TransformerContext( + next_function_id: 0, + next_block_id: 0, + next_case_id: 0, +) + pub type ExpressionReturn { ExpressionReturn( context: TransformerContext, diff --git a/src/compiler/internal/transformer/statements.gleam b/src/compiler/internal/transformer/statements.gleam index 958b641..71306cb 100644 --- a/src/compiler/internal/transformer/statements.gleam +++ b/src/compiler/internal/transformer/statements.gleam @@ -16,14 +16,7 @@ import gleam/option pub fn transform_statement_block( statements: List(glance.Statement), ) -> List(python.Statement) { - transform_statement_block_with_context( - internal.TransformerContext( - next_function_id: 0, - next_block_id: 0, - next_case_id: 0, - ), - statements, - ).statements + transform_statement_block_with_context(internal.empty_context, statements).statements } pub fn transform_statement_block_with_context( @@ -50,6 +43,25 @@ pub fn transform_statement_block_with_context( ) } +pub fn transform_constant( + module: python.Module, + constant: glance.Definition(glance.Constant), +) -> python.Module { + python.Module( + ..module, + constants: [ + python.Constant( + name: constant.definition.name, + value: transform_expression( + internal.empty_context, + constant.definition.value, + ).expression, + ), + ..module.constants + ], + ) +} + fn transform_statement( transform_context: internal.TransformerContext, statement: glance.Statement, diff --git a/src/compiler/python.gleam b/src/compiler/python.gleam index 582ee6b..63611b1 100644 --- a/src/compiler/python.gleam +++ b/src/compiler/python.gleam @@ -30,6 +30,10 @@ pub type BinaryOperator { Modulo } +pub type Constant { + Constant(name: String, value: Expression) +} + pub type Expression { String(String) Number(String) @@ -140,9 +144,10 @@ pub type Module { imports: List(Import), functions: List(Function), custom_types: List(CustomType), + constants: List(Constant), ) } pub fn empty_module() -> Module { - Module(imports: [], functions: [], custom_types: []) + Module(imports: [], functions: [], custom_types: [], constants: []) } diff --git a/src/compiler/transformer.gleam b/src/compiler/transformer.gleam index 6b188aa..d471f44 100644 --- a/src/compiler/transformer.gleam +++ b/src/compiler/transformer.gleam @@ -1,5 +1,6 @@ import compiler/internal/transformer as internal import compiler/internal/transformer/functions +import compiler/internal/transformer/statements import compiler/internal/transformer/types import compiler/python import glance @@ -10,6 +11,7 @@ import gleam/string pub fn transform(input: glance.Module) -> python.Module { python.empty_module() |> list.fold(input.imports, _, transform_import) + |> list.fold(input.constants, _, statements.transform_constant) |> list.fold(input.functions, _, transform_function_or_external) |> list.fold(input.custom_types, _, transform_custom_type_in_module) } @@ -117,7 +119,7 @@ fn transform_custom_type_in_module( ) } -pub fn maybe_extract_external( +fn maybe_extract_external( function_attribute: glance.Attribute, ) -> Result(python.Import, internal.TransformError) { case function_attribute { diff --git a/src/filesystem.gleam b/src/filesystem.gleam index 1c3e158..374ac21 100644 --- a/src/filesystem.gleam +++ b/src/filesystem.gleam @@ -5,7 +5,6 @@ import filepath import gleam/io import gleam/list import gleam/result -import pprint import python_prelude import simplifile @@ -52,7 +51,6 @@ pub fn write_py_main( build_dir: String, module: String, ) -> Result(Nil, errors.Error) { - pprint.debug(has_main) case has_main { True -> filepath.join(build_dir, "__main__.py") diff --git a/test/compiler/directory_structure_tests.gleam b/test/compiler/directory_structure_tests.gleam index 51e9748..0e1323e 100644 --- a/test/compiler/directory_structure_tests.gleam +++ b/test/compiler/directory_structure_tests.gleam @@ -4,12 +4,10 @@ import compiler/project import filepath import gleam/dict import gleam/list -import gleam/option import gleam/set import gleam/string import gleeunit/should import macabre -import pprint import simplifile import temporary diff --git a/test/compiler/expression_test.gleam b/test/compiler/expression_test.gleam index 1e954fa..a67bde7 100644 --- a/test/compiler/expression_test.gleam +++ b/test/compiler/expression_test.gleam @@ -758,3 +758,17 @@ def main(): foo = _fn_block_0()", ) } + +pub fn const_test() { + "const foo = 5" + |> glance.module + |> should.be_ok + |> compiler.compile_module + |> should.equal( + "from gleam_builtins import * + +foo = 5 + +", + ) +} diff --git a/test/compiler/types_test.gleam b/test/compiler/types_test.gleam index 51493be..651698e 100644 --- a/test/compiler/types_test.gleam +++ b/test/compiler/types_test.gleam @@ -45,17 +45,13 @@ pub fn multi_variant_custom_type_test() { |> should.equal( "from gleam_builtins import * -class Foo: - @dataclasses.dataclass(frozen=True) - class Bar: - a: int - - @dataclasses.dataclass(frozen=True) - class Baz: - a: str +@dataclasses.dataclass(frozen=True) +class Bar: + a: int -Bar = Foo.Bar -Baz = Foo.Baz +@dataclasses.dataclass(frozen=True) +class Baz: + a: str ", @@ -92,17 +88,13 @@ pub fn multi_variant_with_no_fields_test() { |> should.equal( "from gleam_builtins import * -class Foo: - @dataclasses.dataclass(frozen=True) - class Bar: - pass - - @dataclasses.dataclass(frozen=True) - class Baz: - pass +@dataclasses.dataclass(frozen=True) +class Bar: + pass -Bar = Foo.Bar -Baz = Foo.Baz +@dataclasses.dataclass(frozen=True) +class Baz: + pass ", @@ -160,17 +152,13 @@ pub fn multi_variant_generic_test() { "from gleam_builtins import * ELEM = typing.TypeVar('ELEM') -class Foo: - @dataclasses.dataclass(frozen=True) - class Bar: - item: ELEM - - @dataclasses.dataclass(frozen=True) - class Baz: - elem: ELEM - -Bar = Foo.Bar -Baz = Foo.Baz +@dataclasses.dataclass(frozen=True) +class Bar: + item: ELEM + +@dataclasses.dataclass(frozen=True) +class Baz: + elem: ELEM ",