├── .cargo └── config.toml ├── .github ├── dependabot.yml └── workflows │ ├── benchmark.yaml │ ├── dependabot.yaml │ ├── docker.yaml │ ├── document.yaml │ ├── lint.yaml │ ├── release.yaml │ └── test.yaml ├── .gitignore ├── .ruby-version ├── .rustfmt.toml ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── Gemfile ├── Gemfile.lock ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── benchmark ├── .gitignore ├── pen │ ├── continuation │ │ ├── main.pen │ │ └── pen.json │ ├── fibonacci │ │ ├── main.pen │ │ └── pen.json │ ├── lambda-lifting │ │ ├── main.pen │ │ └── pen.json │ ├── list-comprehension │ │ ├── main.pen │ │ └── pen.json │ ├── map-insert │ │ ├── main.pen │ │ └── pen.json │ ├── map-update │ │ ├── main.pen │ │ └── pen.json │ └── sum │ │ ├── main.pen │ │ └── pen.json └── rust │ ├── fibonacci │ ├── Cargo.toml │ └── src │ │ └── main.rs │ ├── im_hash_map_insert │ ├── Cargo.toml │ └── src │ │ └── main.rs │ ├── im_hash_map_update │ ├── Cargo.toml │ └── src │ │ └── main.rs │ ├── std_hash_map_insert │ ├── Cargo.toml │ └── src │ │ └── main.rs │ └── sum │ ├── Cargo.toml │ └── src │ └── main.rs ├── cmd ├── pen │ ├── Cargo.toml │ └── src │ │ ├── application_configuration.rs │ │ ├── compile_configuration.rs │ │ ├── dependency_resolver.rs │ │ ├── documentation_configuration.rs │ │ ├── file_path_configuration.rs │ │ ├── infrastructure.rs │ │ ├── main.rs │ │ ├── main_module_compiler.rs │ │ ├── main_package_directory_finder.rs │ │ ├── module_compiler.rs │ │ ├── module_formatter.rs │ │ ├── package_builder.rs │ │ ├── package_creator.rs │ │ ├── package_documentation_generator.rs │ │ ├── package_formatter.rs │ │ ├── package_test_information_compiler.rs │ │ ├── prelude_module_compiler.rs │ │ ├── test_configuration.rs │ │ ├── test_linker.rs │ │ ├── test_module_compiler.rs │ │ └── test_runner.rs └── test │ ├── Cargo.lock │ ├── Cargo.toml │ ├── build.rs │ └── src │ ├── debug.rs │ ├── heap.rs │ ├── main.rs │ ├── spawn.rs │ └── unreachable.rs ├── codecov.yaml ├── cspell.json ├── doc ├── docs │ ├── CNAME │ ├── README.md │ ├── advanced-features │ │ ├── cross-compile.md │ │ ├── ffi.md │ │ └── writing-system-packages.md │ ├── examples │ │ └── .gitignore │ ├── guides │ │ ├── building-an-executable.md │ │ ├── coding-style.md │ │ ├── concurrency-and-parallelism.md │ │ ├── creating-a-library.md │ │ ├── testing.md │ │ └── using-a-library.md │ ├── introduction │ │ ├── building-the-first-program.md │ │ └── install.md │ ├── manifest.json │ ├── references │ │ ├── command-line-tools.md │ │ ├── language │ │ │ ├── built-ins.md │ │ │ ├── modules.md │ │ │ ├── packages.md │ │ │ ├── syntax.md │ │ │ └── types.md │ │ └── standard-packages │ │ │ └── .gitignore │ ├── roadmap.md │ └── the-zen.md ├── mkdocs.yaml ├── package-lock.json ├── package.json ├── requirements.txt └── theme │ ├── extra.css │ └── main.html ├── examples ├── .gitignore ├── README.md ├── algorithms │ ├── fibonacci │ │ ├── README.md │ │ ├── main.pen │ │ └── pen.json │ ├── fizz-buzz │ │ ├── README.md │ │ ├── main.pen │ │ └── pen.json │ ├── knapsack │ │ ├── README.md │ │ ├── knapsack.pen │ │ ├── knapsack.test.pen │ │ ├── main.pen │ │ └── pen.json │ ├── parallel │ │ └── fibonacci │ │ │ ├── README.md │ │ │ ├── main.pen │ │ │ └── pen.json │ └── quick-sort │ │ ├── README.md │ │ ├── main.pen │ │ ├── pen.json │ │ ├── sort.pen │ │ └── sort.test.pen ├── cat │ ├── README.md │ ├── main.pen │ └── pen.json ├── console │ ├── Print.pen │ ├── README.md │ └── pen.json ├── echo │ ├── README.md │ ├── main.pen │ └── pen.json ├── hello-world │ ├── README.md │ ├── main.pen │ └── pen.json ├── http-client │ ├── README.md │ ├── main.pen │ └── pen.json ├── http-server │ ├── README.md │ ├── main.pen │ └── pen.json ├── life-game │ ├── README.md │ ├── lifeGame.pen │ ├── lifeGame.test.pen │ ├── main.pen │ └── pen.json ├── ls │ ├── README.md │ ├── arguments.pen │ ├── arguments.test.pen │ ├── main.pen │ └── pen.json ├── snake │ ├── README.md │ ├── direction.pen │ ├── entity.pen │ ├── entity │ │ ├── frog.pen │ │ ├── snake.pen │ │ └── snake.test.pen │ ├── field.pen │ ├── game.pen │ ├── main.pen │ ├── pen.json │ ├── position.pen │ ├── render.pen │ ├── render.test.pen │ └── usage.pen ├── sql-client │ ├── README.md │ ├── arguments.pen │ ├── command.pen │ ├── main.pen │ └── pen.json ├── tcp-client │ ├── README.md │ ├── arguments.pen │ ├── arguments.test.pen │ ├── main.pen │ └── pen.json ├── tcp-server │ ├── README.md │ ├── arguments.pen │ ├── arguments.test.pen │ ├── main.pen │ └── pen.json ├── udp-client │ ├── README.md │ ├── arguments.pen │ ├── arguments.test.pen │ ├── main.pen │ └── pen.json ├── udp-server │ ├── README.md │ ├── arguments.pen │ ├── arguments.test.pen │ ├── main.pen │ └── pen.json └── yes │ ├── README.md │ ├── arguments.pen │ ├── arguments.test.pen │ ├── main.pen │ └── pen.json ├── features ├── commands │ ├── build.feature │ ├── create.feature │ ├── document.feature │ ├── format.feature │ └── test.feature ├── ffi.feature ├── module.feature ├── package.feature ├── smoke │ ├── built-ins.feature │ ├── examples.feature │ ├── memory-leak │ │ ├── concurrency.feature │ │ └── language.feature │ ├── module.feature │ ├── os.feature │ ├── standard-packages.feature │ ├── syntax.feature │ ├── test-packages.feature │ ├── test.feature │ └── types │ │ ├── list.feature │ │ └── string.feature ├── standard-packages │ ├── core.feature │ ├── os-sync.feature │ ├── os.feature │ ├── random.feature │ └── test.feature ├── support │ └── aruba.rb ├── syntax │ ├── block.feature │ └── concurrency.feature └── types │ ├── any.feature │ ├── boolean.feature │ ├── error.feature │ ├── function.feature │ ├── list.feature │ ├── map.feature │ ├── none.feature │ ├── number.feature │ ├── polymorphism.feature │ ├── record.feature │ ├── stream.feature │ ├── string.feature │ └── union.feature ├── go.mod ├── go.sum ├── lib ├── app │ ├── Cargo.toml │ └── src │ │ ├── application_configuration.rs │ │ ├── common.rs │ │ ├── common │ │ ├── dependency_serializer.rs │ │ ├── file_path_resolver.rs │ │ ├── interface_serializer.rs │ │ ├── module_id_calculator.rs │ │ ├── module_test_information_serializer.rs │ │ ├── package_id_calculator.rs │ │ └── package_test_information_serializer.rs │ │ ├── error.rs │ │ ├── external_package_configuration_reader.rs │ │ ├── external_package_topological_sorter.rs │ │ ├── file_finder.rs │ │ ├── infra.rs │ │ ├── infra │ │ ├── build_script_compiler.rs │ │ ├── build_script_dependency_compiler.rs │ │ ├── build_script_runner.rs │ │ ├── command_runner.rs │ │ ├── external_package_initializer.rs │ │ ├── file_path.rs │ │ ├── file_path_configuration.rs │ │ ├── file_path_displayer.rs │ │ ├── file_system.rs │ │ ├── infrastructure.rs │ │ ├── main_module_target.rs │ │ ├── module_target.rs │ │ ├── module_target_source.rs │ │ ├── package_configuration_reader.rs │ │ ├── package_configuration_writer.rs │ │ ├── test_linker.rs │ │ └── test_module_target.rs │ │ ├── lib.rs │ │ ├── module_compiler.rs │ │ ├── module_compiler │ │ ├── compile_configuration.rs │ │ ├── main_module_configuration_qualifier.rs │ │ └── prelude_type_configuration_qualifier.rs │ │ ├── module_dependency_resolver.rs │ │ ├── module_finder.rs │ │ ├── module_formatter.rs │ │ ├── module_target_source_resolver.rs │ │ ├── package_build_script_compiler.rs │ │ ├── package_build_script_compiler │ │ ├── module_target_collector.rs │ │ └── test_module_target_collector.rs │ │ ├── package_builder.rs │ │ ├── package_configuration.rs │ │ ├── package_creator.rs │ │ ├── package_documentation_generator.rs │ │ ├── package_format_checker.rs │ │ ├── package_formatter.rs │ │ ├── package_initializer.rs │ │ ├── package_initializer │ │ └── external_package_initializer.rs │ │ ├── package_name_formatter.rs │ │ ├── package_test_builder.rs │ │ ├── package_test_information_compiler.rs │ │ ├── prelude_interface_file_finder.rs │ │ ├── system_package_finder.rs │ │ ├── test_configuration.rs │ │ ├── test_linker.rs │ │ ├── test_module_finder.rs │ │ └── test_runner.rs ├── ast-hir │ ├── Cargo.toml │ └── src │ │ ├── error.rs │ │ ├── import.rs │ │ ├── imported_module.rs │ │ ├── imported_module │ │ └── validation.rs │ │ ├── lib.rs │ │ ├── module.rs │ │ ├── module_prefix.rs │ │ ├── name.rs │ │ ├── number.rs │ │ ├── string.rs │ │ └── type_.rs ├── ast │ ├── Cargo.toml │ └── src │ │ ├── analysis.rs │ │ ├── ast.rs │ │ ├── ast │ │ ├── argument.rs │ │ ├── binary_operation.rs │ │ ├── binary_operator.rs │ │ ├── block.rs │ │ ├── call.rs │ │ ├── calling_convention.rs │ │ ├── expression.rs │ │ ├── external_module_path.rs │ │ ├── foreign_export.rs │ │ ├── foreign_import.rs │ │ ├── function_definition.rs │ │ ├── if_.rs │ │ ├── if_branch.rs │ │ ├── if_list.rs │ │ ├── if_map.rs │ │ ├── if_type.rs │ │ ├── if_type_branch.rs │ │ ├── import.rs │ │ ├── internal_module_path.rs │ │ ├── lambda.rs │ │ ├── list.rs │ │ ├── list_comprehension.rs │ │ ├── list_comprehension_branch.rs │ │ ├── list_element.rs │ │ ├── map.rs │ │ ├── map_element.rs │ │ ├── map_entry.rs │ │ ├── module.rs │ │ ├── module_path.rs │ │ ├── number.rs │ │ ├── number_representation.rs │ │ ├── record.rs │ │ ├── record_deconstruction.rs │ │ ├── record_definition.rs │ │ ├── record_field.rs │ │ ├── statement.rs │ │ ├── string.rs │ │ ├── type_alias.rs │ │ ├── type_definition.rs │ │ ├── unary_operation.rs │ │ ├── unary_operator.rs │ │ ├── unqualified_name.rs │ │ └── variable.rs │ │ ├── comment.rs │ │ ├── lib.rs │ │ ├── types.rs │ │ └── types │ │ ├── function.rs │ │ ├── list.rs │ │ ├── map.rs │ │ ├── record.rs │ │ ├── record_field.rs │ │ ├── reference.rs │ │ ├── type_.rs │ │ └── union.rs ├── doc │ ├── Cargo.toml │ └── src │ │ ├── ir.rs │ │ ├── ir │ │ ├── build.rs │ │ └── document.rs │ │ ├── lib.rs │ │ └── markdown.rs ├── ffi-macro │ ├── Cargo.toml │ ├── src │ │ ├── any.rs │ │ ├── attribute_list.rs │ │ ├── bindgen.rs │ │ ├── into_any.rs │ │ ├── lib.rs │ │ └── utilities.rs │ └── tests │ │ ├── function.rs │ │ └── type_.rs ├── ffi │ ├── Cargo.toml │ └── src │ │ ├── any.rs │ │ ├── arc.rs │ │ ├── arc │ │ ├── arc_block.rs │ │ └── arc_buffer.rs │ │ ├── boolean.rs │ │ ├── closure.rs │ │ ├── constants.rs │ │ ├── cps.rs │ │ ├── cps │ │ ├── async_stack.rs │ │ ├── async_stack_action.rs │ │ ├── error.rs │ │ ├── import.rs │ │ └── stack.rs │ │ ├── error.rs │ │ ├── extra.rs │ │ ├── extra │ │ └── result.rs │ │ ├── future.rs │ │ ├── future │ │ ├── call.rs │ │ ├── call_function.rs │ │ ├── from_closure.rs │ │ ├── from_function.rs │ │ ├── stream.rs │ │ └── to_closure.rs │ │ ├── lib.rs │ │ ├── list.rs │ │ ├── none.rs │ │ ├── number.rs │ │ ├── runtime.rs │ │ ├── runtime │ │ └── error.rs │ │ ├── string.rs │ │ └── type_information.rs ├── format │ ├── Cargo.toml │ └── src │ │ ├── context.rs │ │ └── lib.rs ├── hir-mir │ ├── Cargo.toml │ └── src │ │ ├── built_in_call.rs │ │ ├── compile_configuration.rs │ │ ├── concrete_type.rs │ │ ├── context.rs │ │ ├── downcast.rs │ │ ├── error.rs │ │ ├── error_type.rs │ │ ├── expression.rs │ │ ├── generic_type_definition.rs │ │ ├── lib.rs │ │ ├── list_comprehension.rs │ │ ├── list_type_configuration.rs │ │ ├── main_function.rs │ │ ├── main_module_configuration.rs │ │ ├── map_type_configuration.rs │ │ ├── module.rs │ │ ├── module_interface.rs │ │ ├── number_type_configuration.rs │ │ ├── runtime_function_declaration.rs │ │ ├── snapshots │ │ ├── hir_mir__built_in_call__tests__compile_debug.snap │ │ ├── hir_mir__built_in_call__tests__compile_keys.snap │ │ └── hir_mir__built_in_call__tests__compile_values.snap │ │ ├── string_type_configuration.rs │ │ ├── test_function.rs │ │ ├── test_module_configuration.rs │ │ ├── transformation.rs │ │ ├── transformation │ │ ├── boolean_operation.rs │ │ ├── collection_type.rs │ │ ├── equal_operation.rs │ │ ├── equal_operation │ │ │ ├── expression.rs │ │ │ ├── function.rs │ │ │ ├── module.rs │ │ │ ├── operation.rs │ │ │ └── snapshots │ │ │ │ └── hir_mir__transformation__equal_operation__module__tests__transform_comparable_type.snap │ │ ├── hash_calculation.rs │ │ ├── hash_calculation │ │ │ ├── expression.rs │ │ │ ├── function.rs │ │ │ ├── module.rs │ │ │ └── snapshots │ │ │ │ └── hir_mir__transformation__hash_calculation__module__tests__transform_comparable_type.snap │ │ ├── if_list.rs │ │ ├── if_map.rs │ │ ├── list_literal.rs │ │ ├── map_context.rs │ │ ├── map_context │ │ │ ├── expression.rs │ │ │ ├── module.rs │ │ │ └── snapshots │ │ │ │ ├── hir_mir__transformation__map_context__module__tests__transform_function_value.snap │ │ │ │ └── hir_mir__transformation__map_context__module__tests__transform_none_key_and_none_value.snap │ │ ├── map_literal.rs │ │ ├── not_equal_operation.rs │ │ ├── record_equal_function.rs │ │ ├── record_hash_function.rs │ │ ├── record_type_information.rs │ │ └── snapshots │ │ │ ├── hir_mir__transformation__if_list__tests__transform_if_list_with_any_type.snap │ │ │ ├── hir_mir__transformation__if_list__tests__transform_if_list_with_number_type.snap │ │ │ ├── hir_mir__transformation__if_map__tests__transform_if_map.snap │ │ │ ├── hir_mir__transformation__map_literal__tests__transform_empty_map.snap │ │ │ ├── hir_mir__transformation__map_literal__tests__transform_empty_map_with_function_value.snap │ │ │ ├── hir_mir__transformation__map_literal__tests__transform_map_with_2_entries.snap │ │ │ ├── hir_mir__transformation__map_literal__tests__transform_map_with_entry.snap │ │ │ └── hir_mir__transformation__map_literal__tests__transform_map_with_spread_map.snap │ │ ├── type_.rs │ │ ├── type_information.rs │ │ ├── type_information │ │ ├── debug.rs │ │ ├── equal.rs │ │ └── utility.rs │ │ └── variant_type_collection.rs ├── hir │ ├── Cargo.toml │ └── src │ │ ├── analysis.rs │ │ ├── analysis │ │ ├── built_in_type_transformer.rs │ │ ├── built_in_variable_transformer.rs │ │ ├── context.rs │ │ ├── duplicate_function_name_validator.rs │ │ ├── duplicate_type_name_validator.rs │ │ ├── error.rs │ │ ├── expression_visitor.rs │ │ ├── function_definition_qualifier.rs │ │ ├── impossible_type_validator.rs │ │ ├── module_environment.rs │ │ ├── record_field_resolver.rs │ │ ├── record_field_validator.rs │ │ ├── recursive_type_alias_validator.rs │ │ ├── try_operation_validator.rs │ │ ├── type_canonicalizer.rs │ │ ├── type_checker.rs │ │ ├── type_coercer.rs │ │ ├── type_collector.rs │ │ ├── type_comparability_checker.rs │ │ ├── type_difference_calculator.rs │ │ ├── type_equality_checker.rs │ │ ├── type_existence_validator.rs │ │ ├── type_extractor.rs │ │ ├── type_formatter.rs │ │ ├── type_id_calculator.rs │ │ ├── type_inferrer.rs │ │ ├── type_qualifier.rs │ │ ├── type_resolver.rs │ │ ├── type_subsumption_checker.rs │ │ ├── type_transformer.rs │ │ ├── type_visitor.rs │ │ ├── union_type_creator.rs │ │ ├── union_type_member_calculator.rs │ │ ├── unused_error_validator.rs │ │ ├── variable_renamer.rs │ │ └── variable_transformer.rs │ │ ├── ir.rs │ │ ├── ir │ │ ├── addition_operation.rs │ │ ├── argument.rs │ │ ├── arithmetic_operation.rs │ │ ├── block.rs │ │ ├── boolean.rs │ │ ├── boolean_operation.rs │ │ ├── built_in_function.rs │ │ ├── call.rs │ │ ├── calling_convention.rs │ │ ├── else_branch.rs │ │ ├── equality_operation.rs │ │ ├── expression.rs │ │ ├── foreign_declaration.rs │ │ ├── foreign_definition_configuration.rs │ │ ├── function_declaration.rs │ │ ├── function_definition.rs │ │ ├── if_.rs │ │ ├── if_list.rs │ │ ├── if_map.rs │ │ ├── if_type.rs │ │ ├── if_type_branch.rs │ │ ├── lambda.rs │ │ ├── let_.rs │ │ ├── list.rs │ │ ├── list_comprehension.rs │ │ ├── list_comprehension_branch.rs │ │ ├── list_comprehension_iteratee.rs │ │ ├── list_element.rs │ │ ├── map.rs │ │ ├── map_element.rs │ │ ├── map_entry.rs │ │ ├── module.rs │ │ ├── none.rs │ │ ├── not_operation.rs │ │ ├── number.rs │ │ ├── operation.rs │ │ ├── order_operation.rs │ │ ├── record_construction.rs │ │ ├── record_deconstruction.rs │ │ ├── record_field.rs │ │ ├── record_update.rs │ │ ├── string.rs │ │ ├── thunk.rs │ │ ├── try_operation.rs │ │ ├── type_alias.rs │ │ ├── type_coercion.rs │ │ ├── type_definition.rs │ │ └── variable.rs │ │ ├── lib.rs │ │ ├── test.rs │ │ ├── test │ │ ├── ir.rs │ │ ├── ir │ │ │ ├── foreign_declaration.rs │ │ │ ├── function_definition.rs │ │ │ ├── module.rs │ │ │ ├── type_alias.rs │ │ │ └── type_definition.rs │ │ ├── types.rs │ │ └── types │ │ │ └── record.rs │ │ ├── types.rs │ │ └── types │ │ ├── any.rs │ │ ├── boolean.rs │ │ ├── byte_string.rs │ │ ├── error.rs │ │ ├── function.rs │ │ ├── list.rs │ │ ├── map.rs │ │ ├── none.rs │ │ ├── number.rs │ │ ├── record.rs │ │ ├── record_field.rs │ │ ├── reference.rs │ │ ├── type_.rs │ │ └── union.rs ├── infra │ ├── Cargo.toml │ └── src │ │ ├── command_finder.rs │ │ ├── command_runner.rs │ │ ├── default_target_finder.rs │ │ ├── environment_variable_reader.rs │ │ ├── error.rs │ │ ├── external_package_initializer.rs │ │ ├── file_path_converter.rs │ │ ├── file_path_displayer.rs │ │ ├── file_system.rs │ │ ├── json_package_configuration.rs │ │ ├── json_package_configuration_reader.rs │ │ ├── json_package_configuration_writer.rs │ │ ├── lib.rs │ │ ├── llvm_command_finder.rs │ │ ├── logger.rs │ │ ├── ninja_build_script_compiler.rs │ │ ├── ninja_build_script_dependency_compiler.rs │ │ ├── ninja_build_script_runner.rs │ │ ├── package_script_finder.rs │ │ └── test_linker.rs ├── interface │ ├── Cargo.toml │ └── src │ │ ├── function_declaration.rs │ │ ├── lib.rs │ │ ├── module.rs │ │ ├── type_alias.rs │ │ └── type_definition.rs ├── mir-fmm │ ├── Cargo.toml │ └── src │ │ ├── box_.rs │ │ ├── call.rs │ │ ├── closure.rs │ │ ├── closure │ │ ├── drop.rs │ │ ├── metadata.rs │ │ └── sync.rs │ │ ├── configuration.rs │ │ ├── context.rs │ │ ├── entry_function.rs │ │ ├── error.rs │ │ ├── expression.rs │ │ ├── foreign_declaration.rs │ │ ├── foreign_definition.rs │ │ ├── foreign_value.rs │ │ ├── function_declaration.rs │ │ ├── function_definition.rs │ │ ├── lib.rs │ │ ├── pointer.rs │ │ ├── record.rs │ │ ├── reference_count.rs │ │ ├── reference_count │ │ ├── block.rs │ │ ├── count.rs │ │ ├── expression.rs │ │ ├── function.rs │ │ ├── heap.rs │ │ ├── pointer.rs │ │ ├── record.rs │ │ ├── record │ │ │ └── utilities.rs │ │ ├── string.rs │ │ └── variant.rs │ │ ├── snapshots │ │ └── mir_fmm__entry_function__tests__do_not_overwrite_global_functions_in_variables.snap │ │ ├── type_.rs │ │ ├── type_ │ │ ├── foreign.rs │ │ └── variant.rs │ │ ├── type_information.rs │ │ ├── variant.rs │ │ └── yield_.rs ├── mir │ ├── Cargo.toml │ └── src │ │ ├── analysis.rs │ │ ├── analysis │ │ ├── alpha_conversion.rs │ │ ├── alpha_conversion │ │ │ └── context.rs │ │ ├── environment_inference.rs │ │ ├── expression_conversion.rs │ │ ├── free_variable.rs │ │ ├── lambda_lifting.rs │ │ ├── lambda_lifting │ │ │ ├── box_.rs │ │ │ ├── call.rs │ │ │ └── context.rs │ │ ├── normalization.rs │ │ ├── normalization │ │ │ ├── alias_removal.rs │ │ │ └── context.rs │ │ ├── optimization.rs │ │ ├── optimization │ │ │ └── string_concatenation.rs │ │ ├── reference_count.rs │ │ ├── reference_count │ │ │ ├── error.rs │ │ │ ├── transformation.rs │ │ │ └── validation.rs │ │ ├── type_check.rs │ │ ├── type_check │ │ │ ├── context.rs │ │ │ ├── error.rs │ │ │ └── name.rs │ │ ├── type_id.rs │ │ └── variant_type_collection.rs │ │ ├── ir.rs │ │ ├── ir │ │ ├── alternative.rs │ │ ├── argument.rs │ │ ├── arithmetic_operation.rs │ │ ├── arithmetic_operator.rs │ │ ├── byte_string.rs │ │ ├── call.rs │ │ ├── calling_convention.rs │ │ ├── case.rs │ │ ├── clone_variables.rs │ │ ├── comparison_operation.rs │ │ ├── comparison_operator.rs │ │ ├── default_alternative.rs │ │ ├── drop_variables.rs │ │ ├── expression.rs │ │ ├── foreign_declaration.rs │ │ ├── foreign_definition.rs │ │ ├── function_declaration.rs │ │ ├── function_definition.rs │ │ ├── global_function_definition.rs │ │ ├── if_.rs │ │ ├── let_.rs │ │ ├── let_recursive.rs │ │ ├── module.rs │ │ ├── record.rs │ │ ├── record_field.rs │ │ ├── record_update.rs │ │ ├── record_update_field.rs │ │ ├── string_concatenation.rs │ │ ├── synchronize.rs │ │ ├── try_operation.rs │ │ ├── type_definition.rs │ │ ├── type_information.rs │ │ ├── type_information_function.rs │ │ ├── variable.rs │ │ └── variant.rs │ │ ├── lib.rs │ │ ├── test.rs │ │ ├── test │ │ ├── ir.rs │ │ └── ir │ │ │ ├── function_definition.rs │ │ │ ├── global_function_definition.rs │ │ │ └── module.rs │ │ ├── types.rs │ │ └── types │ │ ├── function.rs │ │ ├── record.rs │ │ ├── record_body.rs │ │ ├── type_.rs │ │ └── type_information.rs ├── parse │ ├── Cargo.toml │ └── src │ │ ├── combinator.rs │ │ ├── error.rs │ │ ├── input.rs │ │ ├── lib.rs │ │ ├── operations.rs │ │ ├── parser.rs │ │ └── snapshots │ │ ├── parse__parser__tests__import__fail_to_parse_private_external_module_directory.snap │ │ └── parse__parser__tests__import__fail_to_parse_private_external_module_file.snap ├── position │ ├── Cargo.toml │ └── src │ │ ├── format.rs │ │ ├── lib.rs │ │ ├── position.rs │ │ └── test.rs └── test-info │ ├── Cargo.toml │ └── src │ ├── function.rs │ ├── lib.rs │ ├── module.rs │ └── package.rs ├── packages ├── core │ ├── Bit.pen │ ├── Boolean.pen │ ├── Boolean.test.pen │ ├── Character.pen │ ├── Character.test.pen │ ├── List.pen │ ├── Number.pen │ ├── Number.test.pen │ ├── String.pen │ ├── String.test.pen │ ├── String │ │ ├── Byte.pen │ │ ├── Byte.test.pen │ │ ├── Byte │ │ │ ├── View.pen │ │ │ └── View.test.pen │ │ ├── Utf8.pen │ │ └── Utf8.test.pen │ ├── ffi │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ ├── rust-toolchain.toml │ │ └── src │ │ │ ├── bit.rs │ │ │ ├── character.rs │ │ │ ├── lib.rs │ │ │ ├── number.rs │ │ │ ├── string.rs │ │ │ ├── string │ │ │ ├── builder.rs │ │ │ ├── byte.rs │ │ │ └── utf8.rs │ │ │ └── view.rs │ ├── pen-ffi.sh │ └── pen.json ├── ffi │ ├── any.pen │ ├── list.pen │ └── pen.json ├── flag │ ├── Flag.pen │ ├── Flag.test.pen │ └── pen.json ├── html │ ├── Node.pen │ ├── Render.pen │ ├── Render.test.pen │ ├── ffi │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── pen-ffi.sh │ ├── pen.json │ ├── validate.pen │ └── validate.test.pen ├── http │ ├── Client.pen │ ├── Context.pen │ ├── Context │ │ ├── context.pen │ │ ├── ffiHeaderMap.pen │ │ └── ffiResponse.pen │ ├── Request.pen │ ├── Response.pen │ ├── Server.pen │ ├── ffi │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ ├── rust-toolchain.toml │ │ └── src │ │ │ ├── client.rs │ │ │ ├── header_map.rs │ │ │ ├── lib.rs │ │ │ ├── response.rs │ │ │ └── server.rs │ ├── pen-ffi.sh │ └── pen.json ├── json │ ├── Decode.pen │ ├── Decode.test.pen │ ├── Encode.pen │ ├── Encode.test.pen │ ├── Value.pen │ ├── ffi │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ ├── rust-toolchain.toml │ │ └── src │ │ │ └── lib.rs │ ├── pen-ffi.sh │ └── pen.json ├── os-sync │ ├── Context.pen │ ├── Context │ │ └── context.pen │ ├── Directory.pen │ ├── Environment.pen │ ├── File.pen │ ├── File │ │ ├── Metadata.pen │ │ └── OpenOptions.pen │ ├── Process.pen │ ├── Tcp.pen │ ├── Tcp │ │ ├── AcceptedStream.pen │ │ ├── Listener.pen │ │ └── Stream.pen │ ├── Time.pen │ ├── Udp.pen │ ├── Udp │ │ ├── Datagram.pen │ │ └── Socket.pen │ ├── concurrency.pen │ ├── ffi │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ ├── application │ │ │ ├── .cargo │ │ │ │ └── config.toml │ │ │ ├── Cargo.toml │ │ │ ├── build.rs │ │ │ └── src │ │ │ │ ├── debug.rs │ │ │ │ ├── heap.rs │ │ │ │ ├── main.rs │ │ │ │ ├── unreachable.rs │ │ │ │ └── utilities.rs │ │ ├── library │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ │ ├── argument.rs │ │ │ │ ├── directory.rs │ │ │ │ ├── environment_variable.rs │ │ │ │ ├── error.rs │ │ │ │ ├── file.rs │ │ │ │ ├── lib.rs │ │ │ │ ├── open_file_options.rs │ │ │ │ ├── process.rs │ │ │ │ ├── stdio.rs │ │ │ │ ├── tcp.rs │ │ │ │ ├── time.rs │ │ │ │ ├── udp.rs │ │ │ │ └── utilities.rs │ │ └── rust-toolchain.toml │ ├── normalFile.pen │ ├── pen-ffi.sh │ ├── pen-link.sh │ └── pen.json ├── os │ ├── Context.pen │ ├── Context │ │ └── context.pen │ ├── Directory.pen │ ├── Environment.pen │ ├── File.pen │ ├── File │ │ ├── Metadata.pen │ │ └── OpenOptions.pen │ ├── Process.pen │ ├── Tcp.pen │ ├── Tcp │ │ ├── AcceptedStream.pen │ │ ├── Listener.pen │ │ └── Stream.pen │ ├── Time.pen │ ├── Udp.pen │ ├── Udp │ │ ├── Datagram.pen │ │ └── Socket.pen │ ├── ffi │ │ ├── .cargo │ │ │ └── config.toml │ │ ├── application │ │ │ ├── Cargo.lock │ │ │ ├── Cargo.toml │ │ │ ├── build.rs │ │ │ ├── rust-toolchain.toml │ │ │ └── src │ │ │ │ ├── concurrency.rs │ │ │ │ ├── debug.rs │ │ │ │ ├── heap.rs │ │ │ │ ├── main.rs │ │ │ │ ├── unreachable.rs │ │ │ │ └── utilities.rs │ │ └── library │ │ │ ├── Cargo.lock │ │ │ ├── Cargo.toml │ │ │ ├── rust-toolchain.toml │ │ │ └── src │ │ │ ├── argument.rs │ │ │ ├── directory.rs │ │ │ ├── environment_variable.rs │ │ │ ├── error.rs │ │ │ ├── file.rs │ │ │ ├── lib.rs │ │ │ ├── open_file_options.rs │ │ │ ├── process.rs │ │ │ ├── stdio.rs │ │ │ ├── tcp.rs │ │ │ ├── time.rs │ │ │ ├── udp.rs │ │ │ └── utilities.rs │ ├── normalFile.pen │ ├── pen-ffi.sh │ ├── pen-link.sh │ └── pen.json ├── prelude │ ├── Hash.pen │ ├── List.pen │ ├── Map.pen │ ├── Number.pen │ ├── String.pen │ ├── ffi │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ ├── rust-toolchain.toml │ │ └── src │ │ │ └── lib.rs │ ├── pen-ffi.sh │ └── pen.json ├── random │ ├── Context.pen │ ├── Context │ │ └── context.pen │ ├── Random.pen │ ├── ffi │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── pen-ffi.sh │ └── pen.json ├── reflect │ ├── Any.pen │ └── pen.json ├── regex │ ├── Expression.pen │ ├── Expression.test.pen │ ├── ffi │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── pen-ffi.sh │ └── pen.json ├── sql │ ├── Context.pen │ ├── Context │ │ └── context.pen │ ├── Pool.pen │ ├── Pool │ │ ├── Options.pen │ │ └── pool.pen │ ├── Value.pen │ ├── ffi │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ ├── rust-toolchain.toml │ │ └── src │ │ │ ├── error.rs │ │ │ ├── lib.rs │ │ │ └── pool.rs │ ├── pen-ffi.sh │ └── pen.json └── test │ ├── Assert.pen │ ├── Assert.test.pen │ ├── State.pen │ ├── State.test.pen │ ├── ffi │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ └── lib.rs │ ├── pen-ffi.sh │ └── pen.json ├── rust-toolchain.toml ├── test ├── prelude │ ├── function.test.pen │ ├── list.test.pen │ ├── map.test.pen │ ├── pen.json │ ├── record.test.pen │ ├── record │ │ ├── qualifiedImport.pen │ │ ├── record.pen │ │ └── unqualifiedImport.pen │ ├── string.test.pen │ └── union.test.pen └── reflect │ ├── any.test.pen │ └── pen.json └── tools ├── benchmark.sh ├── build.sh ├── build_asset.sh ├── check_memory_leak.sh ├── ci └── github │ ├── setup.sh │ └── setup │ └── action.yaml ├── coverage.sh ├── document ├── build.sh └── serve.sh ├── format.sh ├── integration_test.sh ├── lint.sh ├── run_all_crates.sh ├── unit_test.sh ├── unused_dependencies.sh ├── update_dependabot.sh ├── update_dependencies.sh ├── utilities.sh └── version.sh /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | rustflags = ["-A", "improper_ctypes", "-D", "improper_ctypes_definitions"] 3 | -------------------------------------------------------------------------------- /.github/workflows/benchmark.yaml: -------------------------------------------------------------------------------- 1 | name: benchmark 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | merge_group: 8 | concurrency: 9 | group: benchmark-${{ github.ref }} 10 | cancel-in-progress: true 11 | jobs: 12 | benchmark: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: ./tools/ci/github/setup 17 | - run: tools/build.sh 18 | - run: tools/benchmark.sh -b 19 | - run: tools/benchmark.sh 20 | -------------------------------------------------------------------------------- /.github/workflows/dependabot.yaml: -------------------------------------------------------------------------------- 1 | name: dependabot 2 | on: pull_request 3 | permissions: 4 | contents: write 5 | pull-requests: write 6 | jobs: 7 | merge: 8 | runs-on: ubuntu-latest 9 | if: github.actor == 'dependabot[bot]' 10 | steps: 11 | - run: gh pr merge --auto --squash ${{ github.event.pull_request.html_url }} 12 | env: 13 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 14 | -------------------------------------------------------------------------------- /.github/workflows/document.yaml: -------------------------------------------------------------------------------- 1 | name: document 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | merge_group: 8 | jobs: 9 | document: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: homebrew/actions/setup-homebrew@master 14 | - run: tools/ci/github/setup.sh 15 | - run: tools/document/build.sh 16 | - uses: peaceiris/actions-gh-pages@v4 17 | if: github.ref == 'refs/heads/main' 18 | with: 19 | github_token: ${{ secrets.GITHUB_TOKEN }} 20 | publish_dir: doc/site 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.data 2 | *.info 3 | *.new 4 | *.old 5 | *.svg 6 | *.tar.* 7 | .pen 8 | assets 9 | dhat-* 10 | site 11 | target 12 | tmp 13 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.1 2 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2021" 2 | use_field_init_shorthand = true 3 | use_try_shorthand = true 4 | imports_granularity = "Crate" 5 | wrap_comments = true 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "benchmark/rust/fibonacci", 4 | "benchmark/rust/im_hash_map_insert", 5 | "benchmark/rust/im_hash_map_update", 6 | "benchmark/rust/std_hash_map_insert", 7 | "benchmark/rust/sum", 8 | "cmd/pen", 9 | "lib/app", 10 | "lib/ast", 11 | "lib/ast-hir", 12 | "lib/doc", 13 | "lib/ffi", 14 | "lib/ffi-macro", 15 | "lib/format", 16 | "lib/hir", 17 | "lib/hir-mir", 18 | "lib/infra", 19 | "lib/interface", 20 | "lib/mir", 21 | "lib/mir-fmm", 22 | "lib/parse", 23 | "lib/position", 24 | "lib/test-info", 25 | ] 26 | exclude = ["benchmark", "cmd/test", "examples", "packages", "test", "tmp"] 27 | resolver = "2" 28 | 29 | [profile.release] 30 | lto = true 31 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'aruba', '~> 2.3.0' 4 | -------------------------------------------------------------------------------- /benchmark/.gitignore: -------------------------------------------------------------------------------- 1 | app 2 | -------------------------------------------------------------------------------- /benchmark/pen/continuation/main.pen: -------------------------------------------------------------------------------- 1 | import Os'File 2 | 3 | main = \(ctx context) none { 4 | loop(10000000) 5 | 6 | none 7 | } 8 | 9 | loop = \(x number) none { 10 | if x == 0 { 11 | none 12 | } else { 13 | add(x, x, x) 14 | loop(x - 1) 15 | } 16 | } 17 | 18 | add = \(x number, y number, z number) number { 19 | v1 = value() 20 | v2 = value() 21 | v3 = value() 22 | w1 = value() 23 | w2 = value() 24 | w3 = value() 25 | 26 | v1 + v2 + v3 + x + y + z + w1 + w2 + w3 27 | } 28 | 29 | value = \() number { 30 | 42 31 | } 32 | -------------------------------------------------------------------------------- /benchmark/pen/continuation/pen.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "dependencies": { 4 | "Os": "pen:///os" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /benchmark/pen/fibonacci/main.pen: -------------------------------------------------------------------------------- 1 | import Core'Number 2 | import Os'File 3 | 4 | main = \(ctx context) none { 5 | _ = File'Write( 6 | ctx.Os, 7 | File'StdOut(), 8 | Number'String(fibonacci(40)), 9 | ) 10 | 11 | none 12 | } 13 | 14 | fibonacci = \(n number) number { 15 | if n <= 0 { 16 | 0 17 | } else if n == 1 { 18 | 1 19 | } else { 20 | fibonacci(n - 1) + fibonacci(n - 2) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /benchmark/pen/fibonacci/pen.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "dependencies": { 4 | "Core": "pen:///core", 5 | "Os": "pen:///os" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /benchmark/pen/lambda-lifting/main.pen: -------------------------------------------------------------------------------- 1 | import Core'Number 2 | import Os'File 3 | 4 | main = \(ctx context) none { 5 | _ = File'Write( 6 | ctx.Os, 7 | File'StdOut(), 8 | Number'String(sum(0, 100000000)), 9 | ) 10 | 11 | none 12 | } 13 | 14 | sum = \(x number, i number) number { 15 | add = \(x number, y number) number { x + y } 16 | 17 | if i == 0 { 18 | x 19 | } else { 20 | sum(add(x, i), i - 1) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /benchmark/pen/lambda-lifting/pen.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "dependencies": { 4 | "Core": "pen:///core", 5 | "Os": "pen:///os" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /benchmark/pen/list-comprehension/main.pen: -------------------------------------------------------------------------------- 1 | import Core'Number 2 | import Os'Context { Context } 3 | import Os'File 4 | import Os'Process 5 | 6 | main = \(ctx context) none { 7 | if x = print(ctx.Os, [number x() * x() for x in Number'Sequence(100000)]) as none { 8 | none 9 | } else { 10 | debug(x) 11 | Process'Exit(ctx.Os, 1) 12 | } 13 | } 14 | 15 | print = \(ctx Context, xs [number]) none | error { 16 | if [x, ...xs] = xs { 17 | File'Write(ctx, File'StdOut(), Number'String(x()) + "\n")? 18 | 19 | print(ctx, xs) 20 | } else { 21 | none 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /benchmark/pen/list-comprehension/pen.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "dependencies": { 4 | "Core": "pen:///core", 5 | "Os": "pen:///os" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /benchmark/pen/map-insert/main.pen: -------------------------------------------------------------------------------- 1 | main = \(ctx context) none { 2 | numberSet({number: none}, 100000) 3 | 4 | none 5 | } 6 | 7 | numberSet = \(s {number: none}, n number) {number: none} { 8 | if n == 0 { 9 | s 10 | } else { 11 | numberSet({number: none ...s, n: none}, n - 1) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /benchmark/pen/map-insert/pen.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "dependencies": { 4 | "Os": "pen:///os" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /benchmark/pen/map-update/main.pen: -------------------------------------------------------------------------------- 1 | main = \(ctx context) none { 2 | n = 100000 3 | 4 | numberSet(numberSet({number: none}, n), n) 5 | 6 | none 7 | } 8 | 9 | numberSet = \(s {number: none}, n number) {number: none} { 10 | if n == 0 { 11 | s 12 | } else { 13 | numberSet({number: none ...s, n: none}, n - 1) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /benchmark/pen/map-update/pen.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "dependencies": { 4 | "Os": "pen:///os" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /benchmark/pen/sum/main.pen: -------------------------------------------------------------------------------- 1 | import Core'Number 2 | import Os'File 3 | 4 | main = \(ctx context) none { 5 | _ = File'Write( 6 | ctx.Os, 7 | File'StdOut(), 8 | Number'String(sum(0, 100000000)), 9 | ) 10 | 11 | none 12 | } 13 | 14 | sum = \(x number, i number) number { 15 | if i == 0 { 16 | x 17 | } else { 18 | sum(x + i, i - 1) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /benchmark/pen/sum/pen.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "dependencies": { 4 | "Core": "pen:///core", 5 | "Os": "pen:///os" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /benchmark/rust/fibonacci/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fibonacci" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /benchmark/rust/fibonacci/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | dbg!(fibonacci(40.0)); 3 | } 4 | 5 | fn fibonacci(n: f64) -> f64 { 6 | if n <= 0.0 { 7 | 0.0 8 | } else if n == 1.0 { 9 | 1.0 10 | } else { 11 | fibonacci(n - 1.0) + fibonacci(n - 2.0) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /benchmark/rust/im_hash_map_insert/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "im-hash-map-insert" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | im = "15" 8 | ordered-float = "5" 9 | -------------------------------------------------------------------------------- /benchmark/rust/im_hash_map_insert/src/main.rs: -------------------------------------------------------------------------------- 1 | use ordered_float::OrderedFloat; 2 | 3 | fn main() { 4 | let mut map = im::HashMap::new(); 5 | 6 | for key in 0..100_000 { 7 | map.insert(OrderedFloat::from(key as f64), ()); 8 | } 9 | 10 | dbg!(map.len()); 11 | } 12 | -------------------------------------------------------------------------------- /benchmark/rust/im_hash_map_update/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "im-hash-map-update" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | im = "15" 8 | ordered-float = "5" 9 | -------------------------------------------------------------------------------- /benchmark/rust/im_hash_map_update/src/main.rs: -------------------------------------------------------------------------------- 1 | use ordered_float::OrderedFloat; 2 | 3 | fn main() { 4 | let mut map = im::HashMap::new(); 5 | 6 | for key in 0..100_000 { 7 | map = map.update(OrderedFloat::from(key as f64), ()); 8 | } 9 | 10 | dbg!(map.len()); 11 | } 12 | -------------------------------------------------------------------------------- /benchmark/rust/std_hash_map_insert/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "std-hash-map-insert" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | ordered-float = "5" 8 | -------------------------------------------------------------------------------- /benchmark/rust/std_hash_map_insert/src/main.rs: -------------------------------------------------------------------------------- 1 | use ordered_float::OrderedFloat; 2 | use std::collections::HashMap; 3 | 4 | fn main() { 5 | let mut map = HashMap::new(); 6 | 7 | for key in 0..100_000 { 8 | map.insert(OrderedFloat::from(key as f64), ()); 9 | } 10 | 11 | dbg!(map.len()); 12 | } 13 | -------------------------------------------------------------------------------- /benchmark/rust/sum/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sum" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /benchmark/rust/sum/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let mut sum = 0.0; 3 | 4 | for i in 0..=100_000_000 { 5 | sum += i as f64; 6 | } 7 | 8 | dbg!(sum); 9 | } 10 | -------------------------------------------------------------------------------- /cmd/pen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pen" 3 | version = "0.6.9" 4 | authors = ["Yota Toyama "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | clap = { version = "4", features = ["cargo"] } 9 | app = { path = "../../lib/app" } 10 | indoc = "2" 11 | infra = { path = "../../lib/infra" } 12 | url = "2" 13 | -------------------------------------------------------------------------------- /cmd/pen/src/documentation_configuration.rs: -------------------------------------------------------------------------------- 1 | use crate::application_configuration::APPLICATION_CONFIGURATION; 2 | use app::package_documentation_generator::DocumentationConfiguration; 3 | use std::sync::LazyLock; 4 | 5 | pub static DOCUMENTATION_CONFIGURATION: LazyLock = 6 | LazyLock::new(|| DocumentationConfiguration { 7 | language: "pen".into(), 8 | private_names: [APPLICATION_CONFIGURATION 9 | .main_module 10 | .new_system_context_function_name 11 | .to_string()] 12 | .into_iter() 13 | .collect(), 14 | }); 15 | -------------------------------------------------------------------------------- /cmd/pen/src/main_package_directory_finder.rs: -------------------------------------------------------------------------------- 1 | use crate::file_path_configuration::BUILD_CONFIGURATION_FILENAME; 2 | 3 | pub fn find() -> Result> { 4 | let mut directory: &std::path::Path = &std::env::current_dir()?; 5 | 6 | while !directory.join(BUILD_CONFIGURATION_FILENAME).exists() { 7 | directory = directory.parent().ok_or_else(|| { 8 | std::io::Error::new( 9 | std::io::ErrorKind::NotFound, 10 | format!("file {BUILD_CONFIGURATION_FILENAME} not found in any parent directory",), 11 | ) 12 | })? 13 | } 14 | 15 | Ok(directory.into()) 16 | } 17 | -------------------------------------------------------------------------------- /cmd/pen/src/module_formatter.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | error::Error, 3 | io::{stdin, stdout, Read, Write}, 4 | }; 5 | 6 | const STDIN_PATH: &str = ""; 7 | 8 | pub fn format() -> Result<(), Box> { 9 | let mut source = String::new(); 10 | 11 | stdin().read_to_string(&mut source)?; 12 | 13 | write!( 14 | stdout(), 15 | "{}", 16 | app::module_formatter::format(&source, STDIN_PATH)? 17 | )?; 18 | 19 | Ok(()) 20 | } 21 | -------------------------------------------------------------------------------- /cmd/pen/src/test_configuration.rs: -------------------------------------------------------------------------------- 1 | use std::sync::LazyLock; 2 | 3 | pub static TEST_CONFIGURATION: LazyLock = 4 | LazyLock::new(|| app::TestConfiguration { 5 | test_module_configuration: app::TestModuleConfiguration { 6 | test_function_prefix: "_pen_test_".into(), 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /cmd/test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test" 3 | version = "0.1.0" 4 | publish = false 5 | edition = "2021" 6 | 7 | [dependencies] 8 | ffi = { package = "pen-ffi", version = "*" } 9 | -------------------------------------------------------------------------------- /cmd/test/src/debug.rs: -------------------------------------------------------------------------------- 1 | use std::str; 2 | 3 | #[ffi::bindgen] 4 | pub fn _pen_debug(message: ffi::ByteString) { 5 | eprintln!( 6 | "{}", 7 | str::from_utf8(message.as_slice()).unwrap_or("failed to decode debug message") 8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /cmd/test/src/main.rs: -------------------------------------------------------------------------------- 1 | mod debug; 2 | mod heap; 3 | mod spawn; 4 | mod unreachable; 5 | 6 | fn main() {} 7 | -------------------------------------------------------------------------------- /cmd/test/src/spawn.rs: -------------------------------------------------------------------------------- 1 | #[ffi::bindgen] 2 | fn _pen_spawn(closure: ffi::Arc) -> ffi::Arc { 3 | closure 4 | } 5 | 6 | #[ffi::bindgen] 7 | async fn _pen_yield() -> ffi::None { 8 | unreachable!("thunk lock detected") 9 | } 10 | -------------------------------------------------------------------------------- /cmd/test/src/unreachable.rs: -------------------------------------------------------------------------------- 1 | #[no_mangle] 2 | pub extern "C" fn _pen_unreachable() { 3 | unreachable!("PEN_TEST_UNREACHABLE_ERROR") 4 | } 5 | -------------------------------------------------------------------------------- /codecov.yaml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | threshold: 0.01% 6 | -------------------------------------------------------------------------------- /doc/docs/CNAME: -------------------------------------------------------------------------------- 1 | pen-lang.org 2 | -------------------------------------------------------------------------------- /doc/docs/examples/.gitignore: -------------------------------------------------------------------------------- 1 | *.md 2 | !README.md 3 | -------------------------------------------------------------------------------- /doc/docs/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Pen", 3 | "name": "Pen programming language", 4 | "icons": [{ "src": "icon.svg", "sizes": "any" }], 5 | "start_url": ".", 6 | "display": "standalone", 7 | "theme_color": "#f55", 8 | "background_color": "slategray" 9 | } 10 | -------------------------------------------------------------------------------- /doc/docs/references/standard-packages/.gitignore: -------------------------------------------------------------------------------- 1 | *.md 2 | !README.md 3 | -------------------------------------------------------------------------------- /doc/docs/the-zen.md: -------------------------------------------------------------------------------- 1 | # The Zen 2 | 3 | - Simple is better than complex. 4 | - Explicit is better than implicit. 5 | - Clear is better than clever. 6 | - One way to do one thing 7 | - One solution for many problems 8 | - Steady value over volatile value 9 | 10 | ## References 11 | 12 | - [Go Proverbs](https://go-proverbs.github.io/) 13 | - [The Zen of Python](https://www.python.org/dev/peps/pep-0020/) 14 | - [Zen | The Zig programming language](https://ziglang.org/documentation/master/#Zen) 15 | -------------------------------------------------------------------------------- /doc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "build": "rm -rf theme/assets && mkdir -p theme/assets && cp -r node_modules/@highlightjs/cdn-assets theme/assets/highlight" 4 | }, 5 | "dependencies": { 6 | "@highlightjs/cdn-assets": "^11.11.1" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /doc/requirements.txt: -------------------------------------------------------------------------------- 1 | mdx-truly-sane-lists==1.3 2 | mkdocs==1.6.1 3 | mkdocs-material==9.6.11 4 | -------------------------------------------------------------------------------- /doc/theme/extra.css: -------------------------------------------------------------------------------- 1 | .md-typeset code { 2 | padding: 0.1em 0.3em; 3 | } 4 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | app 2 | -------------------------------------------------------------------------------- /examples/algorithms/fibonacci/README.md: -------------------------------------------------------------------------------- 1 | # Fibonacci number 2 | 3 | [Fibonacci number](https://en.wikipedia.org/wiki/Fibonacci_number) calculation 4 | -------------------------------------------------------------------------------- /examples/algorithms/fibonacci/pen.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "dependencies": { 4 | "Core": "pen:///core", 5 | "Os": "pen:///os" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/algorithms/fizz-buzz/README.md: -------------------------------------------------------------------------------- 1 | # Fizz buzz 2 | 3 | A [fizz buzz](https://en.wikipedia.org/wiki/Fizz_buzz) word game 4 | -------------------------------------------------------------------------------- /examples/algorithms/fizz-buzz/pen.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "dependencies": { 4 | "Core": "pen:///core", 5 | "Os": "pen:///os" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/algorithms/knapsack/README.md: -------------------------------------------------------------------------------- 1 | # The knapsack problem 2 | 3 | An implementation of the knapsack problem using dynamic programming 4 | -------------------------------------------------------------------------------- /examples/algorithms/knapsack/main.pen: -------------------------------------------------------------------------------- 1 | import Core'Number 2 | import Core'String 3 | import Os'Context { Context } 4 | import Os'File 5 | 6 | import 'knapsack { Bag, Item } 7 | 8 | main = \(ctx context) none { 9 | is = knapsack'Pack( 10 | Bag{Capacity: 3}, 11 | [Item 12 | Item{Cost: 1, Value: 1}, 13 | Item{Cost: 2, Value: 1}, 14 | Item{Cost: 1, Value: 2}, 15 | Item{Cost: 2, Value: 5}, 16 | ], 17 | ) 18 | 19 | _ = File'Write( 20 | ctx.Os, 21 | File'StdOut(), 22 | String'Join([string showItem(i()) for i in is], ", ") + "\n", 23 | ) 24 | 25 | none 26 | } 27 | 28 | showItem = \(i Item) string { 29 | "(" + Number'String(i.Cost) + ", " + Number'String(i.Value) + ")" 30 | } 31 | -------------------------------------------------------------------------------- /examples/algorithms/knapsack/pen.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "dependencies": { 4 | "Core": "pen:///core", 5 | "Os": "pen:///os", 6 | "Test": "pen:///test" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/algorithms/parallel/fibonacci/README.md: -------------------------------------------------------------------------------- 1 | # Fibonacci number 2 | 3 | Parallel calculation of [Fibonacci number](https://en.wikipedia.org/wiki/Fibonacci_number) 4 | -------------------------------------------------------------------------------- /examples/algorithms/parallel/fibonacci/pen.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "dependencies": { 4 | "Core": "pen:///core", 5 | "Os": "pen:///os" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/algorithms/quick-sort/README.md: -------------------------------------------------------------------------------- 1 | # Quick sort 2 | -------------------------------------------------------------------------------- /examples/algorithms/quick-sort/main.pen: -------------------------------------------------------------------------------- 1 | import Core'Number 2 | import Os'Context { Context } 3 | import Os'File 4 | import Random'Random 5 | 6 | import 'sort 7 | 8 | total = \() number { 20 } 9 | 10 | max = \() number { 100 } 11 | 12 | main = \(ctx context) none { 13 | _ = printNumbers( 14 | ctx.Os, 15 | sort'Sort( 16 | [number 17 | Number'Floor(Random'Number(ctx.Random) * max()) 18 | for _ in Number'Sequence(total()) 19 | ], 20 | ), 21 | ) 22 | 23 | none 24 | } 25 | 26 | printNumbers = \(ctx Context, ns [number]) none | error { 27 | if [n, ...ns] = ns { 28 | File'Write(ctx, File'StdOut(), Number'String(n()) + "\n")? 29 | 30 | printNumbers(ctx, ns) 31 | } else { 32 | none 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /examples/algorithms/quick-sort/pen.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "dependencies": { 4 | "Core": "pen:///core", 5 | "Os": "pen:///os", 6 | "Random": "pen:///random", 7 | "Test": "pen:///test" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/algorithms/quick-sort/sort.pen: -------------------------------------------------------------------------------- 1 | Sort = \(ns [number]) [number] { 2 | if [n, ...ns] = ns { 3 | f = \(x number) boolean { x < n() } 4 | 5 | [number 6 | ...Sort([number n() for n in ns if f(n())]), 7 | n(), 8 | ...Sort([number n() for n in ns if !f(n())]), 9 | ] 10 | } else { 11 | [number] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/algorithms/quick-sort/sort.test.pen: -------------------------------------------------------------------------------- 1 | import Test'Assert 2 | 3 | import 'sort 4 | 5 | TestSortNothing = \() none | error { 6 | Assert'Equal(sort'Sort([number]), [number]) 7 | } 8 | 9 | TestSortNumbers = \() none | error { 10 | Assert'Equal(sort'Sort([number 3, 1, 2]), [number 1, 2, 3]) 11 | } 12 | -------------------------------------------------------------------------------- /examples/cat/README.md: -------------------------------------------------------------------------------- 1 | # `cat` command 2 | 3 | An application to concatenate files 4 | 5 | ## Usage 6 | 7 | ```sh 8 | pen build 9 | ./app file_1 file_2 10 | ``` 11 | -------------------------------------------------------------------------------- /examples/cat/pen.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "dependencies": { 4 | "Console": "../console", 5 | "Os": "pen:///os" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/console/Print.pen: -------------------------------------------------------------------------------- 1 | import Os'Context { Context } 2 | import Os'File 3 | 4 | Line = \(ctx Context, s string) none | error { 5 | File'Write(ctx, File'StdOut(), s + "\n")? 6 | 7 | none 8 | } 9 | 10 | Lines = \(ctx Context, ss [string]) none | error { 11 | if [s, ...ss] = ss { 12 | Line(ctx, s())? 13 | Lines(ctx, ss)? 14 | } else { 15 | none 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/console/README.md: -------------------------------------------------------------------------------- 1 | # `Console` 2 | 3 | A library that handles console I/O wrapping the `Os` standard package 4 | -------------------------------------------------------------------------------- /examples/console/pen.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "library", 3 | "dependencies": { 4 | "Core": "pen:///core", 5 | "Os": "pen:///os", 6 | "Test": "pen:///test" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/echo/README.md: -------------------------------------------------------------------------------- 1 | # `echo` command 2 | 3 | An application to echo command line arguments 4 | 5 | ## Usage 6 | 7 | ```sh 8 | pen build 9 | ./app hello world 10 | ``` 11 | -------------------------------------------------------------------------------- /examples/echo/main.pen: -------------------------------------------------------------------------------- 1 | import Console'Print 2 | import Core'String 3 | import Os'Context { Context } 4 | import Os'Environment 5 | import Os'Process 6 | 7 | main = \(ctx context) none { 8 | if e = run(ctx.Os) as none { 9 | none 10 | } else { 11 | _ = Print'Line(ctx.Os, if s = source(e) as string { s } else { "unknown error" }) 12 | Process'Exit(ctx.Os, 1) 13 | } 14 | } 15 | 16 | run = \(ctx Context) none | error { 17 | Print'Line(ctx, String'Join(Environment'Arguments(ctx), " "))? 18 | 19 | none 20 | } 21 | -------------------------------------------------------------------------------- /examples/echo/pen.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "dependencies": { 4 | "Console": "../console", 5 | "Core": "pen:///core", 6 | "Os": "pen:///os", 7 | "Test": "pen:///test" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/hello-world/README.md: -------------------------------------------------------------------------------- 1 | # Hello, world! 2 | 3 | An application to print "Hello, world!" in console 4 | 5 | ## Usage 6 | 7 | ```sh 8 | pen build 9 | ./app 10 | ``` 11 | -------------------------------------------------------------------------------- /examples/hello-world/main.pen: -------------------------------------------------------------------------------- 1 | import Os'Context { Context } 2 | import Os'File 3 | 4 | main = \(ctx context) none { 5 | _ = File'Write(ctx.Os, File'StdOut(), "Hello, world!\n") 6 | 7 | none 8 | } 9 | -------------------------------------------------------------------------------- /examples/hello-world/pen.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "dependencies": { 4 | "Os": "pen:///os" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/http-client/README.md: -------------------------------------------------------------------------------- 1 | # HTTP client 2 | 3 | An HTTP client to send a request to a server 4 | 5 | ## Usage 6 | 7 | ```sh 8 | pen build 9 | ./app get http://localhost:8080 'Hello, world!' 10 | ``` 11 | -------------------------------------------------------------------------------- /examples/http-client/pen.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "dependencies": { 4 | "Core": "pen:///core", 5 | "Flag": "pen:///flag", 6 | "Http": "pen:///http", 7 | "Os": "pen:///os" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/http-server/README.md: -------------------------------------------------------------------------------- 1 | # HTTP server 2 | 3 | An HTTP server that echos request bodies 4 | 5 | ## Usage 6 | 7 | ```sh 8 | pen build 9 | ./app localhost:8080 10 | ``` 11 | -------------------------------------------------------------------------------- /examples/http-server/pen.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "dependencies": { 4 | "Core": "pen:///core", 5 | "Flag": "pen:///flag", 6 | "Http": "pen:///http", 7 | "Os": "pen:///os" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/life-game/README.md: -------------------------------------------------------------------------------- 1 | # Conway's game of life 2 | 3 | An application that runs Conway's game of life in console 4 | 5 | ## Usage 6 | 7 | ```sh 8 | pen build 9 | ./app 10 | ``` 11 | -------------------------------------------------------------------------------- /examples/life-game/pen.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "dependencies": { 4 | "Console": "../console", 5 | "Core": "pen:///core", 6 | "Os": "pen:///os", 7 | "Random": "pen:///random", 8 | "Test": "pen:///test" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/ls/README.md: -------------------------------------------------------------------------------- 1 | # `ls` command 2 | 3 | An application to list contents in a directory 4 | 5 | ## Usage 6 | 7 | ```sh 8 | pen build 9 | ./app my-directory 10 | ``` 11 | -------------------------------------------------------------------------------- /examples/ls/arguments.pen: -------------------------------------------------------------------------------- 1 | type Arguments { 2 | Directory string 3 | } 4 | 5 | Parse = \(xs [string]) Arguments | error { 6 | if [x, ...xs] = xs { 7 | if [_, ..._] = xs { 8 | error("too many arguments") 9 | } else { 10 | Arguments{Directory: x()} 11 | } 12 | } else { 13 | Arguments{Directory: "."} 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/ls/main.pen: -------------------------------------------------------------------------------- 1 | import Console'Print 2 | import Os'Context { Context } 3 | import Os'Directory 4 | import Os'Environment 5 | import Os'Process 6 | 7 | import 'arguments 8 | 9 | main = \(ctx context) none { 10 | if e = run(ctx.Os) as none { 11 | none 12 | } else { 13 | _ = Print'Line(ctx.Os, if s = source(e) as string { s } else { "unknown error" }) 14 | Process'Exit(ctx.Os, 1) 15 | } 16 | } 17 | 18 | run = \(ctx Context) none | error { 19 | args = arguments'Parse(Environment'Arguments(ctx))? 20 | 21 | Print'Lines(ctx, Directory'Read(ctx, args.Directory)?)? 22 | 23 | none 24 | } 25 | -------------------------------------------------------------------------------- /examples/ls/pen.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "dependencies": { 4 | "Console": "../console", 5 | "Core": "pen:///core", 6 | "Os": "pen:///os", 7 | "Test": "pen:///test" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/snake/README.md: -------------------------------------------------------------------------------- 1 | # Snake 2 | 3 | A snake game 4 | 5 | ## Usage 6 | 7 | ```sh 8 | pen build 9 | ./app 10 | ``` 11 | -------------------------------------------------------------------------------- /examples/snake/direction.pen: -------------------------------------------------------------------------------- 1 | type Up {} 2 | 3 | type Down {} 4 | 5 | type Left {} 6 | 7 | type Right {} 8 | 9 | type Direction = Up | Down | Left | Right 10 | -------------------------------------------------------------------------------- /examples/snake/entity.pen: -------------------------------------------------------------------------------- 1 | import 'entity'frog { Frog } 2 | import 'entity'snake { Snake } 3 | 4 | type Entity = Snake | Frog 5 | -------------------------------------------------------------------------------- /examples/snake/entity/frog.pen: -------------------------------------------------------------------------------- 1 | import 'position { Position } 2 | 3 | type Frog { 4 | position Position 5 | } 6 | 7 | New = \(p Position) Frog { 8 | Frog{position: p} 9 | } 10 | 11 | Position = \(f Frog) Position { 12 | f.position 13 | } 14 | -------------------------------------------------------------------------------- /examples/snake/field.pen: -------------------------------------------------------------------------------- 1 | import 'position { Position } 2 | 3 | type Field { 4 | Width number 5 | Height number 6 | } 7 | 8 | IsInside = \(f Field, p Position) boolean { 9 | 1 <= p.X & p.X <= f.Width & 1 <= p.Y & p.Y <= f.Height 10 | } 11 | -------------------------------------------------------------------------------- /examples/snake/pen.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "dependencies": { 4 | "Console": "../console", 5 | "Core": "pen:///core", 6 | "Os": "pen:///os", 7 | "Random": "pen:///random", 8 | "Test": "pen:///test" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/snake/position.pen: -------------------------------------------------------------------------------- 1 | import 'direction { Direction } 2 | 3 | type Position { 4 | X number 5 | Y number 6 | } 7 | 8 | Move = \(p Position, d Direction) Position { 9 | if _ = d as direction'Left { 10 | Position{...p, X: p.X - 1} 11 | } else if direction'Right { 12 | Position{...p, X: p.X + 1} 13 | } else if direction'Up { 14 | Position{...p, Y: p.Y - 1} 15 | } else if direction'Down { 16 | Position{...p, Y: p.Y + 1} 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/snake/render.test.pen: -------------------------------------------------------------------------------- 1 | import Test'Assert 2 | 3 | import 'direction 4 | import 'field { Field } 5 | import 'game { Game } 6 | import 'render { Render } 7 | 8 | RenderGame = \() none | error { 9 | Assert'Equal( 10 | Render(game'New(Field{Width: 2, Height: 2}, \() number { 0 })), 11 | [string 12 | "o.", 13 | "..", 14 | ], 15 | ) 16 | } 17 | 18 | RenderGameWithMovedSnake = \() none | error { 19 | g = game'New(Field{Width: 2, Height: 2}, \() number { 0 }) 20 | g = game'MoveSnake(g, direction'Down{}) 21 | g = game'Tick(g) 22 | 23 | if g = g as none { 24 | Assert'Fail() 25 | } else { 26 | Assert'Equal( 27 | Render(g), 28 | [string 29 | "..", 30 | "o.", 31 | ], 32 | ) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /examples/snake/usage.pen: -------------------------------------------------------------------------------- 1 | Usage = \() string { 2 | "h: left, j: down, k: up, l: right, q: quit" 3 | } 4 | -------------------------------------------------------------------------------- /examples/sql-client/README.md: -------------------------------------------------------------------------------- 1 | # SQL client 2 | 3 | A SQL database client to run a query 4 | 5 | ## Usage 6 | 7 | ```sh 8 | pen build 9 | ./app query postgresql://localhost:5432/database 'SELECT * FROM foo;' 10 | ./app execute postgresql://localhost:5432/database "INSERT INTO foo VALUES (1, 'bar');" 11 | ``` 12 | -------------------------------------------------------------------------------- /examples/sql-client/arguments.pen: -------------------------------------------------------------------------------- 1 | import Flag'Flag 2 | 3 | import 'command { Command } 4 | 5 | type Arguments { 6 | Command Command 7 | Uri string 8 | Query string 9 | } 10 | 11 | Parse = \(args [string]) Arguments | error { 12 | f = Flag'Parse(args)? 13 | 14 | if [c, ...ss] = f.Positional { 15 | c = if c() == "query" { 16 | command'Query{} 17 | } else if c() == "execute" { 18 | command'Execute{} 19 | } else { 20 | error("unknown command") 21 | }? 22 | 23 | if [u, ...ss] = ss { 24 | if [q, ...ss] = ss { 25 | Arguments{Command: c, Uri: u(), Query: q()} 26 | } else { 27 | error("query missing") 28 | } 29 | } else { 30 | error("uri missing") 31 | } 32 | } else { 33 | error("command missing") 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/sql-client/command.pen: -------------------------------------------------------------------------------- 1 | type Query {} 2 | 3 | type Execute {} 4 | 5 | type Command = Query | Execute 6 | -------------------------------------------------------------------------------- /examples/sql-client/pen.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "dependencies": { 4 | "Core": "pen:///core", 5 | "Flag": "pen:///flag", 6 | "Sql": "pen:///sql", 7 | "Os": "pen:///os" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/tcp-client/README.md: -------------------------------------------------------------------------------- 1 | # TCP client 2 | 3 | A TCP client which keeps sending and receiving message counts to a server 4 | 5 | ## Usage 6 | 7 | ```sh 8 | pen build 9 | ./app localhost:12345 10 | ``` 11 | -------------------------------------------------------------------------------- /examples/tcp-client/arguments.pen: -------------------------------------------------------------------------------- 1 | type Arguments { 2 | Host string 3 | Message string 4 | } 5 | 6 | Parse = \(ss [string]) Arguments | error { 7 | if [s, ...ss] = ss { 8 | if [m, ..._] = ss { 9 | Arguments{Host: s(), Message: m()} 10 | } else { 11 | error("message not defined") 12 | } 13 | } else { 14 | error("host not defined") 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/tcp-client/arguments.test.pen: -------------------------------------------------------------------------------- 1 | import Test'Assert 2 | 3 | import 'arguments 4 | 5 | ParseArgument = \() none | error { 6 | args = arguments'Parse([string "localhost:12345", "foo"])? 7 | 8 | Assert'Equal(args.Host, "localhost:12345")? 9 | Assert'Equal(args.Message, "foo") 10 | } 11 | -------------------------------------------------------------------------------- /examples/tcp-client/main.pen: -------------------------------------------------------------------------------- 1 | import Console'Print 2 | import Os'Context { Context } 3 | import Os'Environment 4 | import Os'Process 5 | import Os'Tcp 6 | import Os'Tcp'Stream { Stream } 7 | 8 | import 'arguments 9 | 10 | main = \(ctx context) none { 11 | if e = run(ctx.Os) as none { 12 | none 13 | } else { 14 | _ = Print'Line(ctx.Os, if s = source(e) as string { s } else { "unknown error" }) 15 | Process'Exit(ctx.Os, 1) 16 | } 17 | } 18 | 19 | run = \(ctx Context) none | error { 20 | args = arguments'Parse(Environment'Arguments(ctx))? 21 | 22 | s = Tcp'Connect(ctx, args.Host)? 23 | Tcp'Send(ctx, s, args.Message)? 24 | Print'Line(ctx, Tcp'Receive(ctx, s, 1024)?)? 25 | 26 | none 27 | } 28 | -------------------------------------------------------------------------------- /examples/tcp-client/pen.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "dependencies": { 4 | "Console": "../console", 5 | "Core": "pen:///core", 6 | "Os": "pen:///os", 7 | "Test": "pen:///test" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/tcp-server/README.md: -------------------------------------------------------------------------------- 1 | # TCP server 2 | 3 | A TCP server that echos received messages 4 | 5 | ## Usage 6 | 7 | ```sh 8 | pen build 9 | ./app localhost:12345 10 | ``` 11 | -------------------------------------------------------------------------------- /examples/tcp-server/arguments.pen: -------------------------------------------------------------------------------- 1 | type Arguments { 2 | Host string 3 | } 4 | 5 | Parse = \(ss [string]) Arguments | error { 6 | if [s, ...ss] = ss { 7 | if [_, ..._] = ss { 8 | error("too many arguments") 9 | } else { 10 | Arguments{Host: s()} 11 | } 12 | } else { 13 | error("host not defined") 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/tcp-server/arguments.test.pen: -------------------------------------------------------------------------------- 1 | import Test'Assert 2 | 3 | import 'arguments 4 | 5 | ParseArgument = \() none | error { 6 | args = arguments'Parse([string "localhost:12345"])? 7 | 8 | Assert'Equal(args.Host, "localhost:12345") 9 | } 10 | -------------------------------------------------------------------------------- /examples/tcp-server/pen.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "dependencies": { 4 | "Console": "../console", 5 | "Os": "pen:///os", 6 | "Test": "pen:///test" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/udp-client/README.md: -------------------------------------------------------------------------------- 1 | # UDP client 2 | 3 | A UDP client which keeps sending and receiving message counts to a server 4 | 5 | ## Usage 6 | 7 | ```sh 8 | pen build 9 | ./app localhost:12345 10 | ``` 11 | -------------------------------------------------------------------------------- /examples/udp-client/arguments.pen: -------------------------------------------------------------------------------- 1 | type Arguments { 2 | Host string 3 | Message string 4 | } 5 | 6 | Parse = \(ss [string]) Arguments | error { 7 | if [s, ...ss] = ss { 8 | if [m, ..._] = ss { 9 | Arguments{Host: s(), Message: m()} 10 | } else { 11 | error("message not defined") 12 | } 13 | } else { 14 | error("host not defined") 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/udp-client/arguments.test.pen: -------------------------------------------------------------------------------- 1 | import Test'Assert 2 | 3 | import 'arguments 4 | 5 | ParseArgument = \() none | error { 6 | args = arguments'Parse([string "localhost:12345", "foo"])? 7 | 8 | Assert'Equal(args.Host, "localhost:12345")? 9 | Assert'Equal(args.Message, "foo")? 10 | } 11 | -------------------------------------------------------------------------------- /examples/udp-client/main.pen: -------------------------------------------------------------------------------- 1 | import Console'Print 2 | import Os'Context { Context } 3 | import Os'Environment 4 | import Os'Process 5 | import Os'Udp 6 | 7 | import 'arguments 8 | 9 | main = \(ctx context) none { 10 | if e = run(ctx.Os) as none { 11 | none 12 | } else { 13 | _ = Print'Line(ctx.Os, if s = source(e) as string { s } else { "unknown error" }) 14 | Process'Exit(ctx.Os, 1) 15 | } 16 | } 17 | 18 | run = \(ctx Context) none | error { 19 | args = arguments'Parse(Environment'Arguments(ctx))? 20 | 21 | s = Udp'Bind(ctx, "localhost:0")? 22 | Udp'Connect(ctx, s, args.Host)? 23 | Udp'Send(ctx, s, args.Message)? 24 | Print'Line(ctx, Udp'Receive(ctx, s)?) 25 | } 26 | -------------------------------------------------------------------------------- /examples/udp-client/pen.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "dependencies": { 4 | "Console": "../console", 5 | "Core": "pen:///core", 6 | "Os": "pen:///os", 7 | "Test": "pen:///test" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/udp-server/README.md: -------------------------------------------------------------------------------- 1 | # UDP server 2 | 3 | A UDP server that echos received messages 4 | 5 | ## Usage 6 | 7 | ```sh 8 | pen build 9 | ./app localhost:12345 10 | ``` 11 | -------------------------------------------------------------------------------- /examples/udp-server/arguments.pen: -------------------------------------------------------------------------------- 1 | type Arguments { 2 | Host string 3 | } 4 | 5 | Parse = \(ss [string]) Arguments | error { 6 | if [s, ...ss] = ss { 7 | if [_, ..._] = ss { 8 | error("too many arguments") 9 | } else { 10 | Arguments{Host: s()} 11 | } 12 | } else { 13 | error("host not defined") 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/udp-server/arguments.test.pen: -------------------------------------------------------------------------------- 1 | import Test'Assert 2 | 3 | import 'arguments 4 | 5 | ParseArgument = \() none | error { 6 | args = arguments'Parse([string "localhost:12345"])? 7 | 8 | Assert'Equal(args.Host, "localhost:12345") 9 | } 10 | -------------------------------------------------------------------------------- /examples/udp-server/pen.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "dependencies": { 4 | "Console": "../console", 5 | "Os": "pen:///os", 6 | "Test": "pen:///test" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/yes/README.md: -------------------------------------------------------------------------------- 1 | # `yes` command 2 | 3 | An application to keep printing "yes" 4 | 5 | ## Usage 6 | 7 | ```sh 8 | pen build 9 | ./app 10 | ``` 11 | -------------------------------------------------------------------------------- /examples/yes/arguments.pen: -------------------------------------------------------------------------------- 1 | import Core'String 2 | 3 | type Arguments { 4 | Message string 5 | } 6 | 7 | Parse = \(xs [string]) Arguments { 8 | s = String'Join(xs, " ") 9 | 10 | Arguments{ 11 | Message: if s == "" { 12 | "yes" 13 | } else { 14 | s 15 | }, 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/yes/arguments.test.pen: -------------------------------------------------------------------------------- 1 | import Test'Assert 2 | 3 | import 'arguments 4 | 5 | ParseNoArgument = \() none | error { 6 | args = arguments'Parse([string]) 7 | 8 | Assert'Equal(args.Message, "yes") 9 | } 10 | 11 | ParseOneArgument = \() none | error { 12 | args = arguments'Parse([string "foo"]) 13 | 14 | Assert'Equal(args.Message, "foo") 15 | } 16 | 17 | ParseTwoArguments = \() none | error { 18 | args = arguments'Parse([string "foo", "bar"]) 19 | 20 | Assert'Equal(args.Message, "foo bar") 21 | } 22 | -------------------------------------------------------------------------------- /examples/yes/main.pen: -------------------------------------------------------------------------------- 1 | import Console'Print 2 | import Os'Context { Context } 3 | import Os'Environment 4 | import Os'Process 5 | 6 | import 'arguments 7 | 8 | main = \(ctx context) none { 9 | if e = run(ctx.Os) as none { 10 | none 11 | } else { 12 | _ = Print'Line(ctx.Os, if s = source(e) as string { s } else { "unknown error" }) 13 | Process'Exit(ctx.Os, 1) 14 | } 15 | } 16 | 17 | run = \(ctx Context) none | error { 18 | printLines(ctx, arguments'Parse(Environment'Arguments(ctx)).Message) 19 | } 20 | 21 | printLines = \(ctx Context, s string) none | error { 22 | Print'Line(ctx, s)? 23 | 24 | printLines(ctx, s) 25 | } 26 | -------------------------------------------------------------------------------- /examples/yes/pen.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "dependencies": { 4 | "Console": "../console", 5 | "Core": "pen:///core", 6 | "Os": "pen:///os", 7 | "Test": "pen:///test" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /features/commands/document.feature: -------------------------------------------------------------------------------- 1 | Feature: Generating documentation for a package 2 | Background: 3 | Given a file named "pen.json" with: 4 | """json 5 | { 6 | "type": "library", 7 | "dependencies": {} 8 | } 9 | """ 10 | 11 | Scenario: Generate documentation for a package 12 | Given a file named "Foo.pen" with: 13 | """pen 14 | # Do something nice. 15 | Foo = \() none { 16 | none 17 | } 18 | """ 19 | When I run the following script: 20 | """sh 21 | pen document \ 22 | --name Foo \ 23 | --url https://github.com/foo/foo \ 24 | --description "This package is cool." \ 25 | > Foo.md 26 | """ 27 | Then a file named "Foo.md" should contain "`Foo` package" 28 | -------------------------------------------------------------------------------- /features/smoke/standard-packages.feature: -------------------------------------------------------------------------------- 1 | Feature: Standard packages 2 | Scenario Outline: Build and test standard packages 3 | Given I run the following script: 4 | """ 5 | cp -r $PEN_ROOT/packages . 6 | """ 7 | When I cd to "packages/" 8 | Then I successfully run `pen format --check` 9 | And I successfully run `pen build` 10 | And I successfully run `pen test` 11 | 12 | Examples: 13 | | package | 14 | | core | 15 | | flag | 16 | | html | 17 | | http | 18 | | json | 19 | | os | 20 | | os-sync | 21 | | random | 22 | | reflect | 23 | | regex | 24 | | sql | 25 | | test | 26 | -------------------------------------------------------------------------------- /features/smoke/test-packages.feature: -------------------------------------------------------------------------------- 1 | Feature: Test packages 2 | Scenario Outline: Build and test test packages 3 | Given I run the following script: 4 | """ 5 | cp -r $PEN_ROOT/test . 6 | """ 7 | When I cd to "test/" 8 | Then I successfully run `pen format --check` 9 | And I successfully run `pen build` 10 | And I successfully run `pen test` 11 | 12 | Examples: 13 | | package | 14 | | prelude | 15 | | reflect | 16 | -------------------------------------------------------------------------------- /features/smoke/types/string.feature: -------------------------------------------------------------------------------- 1 | Feature: String 2 | Background: 3 | Given a file named "pen.json" with: 4 | """json 5 | { 6 | "type": "application", 7 | "dependencies": { 8 | "Os": "pen:///os" 9 | } 10 | } 11 | """ 12 | 13 | Scenario: Concatenate zero-length strings 14 | Given a file named "main.pen" with: 15 | """pen 16 | import Os'Process 17 | 18 | main = \(ctx context) none { 19 | debug("" + "foo" + "") 20 | } 21 | """ 22 | When I successfully run `pen build` 23 | Then I successfully run `check_memory_leak.sh ./app` 24 | And I successfully run `./app` 25 | And the stderr from "./app" should contain exactly "\"foo\"" 26 | -------------------------------------------------------------------------------- /features/support/aruba.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'aruba/cucumber' 4 | 5 | Aruba.configure do |config| 6 | config.exit_timeout = 1200 7 | config.home_directory = ENV['HOME'] 8 | end 9 | -------------------------------------------------------------------------------- /features/types/any.feature: -------------------------------------------------------------------------------- 1 | Feature: Any 2 | Background: 3 | Given a file named "pen.json" with: 4 | """json 5 | { 6 | "type": "library", 7 | "dependencies": {} 8 | } 9 | """ 10 | 11 | Scenario: Use an any type 12 | Given a file named "Foo.pen" with: 13 | """pen 14 | f = \() any { 15 | 42 16 | } 17 | """ 18 | When I run `pen build` 19 | Then the exit status should be 0 20 | 21 | Scenario: Downcast an any type 22 | Given a file named "Foo.pen" with: 23 | """pen 24 | f = \(x any) number { 25 | if x = x as number { 26 | x 27 | } else { 28 | 0 29 | } 30 | } 31 | """ 32 | When I run `pen build` 33 | Then the exit status should be 0 34 | -------------------------------------------------------------------------------- /features/types/none.feature: -------------------------------------------------------------------------------- 1 | Feature: None 2 | Background: 3 | Given a file named "pen.json" with: 4 | """json 5 | { 6 | "type": "library", 7 | "dependencies": {} 8 | } 9 | """ 10 | 11 | Scenario: Use a none literal 12 | Given a file named "Foo.pen" with: 13 | """pen 14 | f = \() none { 15 | none 16 | } 17 | """ 18 | When I run `pen build` 19 | Then the exit status should be 0 20 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/pen-lang/pen 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/cucumber/gherkin-go v5.1.0+incompatible // indirect 7 | github.com/raviqqe/gherkin2markdown v0.0.0-20210817012807-29d4bf48914b // indirect 8 | ) 9 | -------------------------------------------------------------------------------- /lib/app/src/application_configuration.rs: -------------------------------------------------------------------------------- 1 | pub struct ApplicationConfiguration { 2 | pub application_filename: String, 3 | pub main_module_basename: String, 4 | pub context_module_basename: String, 5 | pub main_module: MainModuleConfiguration, 6 | } 7 | 8 | pub struct MainModuleConfiguration { 9 | pub source_main_function_name: String, 10 | pub object_main_function_name: String, 11 | pub main_context_type_name: String, 12 | pub system_context_type_name: String, 13 | pub new_system_context_function_name: String, 14 | } 15 | -------------------------------------------------------------------------------- /lib/app/src/common.rs: -------------------------------------------------------------------------------- 1 | pub mod dependency_serializer; 2 | pub mod file_path_resolver; 3 | pub mod interface_serializer; 4 | pub mod module_id_calculator; 5 | pub mod module_test_information_serializer; 6 | pub mod package_id_calculator; 7 | pub mod package_test_information_serializer; 8 | -------------------------------------------------------------------------------- /lib/app/src/common/dependency_serializer.rs: -------------------------------------------------------------------------------- 1 | use crate::infra::FilePath; 2 | use std::{collections::BTreeMap, error::Error}; 3 | 4 | type InterfaceFileMap = BTreeMap; 5 | 6 | pub fn serialize( 7 | interface_files: &InterfaceFileMap, 8 | prelude_interface_files: &[FilePath], 9 | ) -> Result, Box> { 10 | Ok(bincode::serde::encode_to_vec( 11 | (interface_files, prelude_interface_files), 12 | bincode::config::standard(), 13 | )?) 14 | } 15 | 16 | pub fn deserialize(slice: &[u8]) -> Result<(InterfaceFileMap, Vec), Box> { 17 | Ok(bincode::serde::decode_from_slice(slice, bincode::config::standard())?.0) 18 | } 19 | -------------------------------------------------------------------------------- /lib/app/src/common/interface_serializer.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | 3 | pub fn serialize(module: &interface::Module) -> Result, Box> { 4 | Ok(bincode::serde::encode_to_vec( 5 | module, 6 | bincode::config::standard(), 7 | )?) 8 | } 9 | 10 | pub fn deserialize(slice: &[u8]) -> Result> { 11 | Ok(bincode::serde::decode_from_slice(slice, bincode::config::standard())?.0) 12 | } 13 | -------------------------------------------------------------------------------- /lib/app/src/common/module_id_calculator.rs: -------------------------------------------------------------------------------- 1 | use crate::infra::FilePath; 2 | use std::{ 3 | collections::hash_map::DefaultHasher, 4 | hash::{Hash, Hasher}, 5 | }; 6 | 7 | pub fn calculate(source_file: &FilePath) -> String { 8 | let mut hasher = DefaultHasher::new(); 9 | 10 | source_file.hash(&mut hasher); 11 | 12 | format!( 13 | "{}_{:x}", 14 | source_file.file_name().with_extension(""), 15 | hasher.finish() 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /lib/app/src/common/module_test_information_serializer.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | 3 | pub fn serialize(information: &test_info::Module) -> Result, Box> { 4 | Ok(serde_json::to_vec(&information)?) 5 | } 6 | 7 | pub fn deserialize(slice: &[u8]) -> Result> { 8 | Ok(serde_json::from_slice(slice)?) 9 | } 10 | -------------------------------------------------------------------------------- /lib/app/src/common/package_id_calculator.rs: -------------------------------------------------------------------------------- 1 | use regex::Regex; 2 | use std::sync::LazyLock; 3 | 4 | const REPLACEMENT_STRING: &str = "_"; 5 | 6 | static REPLACEMENT_REGEX: LazyLock = LazyLock::new(|| Regex::new(r"[.:/]+").unwrap()); 7 | 8 | pub fn calculate(url: &url::Url) -> String { 9 | REPLACEMENT_REGEX 10 | .replace_all(&format!("{url}"), REPLACEMENT_STRING) 11 | .into() 12 | } 13 | -------------------------------------------------------------------------------- /lib/app/src/common/package_test_information_serializer.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | 3 | pub fn serialize(information: &test_info::Package) -> Result, Box> { 4 | Ok(serde_json::to_vec(&information)?) 5 | } 6 | 7 | pub fn deserialize(slice: &[u8]) -> Result> { 8 | Ok(serde_json::from_slice(slice)?) 9 | } 10 | -------------------------------------------------------------------------------- /lib/app/src/infra/build_script_dependency_compiler.rs: -------------------------------------------------------------------------------- 1 | use super::FilePath; 2 | 3 | pub trait BuildScriptDependencyCompiler { 4 | fn compile(&self, object_file: &FilePath, interface_files: &[FilePath]) -> String; 5 | } 6 | -------------------------------------------------------------------------------- /lib/app/src/infra/build_script_runner.rs: -------------------------------------------------------------------------------- 1 | use super::file_path::FilePath; 2 | use std::error::Error; 3 | 4 | pub trait BuildScriptRunner { 5 | fn run( 6 | &self, 7 | build_script_file: &FilePath, 8 | target_file: &FilePath, 9 | ) -> Result<(), Box>; 10 | } 11 | -------------------------------------------------------------------------------- /lib/app/src/infra/command_runner.rs: -------------------------------------------------------------------------------- 1 | use super::file_path::FilePath; 2 | use std::error::Error; 3 | 4 | pub trait CommandRunner { 5 | fn run(&self, executable_file: &FilePath) -> Result<(), Box>; 6 | } 7 | -------------------------------------------------------------------------------- /lib/app/src/infra/external_package_initializer.rs: -------------------------------------------------------------------------------- 1 | use super::file_path::FilePath; 2 | use std::error::Error; 3 | 4 | pub trait ExternalPackageInitializer { 5 | fn initialize( 6 | &self, 7 | url: &url::Url, 8 | package_directory: &FilePath, 9 | ) -> Result<(), Box>; 10 | } 11 | -------------------------------------------------------------------------------- /lib/app/src/infra/file_path_configuration.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Debug)] 2 | pub struct FilePathConfiguration { 3 | pub source_file_extension: &'static str, 4 | pub object_file_extension: &'static str, 5 | pub interface_file_extension: &'static str, 6 | pub test_information_file_extension: &'static str, 7 | pub archive_file_extension: &'static str, 8 | pub build_script_file_extension: &'static str, 9 | pub test_file_extension: &'static str, 10 | } 11 | -------------------------------------------------------------------------------- /lib/app/src/infra/file_path_displayer.rs: -------------------------------------------------------------------------------- 1 | use super::FilePath; 2 | 3 | pub trait FilePathDisplayer { 4 | fn display(&self, file_path: &FilePath) -> String; 5 | } 6 | -------------------------------------------------------------------------------- /lib/app/src/infra/file_system.rs: -------------------------------------------------------------------------------- 1 | use super::FilePath; 2 | use std::error::Error; 3 | 4 | pub trait FileSystem { 5 | fn exists(&self, path: &FilePath) -> bool; 6 | fn is_directory(&self, path: &FilePath) -> bool; 7 | fn read_directory(&self, path: &FilePath) -> Result, Box>; 8 | fn read_to_string(&self, path: &FilePath) -> Result>; 9 | fn read_to_vec(&self, path: &FilePath) -> Result, Box>; 10 | fn write(&self, path: &FilePath, data: &[u8]) -> Result<(), Box>; 11 | } 12 | -------------------------------------------------------------------------------- /lib/app/src/infra/module_target_source.rs: -------------------------------------------------------------------------------- 1 | pub struct ModuleTargetSource { 2 | package_name: Option, 3 | module_name: String, 4 | } 5 | 6 | impl ModuleTargetSource { 7 | pub fn new(package_name: Option, module_name: impl Into) -> Self { 8 | Self { 9 | package_name, 10 | module_name: module_name.into(), 11 | } 12 | } 13 | 14 | pub fn package_name(&self) -> Option<&str> { 15 | self.package_name.as_deref() 16 | } 17 | 18 | pub fn module_name(&self) -> &str { 19 | &self.module_name 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/app/src/infra/package_configuration_reader.rs: -------------------------------------------------------------------------------- 1 | use super::file_path::FilePath; 2 | use crate::package_configuration::PackageConfiguration; 3 | use std::error::Error; 4 | 5 | pub trait PackageConfigurationReader { 6 | fn read(&self, package_directory: &FilePath) -> Result>; 7 | } 8 | -------------------------------------------------------------------------------- /lib/app/src/infra/package_configuration_writer.rs: -------------------------------------------------------------------------------- 1 | use super::file_path::FilePath; 2 | use crate::package_configuration::PackageConfiguration; 3 | use std::error::Error; 4 | 5 | pub trait PackageConfigurationWriter { 6 | fn write( 7 | &self, 8 | configuration: &PackageConfiguration, 9 | package_directory: &FilePath, 10 | ) -> Result<(), Box>; 11 | } 12 | -------------------------------------------------------------------------------- /lib/app/src/infra/test_linker.rs: -------------------------------------------------------------------------------- 1 | use super::FilePath; 2 | use std::error::Error; 3 | 4 | pub trait TestLinker { 5 | fn link( 6 | &self, 7 | package_test_information: &test_info::Package, 8 | archive_files: &[FilePath], 9 | test_file: &FilePath, 10 | output_directory: &FilePath, 11 | ) -> Result<(), Box>; 12 | } 13 | -------------------------------------------------------------------------------- /lib/app/src/module_compiler/compile_configuration.rs: -------------------------------------------------------------------------------- 1 | pub struct CompileConfiguration { 2 | pub fmm: FmmConfiguration, 3 | pub mir: MirConfiguration, 4 | pub hir: HirConfiguration, 5 | } 6 | 7 | pub type FmmConfiguration = fmm_llvm::InstructionConfiguration; 8 | pub type MirConfiguration = mir_fmm::Configuration; 9 | pub type HashConfiguration = hir_mir::HashConfiguration; 10 | pub type HirConfiguration = hir_mir::CompileConfiguration; 11 | pub type ListTypeConfiguration = hir_mir::ListTypeConfiguration; 12 | pub type MapTypeConfiguration = hir_mir::MapTypeConfiguration; 13 | pub type MapTypeIterationConfiguration = hir_mir::MapTypeIterationConfiguration; 14 | pub type NumberTypeConfiguration = hir_mir::NumberTypeConfiguration; 15 | pub type StringTypeConfiguration = hir_mir::StringTypeConfiguration; 16 | -------------------------------------------------------------------------------- /lib/app/src/module_finder.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | file_finder, 3 | infra::{FilePath, Infrastructure}, 4 | }; 5 | use std::error::Error; 6 | 7 | pub fn find( 8 | infrastructure: &Infrastructure, 9 | package_directory: &FilePath, 10 | ) -> Result, Box> { 11 | file_finder::find( 12 | infrastructure, 13 | package_directory, 14 | infrastructure.file_path_configuration.source_file_extension, 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /lib/app/src/module_formatter.rs: -------------------------------------------------------------------------------- 1 | use parse::{parse, parse_comments}; 2 | use std::error::Error; 3 | 4 | pub fn format(source: &str, path: &str) -> Result> { 5 | Ok(format::format( 6 | &parse(source, path)?, 7 | &parse_comments(source, path)?, 8 | )) 9 | } 10 | -------------------------------------------------------------------------------- /lib/app/src/module_target_source_resolver.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | common::file_path_resolver, 3 | infra::{FilePath, ModuleTargetSource}, 4 | package_name_formatter, 5 | }; 6 | 7 | pub fn resolve( 8 | package_url: Option<&url::Url>, 9 | package_directory: &FilePath, 10 | module_file_path: &FilePath, 11 | ) -> ModuleTargetSource { 12 | ModuleTargetSource::new( 13 | package_url.map(package_name_formatter::format), 14 | file_path_resolver::resolve_module_path_components(package_directory, module_file_path) 15 | .join(ast::IDENTIFIER_SEPARATOR), 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /lib/app/src/package_name_formatter.rs: -------------------------------------------------------------------------------- 1 | pub fn format(url: &url::Url) -> String { 2 | url.to_string() 3 | } 4 | -------------------------------------------------------------------------------- /lib/app/src/test_configuration.rs: -------------------------------------------------------------------------------- 1 | pub struct TestConfiguration { 2 | pub test_module_configuration: TestModuleConfiguration, 3 | } 4 | 5 | pub type TestModuleConfiguration = hir_mir::TestModuleConfiguration; 6 | -------------------------------------------------------------------------------- /lib/app/src/test_module_finder.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | file_finder, 3 | infra::{FilePath, Infrastructure}, 4 | }; 5 | use std::error::Error; 6 | 7 | pub fn find( 8 | infrastructure: &Infrastructure, 9 | package_directory: &FilePath, 10 | ) -> Result, Box> { 11 | file_finder::find( 12 | infrastructure, 13 | package_directory, 14 | infrastructure.file_path_configuration.test_file_extension, 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /lib/ast-hir/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ast_hir" 3 | version = "0.1.0" 4 | authors = ["Yota Toyama "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | ast = { path = "../ast" } 9 | fnv = "1" 10 | hir = { path = "../hir" } 11 | interface = { path = "../interface" } 12 | itertools = "0.14" 13 | position = { path = "../position" } 14 | regex = "1" 15 | 16 | [dev-dependencies] 17 | pretty_assertions = "1.4" 18 | -------------------------------------------------------------------------------- /lib/ast-hir/src/name.rs: -------------------------------------------------------------------------------- 1 | pub fn qualify(prefix: &str, name: &str) -> String { 2 | prefix.to_owned() + ast::IDENTIFIER_SEPARATOR + name 3 | } 4 | 5 | #[cfg(test)] 6 | mod tests { 7 | use super::*; 8 | 9 | #[test] 10 | fn qualify_name() { 11 | assert_eq!(qualify("foo", "bar"), "foo'bar"); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/ast/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ast" 3 | version = "0.1.0" 4 | authors = ["Yota Toyama "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | position = { path = "../position" } 9 | serde = { version = "1", features = ["derive", "rc"] } 10 | -------------------------------------------------------------------------------- /lib/ast/src/ast/argument.rs: -------------------------------------------------------------------------------- 1 | use crate::types::Type; 2 | use position::Position; 3 | 4 | #[derive(Clone, Debug, PartialEq, Eq)] 5 | pub struct Argument { 6 | name: String, 7 | type_: Type, 8 | position: Position, 9 | } 10 | 11 | impl Argument { 12 | pub fn new(name: impl Into, type_: impl Into, position: Position) -> Self { 13 | Self { 14 | name: name.into(), 15 | type_: type_.into(), 16 | position, 17 | } 18 | } 19 | 20 | pub fn name(&self) -> &str { 21 | &self.name 22 | } 23 | 24 | pub fn type_(&self) -> &Type { 25 | &self.type_ 26 | } 27 | 28 | pub fn position(&self) -> &Position { 29 | &self.position 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/ast/src/ast/binary_operator.rs: -------------------------------------------------------------------------------- 1 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 2 | pub enum BinaryOperator { 3 | Add, 4 | Subtract, 5 | Multiply, 6 | Divide, 7 | 8 | And, 9 | Or, 10 | 11 | Equal, 12 | NotEqual, 13 | 14 | LessThan, 15 | LessThanOrEqual, 16 | GreaterThan, 17 | GreaterThanOrEqual, 18 | } 19 | -------------------------------------------------------------------------------- /lib/ast/src/ast/calling_convention.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 2 | pub enum CallingConvention { 3 | Native, 4 | C, 5 | } 6 | 7 | impl Default for CallingConvention { 8 | fn default() -> Self { 9 | Self::Native 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/ast/src/ast/foreign_export.rs: -------------------------------------------------------------------------------- 1 | use crate::CallingConvention; 2 | 3 | #[derive(Clone, Debug, PartialEq, Eq)] 4 | pub struct ForeignExport { 5 | calling_convention: CallingConvention, 6 | } 7 | 8 | impl ForeignExport { 9 | pub fn new(calling_convention: CallingConvention) -> Self { 10 | Self { calling_convention } 11 | } 12 | 13 | pub fn calling_convention(&self) -> CallingConvention { 14 | self.calling_convention 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/ast/src/ast/if_.rs: -------------------------------------------------------------------------------- 1 | use super::{Block, IfBranch}; 2 | use position::Position; 3 | use std::rc::Rc; 4 | 5 | #[derive(Clone, Debug, PartialEq)] 6 | pub struct If { 7 | branches: Vec, 8 | else_: Rc, 9 | position: Position, 10 | } 11 | 12 | impl If { 13 | pub fn new(branches: Vec, else_: Block, position: Position) -> Self { 14 | Self { 15 | branches, 16 | else_: else_.into(), 17 | position, 18 | } 19 | } 20 | 21 | pub fn branches(&self) -> &[IfBranch] { 22 | &self.branches 23 | } 24 | 25 | pub fn else_(&self) -> &Block { 26 | &self.else_ 27 | } 28 | 29 | pub fn position(&self) -> &Position { 30 | &self.position 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/ast/src/ast/if_branch.rs: -------------------------------------------------------------------------------- 1 | use super::{expression::Expression, Block}; 2 | use std::rc::Rc; 3 | 4 | #[derive(Clone, Debug, PartialEq)] 5 | pub struct IfBranch { 6 | condition: Rc, 7 | block: Rc, 8 | } 9 | 10 | impl IfBranch { 11 | pub fn new(condition: impl Into, block: Block) -> Self { 12 | Self { 13 | condition: condition.into().into(), 14 | block: block.into(), 15 | } 16 | } 17 | 18 | pub fn condition(&self) -> &Expression { 19 | &self.condition 20 | } 21 | 22 | pub fn block(&self) -> &Block { 23 | &self.block 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/ast/src/ast/if_type_branch.rs: -------------------------------------------------------------------------------- 1 | use super::Block; 2 | use crate::types::Type; 3 | 4 | #[derive(Clone, Debug, PartialEq)] 5 | pub struct IfTypeBranch { 6 | type_: Type, 7 | block: Block, 8 | } 9 | 10 | impl IfTypeBranch { 11 | pub fn new(type_: impl Into, block: Block) -> Self { 12 | Self { 13 | type_: type_.into(), 14 | block, 15 | } 16 | } 17 | 18 | pub fn type_(&self) -> &Type { 19 | &self.type_ 20 | } 21 | 22 | pub fn block(&self) -> &Block { 23 | &self.block 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/ast/src/ast/list.rs: -------------------------------------------------------------------------------- 1 | use super::list_element::ListElement; 2 | use crate::types::Type; 3 | use position::Position; 4 | 5 | #[derive(Clone, Debug, PartialEq)] 6 | pub struct List { 7 | type_: Type, 8 | elements: Vec, 9 | position: Position, 10 | } 11 | 12 | impl List { 13 | pub fn new(type_: impl Into, elements: Vec, position: Position) -> Self { 14 | Self { 15 | type_: type_.into(), 16 | elements, 17 | position, 18 | } 19 | } 20 | 21 | pub fn type_(&self) -> &Type { 22 | &self.type_ 23 | } 24 | 25 | pub fn elements(&self) -> &[ListElement] { 26 | &self.elements 27 | } 28 | 29 | pub fn position(&self) -> &Position { 30 | &self.position 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/ast/src/ast/list_element.rs: -------------------------------------------------------------------------------- 1 | use super::expression::Expression; 2 | use position::Position; 3 | 4 | #[derive(Clone, Debug, PartialEq)] 5 | pub enum ListElement { 6 | Multiple(Expression), 7 | Single(Expression), 8 | } 9 | 10 | impl ListElement { 11 | pub fn position(&self) -> &Position { 12 | match self { 13 | Self::Multiple(expression) => expression.position(), 14 | Self::Single(expression) => expression.position(), 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/ast/src/ast/map_element.rs: -------------------------------------------------------------------------------- 1 | use super::expression::Expression; 2 | use crate::MapEntry; 3 | use position::Position; 4 | 5 | #[derive(Clone, Debug, PartialEq)] 6 | pub enum MapElement { 7 | Multiple(Expression), 8 | Single(MapEntry), 9 | } 10 | 11 | impl MapElement { 12 | pub fn position(&self) -> &Position { 13 | match self { 14 | Self::Multiple(expression) => expression.position(), 15 | Self::Single(entry) => entry.position(), 16 | } 17 | } 18 | } 19 | 20 | impl From for MapElement { 21 | fn from(entry: MapEntry) -> Self { 22 | Self::Single(entry) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/ast/src/ast/number.rs: -------------------------------------------------------------------------------- 1 | use crate::NumberRepresentation; 2 | use position::Position; 3 | 4 | #[derive(Clone, Debug, PartialEq, Eq)] 5 | pub struct Number { 6 | value: NumberRepresentation, 7 | position: Position, 8 | } 9 | 10 | impl Number { 11 | pub fn new(value: NumberRepresentation, position: Position) -> Self { 12 | Self { value, position } 13 | } 14 | 15 | pub fn value(&self) -> &NumberRepresentation { 16 | &self.value 17 | } 18 | 19 | pub fn position(&self) -> &Position { 20 | &self.position 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/ast/src/ast/number_representation.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Debug, PartialEq, Eq)] 2 | pub enum NumberRepresentation { 3 | Binary(String), 4 | Hexadecimal(String), 5 | FloatingPoint(String), 6 | } 7 | -------------------------------------------------------------------------------- /lib/ast/src/ast/string.rs: -------------------------------------------------------------------------------- 1 | use position::Position; 2 | 3 | #[derive(Clone, Debug, PartialEq, Eq)] 4 | pub struct ByteString { 5 | value: String, // UTF-8 representation of byte string 6 | position: Position, 7 | } 8 | 9 | impl ByteString { 10 | pub fn new(value: impl Into, position: Position) -> Self { 11 | Self { 12 | value: value.into(), 13 | position, 14 | } 15 | } 16 | 17 | pub fn value(&self) -> &str { 18 | &self.value 19 | } 20 | 21 | pub fn position(&self) -> &Position { 22 | &self.position 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/ast/src/ast/type_alias.rs: -------------------------------------------------------------------------------- 1 | use crate::types::Type; 2 | use position::Position; 3 | 4 | #[derive(Clone, Debug, PartialEq, Eq)] 5 | pub struct TypeAlias { 6 | name: String, 7 | type_: Type, 8 | position: Position, 9 | } 10 | 11 | impl TypeAlias { 12 | pub fn new(name: impl Into, type_: impl Into, position: Position) -> Self { 13 | Self { 14 | name: name.into(), 15 | type_: type_.into(), 16 | position, 17 | } 18 | } 19 | 20 | pub fn name(&self) -> &str { 21 | &self.name 22 | } 23 | 24 | pub fn type_(&self) -> &Type { 25 | &self.type_ 26 | } 27 | 28 | pub fn position(&self) -> &Position { 29 | &self.position 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/ast/src/ast/unary_operator.rs: -------------------------------------------------------------------------------- 1 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 2 | pub enum UnaryOperator { 3 | Not, 4 | Try, 5 | } 6 | -------------------------------------------------------------------------------- /lib/ast/src/ast/unqualified_name.rs: -------------------------------------------------------------------------------- 1 | use position::Position; 2 | 3 | #[derive(Clone, Debug, PartialEq, Eq)] 4 | pub struct UnqualifiedName { 5 | name: String, 6 | position: Position, 7 | } 8 | 9 | impl UnqualifiedName { 10 | pub fn new(name: impl Into, position: Position) -> Self { 11 | Self { 12 | name: name.into(), 13 | position, 14 | } 15 | } 16 | 17 | pub fn name(&self) -> &str { 18 | &self.name 19 | } 20 | 21 | pub fn position(&self) -> &Position { 22 | &self.position 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/ast/src/ast/variable.rs: -------------------------------------------------------------------------------- 1 | use position::Position; 2 | 3 | #[derive(Clone, Debug, PartialEq, Eq)] 4 | pub struct Variable { 5 | name: String, 6 | position: Position, 7 | } 8 | 9 | impl Variable { 10 | pub fn new(name: impl Into, position: Position) -> Self { 11 | Self { 12 | name: name.into(), 13 | position, 14 | } 15 | } 16 | 17 | pub fn name(&self) -> &str { 18 | &self.name 19 | } 20 | 21 | pub fn position(&self) -> &Position { 22 | &self.position 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/ast/src/comment.rs: -------------------------------------------------------------------------------- 1 | use position::Position; 2 | 3 | #[derive(Clone, Debug, PartialEq, Eq)] 4 | pub struct Comment { 5 | line: String, 6 | position: Position, 7 | } 8 | 9 | impl Comment { 10 | pub fn new(line: impl Into, position: Position) -> Self { 11 | Self { 12 | line: line.into(), 13 | position, 14 | } 15 | } 16 | 17 | pub fn line(&self) -> &str { 18 | &self.line 19 | } 20 | 21 | pub fn position(&self) -> &Position { 22 | &self.position 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/ast/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod analysis; 2 | mod ast; 3 | mod comment; 4 | pub mod types; 5 | 6 | pub use self::ast::*; 7 | pub use comment::Comment; 8 | -------------------------------------------------------------------------------- /lib/ast/src/types.rs: -------------------------------------------------------------------------------- 1 | mod function; 2 | mod list; 3 | mod map; 4 | mod record; 5 | mod record_field; 6 | mod reference; 7 | mod type_; 8 | mod union; 9 | 10 | pub use function::*; 11 | pub use list::*; 12 | pub use map::*; 13 | pub use record::*; 14 | pub use record_field::*; 15 | pub use reference::*; 16 | pub use type_::*; 17 | pub use union::*; 18 | -------------------------------------------------------------------------------- /lib/ast/src/types/list.rs: -------------------------------------------------------------------------------- 1 | use super::Type; 2 | use position::Position; 3 | use std::rc::Rc; 4 | 5 | #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 6 | pub struct List { 7 | element: Rc, 8 | position: Position, 9 | } 10 | 11 | impl List { 12 | pub fn new(element: impl Into, position: Position) -> Self { 13 | Self { 14 | element: Rc::new(element.into()), 15 | position, 16 | } 17 | } 18 | 19 | pub fn element(&self) -> &Type { 20 | &self.element 21 | } 22 | 23 | pub fn position(&self) -> &Position { 24 | &self.position 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/ast/src/types/map.rs: -------------------------------------------------------------------------------- 1 | use super::Type; 2 | use position::Position; 3 | use std::rc::Rc; 4 | 5 | #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 6 | pub struct Map { 7 | key: Rc, 8 | value: Rc, 9 | position: Position, 10 | } 11 | 12 | impl Map { 13 | pub fn new(key: impl Into, value: impl Into, position: Position) -> Self { 14 | Self { 15 | key: Rc::new(key.into()), 16 | value: Rc::new(value.into()), 17 | position, 18 | } 19 | } 20 | 21 | pub fn key(&self) -> &Type { 22 | &self.key 23 | } 24 | 25 | pub fn value(&self) -> &Type { 26 | &self.value 27 | } 28 | 29 | pub fn position(&self) -> &Position { 30 | &self.position 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/ast/src/types/record.rs: -------------------------------------------------------------------------------- 1 | use position::Position; 2 | 3 | #[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] 4 | pub struct Record { 5 | name: String, 6 | position: Position, 7 | } 8 | 9 | impl Record { 10 | pub fn new(name: impl Into, position: Position) -> Self { 11 | Self { 12 | name: name.into(), 13 | position, 14 | } 15 | } 16 | 17 | pub fn name(&self) -> &str { 18 | &self.name 19 | } 20 | 21 | pub fn position(&self) -> &Position { 22 | &self.position 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/ast/src/types/record_field.rs: -------------------------------------------------------------------------------- 1 | use super::Type; 2 | use position::Position; 3 | 4 | #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 5 | pub struct RecordField { 6 | name: String, 7 | type_: Type, 8 | position: Position, 9 | } 10 | 11 | impl RecordField { 12 | pub fn new(name: impl Into, type_: impl Into, position: Position) -> Self { 13 | Self { 14 | name: name.into(), 15 | type_: type_.into(), 16 | position, 17 | } 18 | } 19 | 20 | pub fn name(&self) -> &str { 21 | &self.name 22 | } 23 | 24 | pub fn type_(&self) -> &Type { 25 | &self.type_ 26 | } 27 | 28 | pub fn position(&self) -> &Position { 29 | &self.position 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/ast/src/types/reference.rs: -------------------------------------------------------------------------------- 1 | use position::Position; 2 | 3 | #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 4 | pub struct Reference { 5 | name: String, 6 | position: Position, 7 | } 8 | 9 | impl Reference { 10 | pub fn new(name: impl Into, position: Position) -> Self { 11 | Self { 12 | name: name.into(), 13 | position, 14 | } 15 | } 16 | 17 | pub fn name(&self) -> &str { 18 | &self.name 19 | } 20 | 21 | pub fn position(&self) -> &Position { 22 | &self.position 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/ast/src/types/union.rs: -------------------------------------------------------------------------------- 1 | use super::Type; 2 | use position::Position; 3 | use std::rc::Rc; 4 | 5 | #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 6 | pub struct Union { 7 | lhs: Rc, 8 | rhs: Rc, 9 | position: Position, 10 | } 11 | 12 | impl Union { 13 | pub fn new(lhs: impl Into, rhs: impl Into, position: Position) -> Self { 14 | Self { 15 | lhs: lhs.into().into(), 16 | rhs: rhs.into().into(), 17 | position, 18 | } 19 | } 20 | 21 | pub fn lhs(&self) -> &Type { 22 | &self.lhs 23 | } 24 | 25 | pub fn rhs(&self) -> &Type { 26 | &self.rhs 27 | } 28 | 29 | pub fn position(&self) -> &Position { 30 | &self.position 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/doc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "doc" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | ast = { path = "../ast" } 8 | format = { path = "../format" } 9 | indoc = "2" 10 | itertools = "0.14" 11 | position = { path = "../position" } 12 | 13 | [dev-dependencies] 14 | pretty_assertions = "1" 15 | -------------------------------------------------------------------------------- /lib/doc/src/ir.rs: -------------------------------------------------------------------------------- 1 | pub mod build; 2 | mod document; 3 | 4 | pub use document::*; 5 | -------------------------------------------------------------------------------- /lib/doc/src/ir/document.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Debug, PartialEq)] 2 | pub struct Section { 3 | pub title: Text, 4 | pub paragraphs: Vec, 5 | pub children: Vec
, 6 | } 7 | 8 | #[derive(Clone, Debug, PartialEq, Eq)] 9 | pub enum Paragraph { 10 | Text(Text), 11 | Code { language: String, code: String }, 12 | } 13 | 14 | #[derive(Clone, Debug, PartialEq, Eq)] 15 | pub struct Text { 16 | pub spans: Vec, 17 | } 18 | 19 | #[derive(Clone, Debug, PartialEq, Eq)] 20 | pub enum Span { 21 | Normal(String), 22 | Code(String), 23 | } 24 | 25 | impl From for Paragraph { 26 | fn from(text: Text) -> Self { 27 | Self::Text(text) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/ffi-macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pen-ffi-macro" 3 | description = "FFI macro library for Pen programming language" 4 | version = "0.5.0" 5 | publish = true 6 | edition = "2021" 7 | license = "MIT" 8 | authors = ["Yota Toyama "] 9 | repository = "https://github.com/pen-lang/pen" 10 | 11 | [lib] 12 | proc-macro = true 13 | 14 | [dependencies] 15 | convert_case = "0.8" 16 | proc-macro2 = "1" 17 | quote = "1" 18 | syn = { version = "2", features = ["full"] } 19 | 20 | [dev-dependencies] 21 | pen-ffi = { path = "../ffi" } 22 | -------------------------------------------------------------------------------- /lib/ffi-macro/src/attribute_list.rs: -------------------------------------------------------------------------------- 1 | use syn::{ 2 | parse::{Parse, ParseStream}, 3 | punctuated::Punctuated, 4 | Meta, Result, Token, 5 | }; 6 | 7 | pub struct AttributeList { 8 | variables: Vec, 9 | } 10 | 11 | impl AttributeList { 12 | pub fn variables(&self) -> impl Iterator { 13 | self.variables.iter() 14 | } 15 | } 16 | 17 | impl Parse for AttributeList { 18 | fn parse(input: ParseStream) -> Result { 19 | Ok(Self { 20 | variables: Punctuated::::parse_terminated(input)? 21 | .into_iter() 22 | .collect(), 23 | }) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/ffi-macro/tests/type_.rs: -------------------------------------------------------------------------------- 1 | use pen_ffi_macro::{any, into_any}; 2 | 3 | #[any(crate = "pen_ffi")] 4 | #[derive(Clone)] 5 | struct ZeroSized {} 6 | 7 | #[allow(dead_code)] 8 | #[any(crate = "pen_ffi")] 9 | #[derive(Clone)] 10 | struct PointerSized { 11 | x: usize, 12 | } 13 | 14 | #[into_any(crate = "pen_ffi", into_fn = "foo_to_any")] 15 | #[repr(C)] 16 | struct Foo { 17 | x: usize, 18 | } 19 | -------------------------------------------------------------------------------- /lib/ffi/src/boolean.rs: -------------------------------------------------------------------------------- 1 | #[pen_ffi_macro::into_any(crate = "crate", into_fn = "pen_ffi_boolean_to_any")] 2 | #[repr(C)] 3 | #[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] 4 | pub struct Boolean { 5 | value: bool, 6 | } 7 | 8 | impl Boolean { 9 | pub fn new(value: bool) -> Self { 10 | Self { value } 11 | } 12 | } 13 | 14 | impl From for bool { 15 | fn from(number: Boolean) -> Self { 16 | number.value 17 | } 18 | } 19 | 20 | impl From for Boolean { 21 | fn from(value: bool) -> Self { 22 | Self::new(value) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/ffi/src/constants.rs: -------------------------------------------------------------------------------- 1 | pub const DEFAULT_MEMORY_ALIGNMENT: usize = 8; 2 | -------------------------------------------------------------------------------- /lib/ffi/src/cps.rs: -------------------------------------------------------------------------------- 1 | mod async_stack; 2 | mod async_stack_action; 3 | mod error; 4 | mod import; 5 | mod stack; 6 | 7 | pub use async_stack::*; 8 | pub use error::*; 9 | pub use stack::*; 10 | -------------------------------------------------------------------------------- /lib/ffi/src/cps/async_stack_action.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 2 | pub enum AsyncStackAction { 3 | Suspend, 4 | Resume, 5 | Restore, 6 | } 7 | -------------------------------------------------------------------------------- /lib/ffi/src/cps/error.rs: -------------------------------------------------------------------------------- 1 | use super::async_stack_action::AsyncStackAction; 2 | use core::fmt::{self, Display, Formatter}; 3 | 4 | #[derive(Debug, PartialEq, Eq)] 5 | pub enum CpsError { 6 | MissingContext, 7 | UnexpectedAsyncStackAction(Option), 8 | } 9 | 10 | // TODO Implement std::error::Error when it is not `no_std`. 11 | 12 | impl Display for CpsError { 13 | fn fmt(&self, formatter: &mut Formatter) -> Result<(), fmt::Error> { 14 | match self { 15 | Self::MissingContext => { 16 | write!(formatter, "missing context") 17 | } 18 | Self::UnexpectedAsyncStackAction(expected) => { 19 | write!(formatter, "invalid stack action (expected: {expected:?})") 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/ffi/src/error.rs: -------------------------------------------------------------------------------- 1 | use crate::{Any, Arc}; 2 | 3 | #[pen_ffi_macro::into_any(crate = "crate", into_fn = "pen_ffi_error_to_any")] 4 | #[repr(C)] 5 | #[derive(Clone)] 6 | pub struct Error(Arc); 7 | 8 | #[repr(C)] 9 | struct ErrorInner { 10 | source: Any, 11 | } 12 | 13 | impl Error { 14 | pub fn new(source: impl Into) -> Self { 15 | Self( 16 | ErrorInner { 17 | source: source.into(), 18 | } 19 | .into(), 20 | ) 21 | } 22 | } 23 | 24 | #[cfg(feature = "std")] 25 | impl From for Error { 26 | fn from(error: T) -> Self { 27 | use alloc::string::ToString; 28 | 29 | Self::new(crate::ByteString::from(error.to_string())) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/ffi/src/extra.rs: -------------------------------------------------------------------------------- 1 | mod result; 2 | 3 | pub use result::*; 4 | -------------------------------------------------------------------------------- /lib/ffi/src/future.rs: -------------------------------------------------------------------------------- 1 | mod call; 2 | mod call_function; 3 | mod from_closure; 4 | mod from_function; 5 | pub mod stream; 6 | mod to_closure; 7 | 8 | pub use from_closure::*; 9 | pub use from_function::*; 10 | pub use to_closure::*; 11 | 12 | pub mod __private { 13 | pub const INITIAL_STACK_CAPACITY: usize = 64; 14 | } 15 | -------------------------------------------------------------------------------- /lib/ffi/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | extern crate alloc; 4 | #[cfg(any(test, feature = "std"))] 5 | extern crate std; 6 | 7 | mod any; 8 | mod arc; 9 | mod boolean; 10 | mod closure; 11 | mod constants; 12 | pub mod cps; 13 | mod error; 14 | pub mod extra; 15 | pub mod future; 16 | mod list; 17 | mod none; 18 | mod number; 19 | #[cfg(feature = "runtime")] 20 | pub mod runtime; 21 | mod string; 22 | mod type_information; 23 | 24 | pub use any::*; 25 | pub use arc::*; 26 | pub use boolean::*; 27 | pub use closure::*; 28 | pub use constants::*; 29 | pub use error::*; 30 | pub use list::*; 31 | pub use none::*; 32 | pub use number::*; 33 | pub use pen_ffi_macro::*; 34 | pub use string::*; 35 | pub use type_information::*; 36 | -------------------------------------------------------------------------------- /lib/ffi/src/none.rs: -------------------------------------------------------------------------------- 1 | #[pen_ffi_macro::into_any(crate = "crate", into_fn = "pen_ffi_none_to_any")] 2 | #[repr(C)] 3 | #[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] 4 | pub struct None { 5 | _private: [u8; 0], 6 | } 7 | 8 | impl None { 9 | pub const fn new() -> Self { 10 | Self { _private: [] } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/ffi/src/number.rs: -------------------------------------------------------------------------------- 1 | #[pen_ffi_macro::into_any(crate = "crate", into_fn = "pen_ffi_number_to_any")] 2 | #[repr(transparent)] 3 | #[derive(Clone, Copy, Debug, Default, PartialEq)] 4 | pub struct Number { 5 | value: f64, 6 | } 7 | 8 | impl Number { 9 | pub const fn new(value: f64) -> Self { 10 | Self { value } 11 | } 12 | } 13 | 14 | impl From for f64 { 15 | fn from(number: Number) -> Self { 16 | number.value 17 | } 18 | } 19 | 20 | impl From for usize { 21 | fn from(number: Number) -> Self { 22 | number.value as Self 23 | } 24 | } 25 | 26 | impl From for Number { 27 | fn from(value: f64) -> Self { 28 | Self { value } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/ffi/src/runtime.rs: -------------------------------------------------------------------------------- 1 | mod error; 2 | 3 | use crate::Error; 4 | use error::RuntimeError; 5 | use std::sync::RwLock; 6 | use tokio::runtime::Handle; 7 | 8 | static RUNTIME_HANDLE: RwLock> = RwLock::new(None); 9 | 10 | pub fn set_handle(handle: Handle) -> Result<(), Error> { 11 | RUNTIME_HANDLE 12 | .write() 13 | .map_err(|_| RuntimeError::HandleLockPoisoned)? 14 | .replace(handle); 15 | 16 | Ok(()) 17 | } 18 | 19 | pub fn handle() -> Result { 20 | let guard = RUNTIME_HANDLE.read()?; 21 | 22 | if let Some(handle) = guard.as_ref() { 23 | Ok(handle.clone()) 24 | } else { 25 | Err(RuntimeError::HandleNotInitialized.into()) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/ffi/src/runtime/error.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::{self, Display, Formatter}; 2 | use std::error::Error; 3 | 4 | #[derive(Debug, Clone)] 5 | pub enum RuntimeError { 6 | HandleNotInitialized, 7 | HandleLockPoisoned, 8 | } 9 | 10 | impl Display for RuntimeError { 11 | fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { 12 | match self { 13 | Self::HandleNotInitialized => write!(formatter, "runtime handle not initialized"), 14 | Self::HandleLockPoisoned => write!(formatter, "runtime handle lock poisoned"), 15 | } 16 | } 17 | } 18 | 19 | impl Error for RuntimeError {} 20 | -------------------------------------------------------------------------------- /lib/format/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "format" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | ast = { path = "../ast" } 8 | itertools = "0.14" 9 | mfmt = "0.1.4" 10 | position = { path = "../position" } 11 | 12 | [dev-dependencies] 13 | indoc = "2" 14 | pretty_assertions = "1.4.1" 15 | -------------------------------------------------------------------------------- /lib/hir-mir/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hir_mir" 3 | version = "0.1.0" 4 | authors = ["Yota Toyama "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | fnv = "1" 9 | hir = { path = "../hir" } 10 | interface = { path = "../interface" } 11 | itertools = "0.14" 12 | mir = { path = "../mir" } 13 | position = { path = "../position" } 14 | test-info = { path = "../test-info" } 15 | 16 | [dev-dependencies] 17 | insta = "1" 18 | pretty_assertions = "1" 19 | -------------------------------------------------------------------------------- /lib/hir-mir/src/main_module_configuration.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | 3 | #[derive(Clone, Debug)] 4 | pub struct MainModuleConfiguration { 5 | pub source_main_function_name: String, 6 | pub object_main_function_name: String, 7 | pub context_type_name: String, 8 | pub contexts: BTreeMap, 9 | } 10 | 11 | #[derive(Clone, Debug)] 12 | pub struct ContextConfiguration { 13 | pub context_type_name: String, 14 | pub new_context_function_name: String, 15 | } 16 | -------------------------------------------------------------------------------- /lib/hir-mir/src/number_type_configuration.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | use std::sync::LazyLock; 3 | 4 | #[cfg(test)] 5 | pub static NUMBER_TYPE_CONFIGURATION: LazyLock = 6 | LazyLock::new(|| NumberTypeConfiguration { 7 | debug_function_name: "debugNumber".into(), 8 | }); 9 | 10 | #[derive(Clone, Debug, PartialEq, Eq)] 11 | pub struct NumberTypeConfiguration { 12 | pub debug_function_name: String, 13 | } 14 | -------------------------------------------------------------------------------- /lib/hir-mir/src/string_type_configuration.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | use std::sync::LazyLock; 3 | 4 | #[cfg(test)] 5 | pub static STRING_TYPE_CONFIGURATION: LazyLock = 6 | LazyLock::new(|| StringTypeConfiguration { 7 | equal_function_name: "_equalStrings".into(), 8 | }); 9 | 10 | #[derive(Clone, Debug)] 11 | pub struct StringTypeConfiguration { 12 | pub equal_function_name: String, 13 | } 14 | -------------------------------------------------------------------------------- /lib/hir-mir/src/test_module_configuration.rs: -------------------------------------------------------------------------------- 1 | pub struct TestModuleConfiguration { 2 | pub test_function_prefix: String, 3 | } 4 | -------------------------------------------------------------------------------- /lib/hir-mir/src/transformation.rs: -------------------------------------------------------------------------------- 1 | pub mod boolean_operation; 2 | mod collection_type; 3 | pub mod equal_operation; 4 | pub mod hash_calculation; 5 | pub mod if_list; 6 | pub mod if_map; 7 | pub mod list_literal; 8 | pub mod map_context; 9 | pub mod map_literal; 10 | pub mod not_equal_operation; 11 | pub mod record_equal_function; 12 | pub mod record_hash_function; 13 | mod record_type_information; 14 | -------------------------------------------------------------------------------- /lib/hir-mir/src/transformation/equal_operation.rs: -------------------------------------------------------------------------------- 1 | pub mod expression; 2 | pub mod function; 3 | pub mod module; 4 | mod operation; 5 | -------------------------------------------------------------------------------- /lib/hir-mir/src/transformation/equal_operation/expression.rs: -------------------------------------------------------------------------------- 1 | use super::operation; 2 | use crate::{context::Context, error::CompileError}; 3 | use hir::{analysis::AnalysisError, ir::*}; 4 | 5 | pub fn transform( 6 | context: &Context, 7 | operation: &EqualityOperation, 8 | ) -> Result { 9 | Ok(if operation.operator() == EqualityOperator::Equal { 10 | operation::transform( 11 | context, 12 | operation 13 | .type_() 14 | .ok_or_else(|| AnalysisError::TypeNotInferred(operation.position().clone()))?, 15 | operation.lhs(), 16 | operation.rhs(), 17 | operation.position(), 18 | )? 19 | } else { 20 | operation.clone().into() 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /lib/hir-mir/src/transformation/equal_operation/function.rs: -------------------------------------------------------------------------------- 1 | use crate::{context::Context, error::CompileError}; 2 | use fnv::FnvHashMap; 3 | use hir::{analysis::type_id_calculator, ir::*, types::Type}; 4 | 5 | pub fn transform(context: &Context, type_: &Type) -> Result { 6 | Ok(Variable::new( 7 | transform_name(type_, context.types())?, 8 | type_.position().clone(), 9 | ) 10 | .into()) 11 | } 12 | 13 | pub fn transform_name( 14 | type_: &Type, 15 | types: &FnvHashMap, 16 | ) -> Result { 17 | Ok(format!( 18 | "hir:equal:{}", 19 | type_id_calculator::calculate(&type_.clone(), types)? 20 | )) 21 | } 22 | -------------------------------------------------------------------------------- /lib/hir-mir/src/transformation/hash_calculation.rs: -------------------------------------------------------------------------------- 1 | pub mod expression; 2 | pub mod function; 3 | pub mod module; 4 | -------------------------------------------------------------------------------- /lib/hir-mir/src/transformation/hash_calculation/function.rs: -------------------------------------------------------------------------------- 1 | use crate::{context::Context, error::CompileError}; 2 | use fnv::FnvHashMap; 3 | use hir::{analysis::type_id_calculator, ir::*, types::Type}; 4 | 5 | pub fn transform(context: &Context, type_: &Type) -> Result { 6 | Ok(Variable::new( 7 | transform_name(type_, context.types())?, 8 | type_.position().clone(), 9 | ) 10 | .into()) 11 | } 12 | 13 | pub fn transform_name( 14 | type_: &Type, 15 | types: &FnvHashMap, 16 | ) -> Result { 17 | Ok(format!( 18 | "hir:hash:{}", 19 | type_id_calculator::calculate(&type_.clone(), types)? 20 | )) 21 | } 22 | -------------------------------------------------------------------------------- /lib/hir-mir/src/transformation/map_context.rs: -------------------------------------------------------------------------------- 1 | pub mod expression; 2 | pub mod module; 3 | 4 | use crate::CompileError; 5 | use fnv::FnvHashMap; 6 | use hir::{ 7 | analysis::type_id_calculator, 8 | types::{self, Type}, 9 | }; 10 | 11 | fn context_function_name( 12 | type_: &types::Map, 13 | types: &FnvHashMap, 14 | ) -> Result { 15 | Ok(format!( 16 | "hir:map:context:{}", 17 | type_id_calculator::calculate(&type_.clone().into(), types)? 18 | )) 19 | } 20 | -------------------------------------------------------------------------------- /lib/hir-mir/src/transformation/not_equal_operation.rs: -------------------------------------------------------------------------------- 1 | use hir::ir::*; 2 | 3 | pub fn transform(operation: &EqualityOperation) -> Expression { 4 | if operation.operator() == EqualityOperator::NotEqual { 5 | let position = operation.position(); 6 | 7 | If::new( 8 | EqualityOperation::new( 9 | operation.type_().cloned(), 10 | EqualityOperator::Equal, 11 | operation.lhs().clone(), 12 | operation.rhs().clone(), 13 | position.clone(), 14 | ), 15 | Boolean::new(false, position.clone()), 16 | Boolean::new(true, position.clone()), 17 | position.clone(), 18 | ) 19 | .into() 20 | } else { 21 | operation.clone().into() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/hir-mir/src/transformation/record_type_information.rs: -------------------------------------------------------------------------------- 1 | use hir::types; 2 | 3 | pub fn compile_equal_function_name(record_type: &types::Record) -> String { 4 | format!("{}.$equal", record_type.name()) 5 | } 6 | 7 | pub fn compile_hash_function_name(record_type: &types::Record) -> String { 8 | format!("{}.$hash", record_type.name()) 9 | } 10 | -------------------------------------------------------------------------------- /lib/hir/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hir" 3 | version = "0.1.0" 4 | authors = ["Yota Toyama "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | fnv = "1" 9 | petgraph = "0.7" 10 | plist = { git = "https://github.com/raviqqe/plist-rs", branch = "main" } 11 | position = { path = "../position" } 12 | serde = { version = "1", features = ["derive", "rc"] } 13 | 14 | [dev-dependencies] 15 | pretty_assertions = "1" 16 | -------------------------------------------------------------------------------- /lib/hir/src/analysis/context.rs: -------------------------------------------------------------------------------- 1 | use crate::types::{self, Type}; 2 | use fnv::FnvHashMap; 3 | 4 | #[derive(Debug)] 5 | pub struct AnalysisContext { 6 | types: FnvHashMap, 7 | records: FnvHashMap>, 8 | } 9 | 10 | impl AnalysisContext { 11 | pub fn new( 12 | types: FnvHashMap, 13 | records: FnvHashMap>, 14 | ) -> Self { 15 | Self { types, records } 16 | } 17 | 18 | pub fn types(&self) -> &FnvHashMap { 19 | &self.types 20 | } 21 | 22 | pub fn records(&self) -> &FnvHashMap> { 23 | &self.records 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/hir/src/analysis/type_resolver.rs: -------------------------------------------------------------------------------- 1 | use super::AnalysisError; 2 | use crate::types::*; 3 | use fnv::FnvHashMap; 4 | 5 | pub fn resolve( 6 | reference: &Reference, 7 | types: &FnvHashMap, 8 | ) -> Result { 9 | Ok(types 10 | .get(reference.name()) 11 | .ok_or_else(|| AnalysisError::TypeNotFound(reference.clone()))? 12 | .clone() 13 | .set_position(reference.position().clone())) 14 | } 15 | -------------------------------------------------------------------------------- /lib/hir/src/analysis/union_type_creator.rs: -------------------------------------------------------------------------------- 1 | use crate::types::{self, Type}; 2 | use position::Position; 3 | 4 | pub fn create(types: &[Type], position: &Position) -> Option { 5 | types 6 | .iter() 7 | .cloned() 8 | .reduce(|left, right| types::Union::new(left, right, position.clone()).into()) 9 | } 10 | -------------------------------------------------------------------------------- /lib/hir/src/ir/argument.rs: -------------------------------------------------------------------------------- 1 | use crate::types::Type; 2 | 3 | #[derive(Clone, Debug, PartialEq, Eq)] 4 | pub struct Argument { 5 | name: String, 6 | type_: Type, 7 | } 8 | 9 | impl Argument { 10 | pub fn new(name: impl Into, type_: impl Into) -> Self { 11 | Self { 12 | name: name.into(), 13 | type_: type_.into(), 14 | } 15 | } 16 | 17 | pub fn name(&self) -> &str { 18 | &self.name 19 | } 20 | 21 | pub fn type_(&self) -> &Type { 22 | &self.type_ 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/hir/src/ir/block.rs: -------------------------------------------------------------------------------- 1 | use position::Position; use super::{Expression, Statement}; 2 | use std::rc::Rc; 3 | 4 | #[derive(Clone, Debug, PartialEq)] 5 | pub struct Block { 6 | statements: Vec, 7 | expression: Rc, 8 | } 9 | 10 | impl Block { 11 | pub fn new(statements: Vec, expression: impl Into) -> Self { 12 | Self { 13 | statements, 14 | expression: expression.into().into(), 15 | } 16 | } 17 | 18 | pub fn statements(&self) -> &[Statement] { 19 | &self.statements 20 | } 21 | 22 | pub fn expression(&self) -> &Expression { 23 | &self.expression 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/hir/src/ir/boolean.rs: -------------------------------------------------------------------------------- 1 | use position::Position; 2 | 3 | #[derive(Clone, Debug, PartialEq, Eq)] 4 | pub struct Boolean { 5 | value: bool, 6 | position: Position, 7 | } 8 | 9 | impl Boolean { 10 | pub fn new(value: bool, position: Position) -> Self { 11 | Self { value, position } 12 | } 13 | 14 | pub fn value(&self) -> bool { 15 | self.value 16 | } 17 | 18 | pub fn position(&self) -> &Position { 19 | &self.position 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/hir/src/ir/calling_convention.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 2 | pub enum CallingConvention { 3 | C, 4 | Native, 5 | } 6 | -------------------------------------------------------------------------------- /lib/hir/src/ir/foreign_definition_configuration.rs: -------------------------------------------------------------------------------- 1 | use super::calling_convention::CallingConvention; 2 | 3 | #[derive(Clone, Debug, PartialEq, Eq)] 4 | pub struct ForeignDefinitionConfiguration { 5 | calling_convention: CallingConvention, 6 | } 7 | 8 | impl ForeignDefinitionConfiguration { 9 | pub fn new(calling_convention: CallingConvention) -> Self { 10 | Self { calling_convention } 11 | } 12 | 13 | pub fn calling_convention(&self) -> CallingConvention { 14 | self.calling_convention 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/hir/src/ir/if_type_branch.rs: -------------------------------------------------------------------------------- 1 | use super::Expression; 2 | use crate::types::Type; 3 | 4 | #[derive(Clone, Debug, PartialEq)] 5 | pub struct IfTypeBranch { 6 | type_: Type, 7 | expression: Expression, 8 | } 9 | 10 | impl IfTypeBranch { 11 | pub fn new(type_: impl Into, expression: impl Into) -> Self { 12 | Self { 13 | type_: type_.into(), 14 | expression: expression.into(), 15 | } 16 | } 17 | 18 | pub fn type_(&self) -> &Type { 19 | &self.type_ 20 | } 21 | 22 | pub fn expression(&self) -> &Expression { 23 | &self.expression 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/hir/src/ir/list.rs: -------------------------------------------------------------------------------- 1 | use super::list_element::ListElement; 2 | use crate::types::Type; 3 | use position::Position; 4 | 5 | #[derive(Clone, Debug, PartialEq)] 6 | pub struct List { 7 | type_: Type, 8 | elements: Vec, 9 | position: Position, 10 | } 11 | 12 | impl List { 13 | pub fn new(type_: impl Into, elements: Vec, position: Position) -> Self { 14 | Self { 15 | type_: type_.into(), 16 | elements, 17 | position, 18 | } 19 | } 20 | 21 | pub fn type_(&self) -> &Type { 22 | &self.type_ 23 | } 24 | 25 | pub fn elements(&self) -> &[ListElement] { 26 | &self.elements 27 | } 28 | 29 | pub fn position(&self) -> &Position { 30 | &self.position 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/hir/src/ir/list_element.rs: -------------------------------------------------------------------------------- 1 | use super::expression::Expression; 2 | 3 | #[derive(Clone, Debug, PartialEq)] 4 | pub enum ListElement { 5 | Multiple(Expression), 6 | Single(Expression), 7 | } 8 | -------------------------------------------------------------------------------- /lib/hir/src/ir/map_element.rs: -------------------------------------------------------------------------------- 1 | use super::{expression::Expression, map_entry::MapEntry}; 2 | 3 | #[derive(Clone, Debug, PartialEq)] 4 | pub enum MapElement { 5 | Multiple(Expression), 6 | Single(MapEntry), 7 | } 8 | 9 | impl From for MapElement { 10 | fn from(entry: MapEntry) -> Self { 11 | Self::Single(entry) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/hir/src/ir/none.rs: -------------------------------------------------------------------------------- 1 | use position::Position; 2 | 3 | #[derive(Clone, Debug, PartialEq, Eq)] 4 | pub struct None { 5 | position: Position, 6 | } 7 | 8 | impl None { 9 | pub fn new(position: Position) -> Self { 10 | Self { position } 11 | } 12 | 13 | pub fn position(&self) -> &Position { 14 | &self.position 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/hir/src/ir/not_operation.rs: -------------------------------------------------------------------------------- 1 | use super::expression::Expression; 2 | use position::Position; 3 | use std::rc::Rc; 4 | 5 | #[derive(Clone, Debug, PartialEq)] 6 | pub struct NotOperation { 7 | expression: Rc, 8 | position: Position, 9 | } 10 | 11 | impl NotOperation { 12 | pub fn new(expression: impl Into, position: Position) -> Self { 13 | Self { 14 | expression: expression.into().into(), 15 | position, 16 | } 17 | } 18 | 19 | pub fn expression(&self) -> &Expression { 20 | &self.expression 21 | } 22 | 23 | pub fn position(&self) -> &Position { 24 | &self.position 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/hir/src/ir/number.rs: -------------------------------------------------------------------------------- 1 | use position::Position; 2 | 3 | #[derive(Clone, Debug, PartialEq)] 4 | pub struct Number { 5 | value: f64, 6 | position: Position, 7 | } 8 | 9 | impl Number { 10 | pub fn new(value: f64, position: Position) -> Self { 11 | Self { value, position } 12 | } 13 | 14 | pub fn value(&self) -> f64 { 15 | self.value 16 | } 17 | 18 | pub fn position(&self) -> &Position { 19 | &self.position 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/hir/src/ir/string.rs: -------------------------------------------------------------------------------- 1 | use position::Position; 2 | 3 | #[derive(Clone, Debug, PartialEq, Eq)] 4 | pub struct ByteString { 5 | value: Vec, 6 | position: Position, 7 | } 8 | 9 | impl ByteString { 10 | pub fn new(value: impl Into>, position: Position) -> Self { 11 | Self { 12 | value: value.into(), 13 | position, 14 | } 15 | } 16 | 17 | pub fn value(&self) -> &[u8] { 18 | &self.value 19 | } 20 | 21 | pub fn position(&self) -> &Position { 22 | &self.position 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/hir/src/ir/variable.rs: -------------------------------------------------------------------------------- 1 | use position::Position; 2 | 3 | #[derive(Clone, Debug, PartialEq, Eq)] 4 | pub struct Variable { 5 | name: String, 6 | position: Position, 7 | } 8 | 9 | impl Variable { 10 | pub fn new(name: impl Into, position: Position) -> Self { 11 | Self { 12 | name: name.into(), 13 | position, 14 | } 15 | } 16 | 17 | pub fn name(&self) -> &str { 18 | &self.name 19 | } 20 | 21 | pub fn position(&self) -> &Position { 22 | &self.position 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/hir/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod analysis; 2 | pub mod ir; 3 | pub mod test; 4 | pub mod types; 5 | -------------------------------------------------------------------------------- /lib/hir/src/test.rs: -------------------------------------------------------------------------------- 1 | mod ir; 2 | mod types; 3 | 4 | pub use ir::*; 5 | pub use types::*; 6 | -------------------------------------------------------------------------------- /lib/hir/src/test/ir.rs: -------------------------------------------------------------------------------- 1 | mod foreign_declaration; 2 | mod function_definition; 3 | mod module; 4 | mod type_alias; 5 | mod type_definition; 6 | 7 | pub use foreign_declaration::*; 8 | pub use function_definition::*; 9 | pub use module::*; 10 | pub use type_alias::*; 11 | pub use type_definition::*; 12 | -------------------------------------------------------------------------------- /lib/hir/src/test/ir/foreign_declaration.rs: -------------------------------------------------------------------------------- 1 | use crate::{ir::*, types::Type}; 2 | use position::{test::PositionFake, Position}; 3 | 4 | pub trait ForeignDeclarationFake { 5 | fn fake(name: impl Into, type_: impl Into) -> Self; 6 | } 7 | 8 | impl ForeignDeclarationFake for ForeignDeclaration { 9 | fn fake(name: impl Into, type_: impl Into) -> Self { 10 | Self::new(name, "", CallingConvention::C, type_, Position::fake()) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/hir/src/test/ir/function_definition.rs: -------------------------------------------------------------------------------- 1 | use crate::ir::*; 2 | use position::{test::PositionFake, Position}; 3 | 4 | pub trait FunctionDefinitionFake { 5 | fn fake(name: impl Into, lambda: Lambda, public: bool) -> Self; 6 | } 7 | 8 | impl FunctionDefinitionFake for FunctionDefinition { 9 | fn fake(name: impl Into, lambda: Lambda, public: bool) -> Self { 10 | Self::new(name, "", lambda, None, public, Position::fake()) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/hir/src/test/ir/type_alias.rs: -------------------------------------------------------------------------------- 1 | use crate::{ir::*, types::Type}; 2 | use position::{test::PositionFake, Position}; 3 | 4 | pub trait TypeAliasFake { 5 | fn fake(name: impl Into, type_: impl Into, public: bool, external: bool) -> Self; 6 | } 7 | 8 | impl TypeAliasFake for TypeAlias { 9 | fn fake(name: impl Into, type_: impl Into, public: bool, external: bool) -> Self { 10 | Self::new(name, "", type_, public, external, Position::fake()) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/hir/src/test/types.rs: -------------------------------------------------------------------------------- 1 | mod record; 2 | 3 | pub use record::*; 4 | -------------------------------------------------------------------------------- /lib/hir/src/test/types/record.rs: -------------------------------------------------------------------------------- 1 | use crate::types; 2 | use position::{test::PositionFake, Position}; 3 | 4 | pub trait RecordFake { 5 | fn fake(name: impl Into) -> Self; 6 | } 7 | 8 | impl RecordFake for types::Record { 9 | fn fake(name: impl Into) -> Self { 10 | let name = name.into(); 11 | 12 | Self::new(name.clone(), name, Position::fake()) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/hir/src/types.rs: -------------------------------------------------------------------------------- 1 | mod any; 2 | mod boolean; 3 | mod byte_string; 4 | mod error; 5 | mod function; 6 | mod list; 7 | mod map; 8 | mod none; 9 | mod number; 10 | mod record; 11 | mod record_field; 12 | mod reference; 13 | mod type_; 14 | mod union; 15 | 16 | pub use any::*; 17 | pub use boolean::*; 18 | pub use byte_string::*; 19 | pub use error::*; 20 | pub use function::*; 21 | pub use list::*; 22 | pub use map::*; 23 | pub use none::*; 24 | pub use number::*; 25 | pub use record::*; 26 | pub use record_field::*; 27 | pub use reference::*; 28 | pub use type_::*; 29 | pub use union::*; 30 | -------------------------------------------------------------------------------- /lib/hir/src/types/any.rs: -------------------------------------------------------------------------------- 1 | use position::Position; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | #[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] 5 | pub struct Any { 6 | position: Position, 7 | } 8 | 9 | impl Any { 10 | pub fn new(position: Position) -> Self { 11 | Self { position } 12 | } 13 | 14 | pub fn position(&self) -> &Position { 15 | &self.position 16 | } 17 | 18 | pub fn set_position(mut self, position: Position) -> Self { 19 | self.position = position; 20 | self 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/hir/src/types/boolean.rs: -------------------------------------------------------------------------------- 1 | use position::Position; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | #[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] 5 | pub struct Boolean { 6 | position: Position, 7 | } 8 | 9 | impl Boolean { 10 | pub fn new(position: Position) -> Self { 11 | Self { position } 12 | } 13 | 14 | pub fn position(&self) -> &Position { 15 | &self.position 16 | } 17 | 18 | pub fn set_position(mut self, position: Position) -> Self { 19 | self.position = position; 20 | self 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/hir/src/types/byte_string.rs: -------------------------------------------------------------------------------- 1 | use position::Position; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | #[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] 5 | pub struct ByteString { 6 | position: Position, 7 | } 8 | 9 | impl ByteString { 10 | pub fn new(position: Position) -> Self { 11 | Self { position } 12 | } 13 | 14 | pub fn position(&self) -> &Position { 15 | &self.position 16 | } 17 | 18 | pub fn set_position(mut self, position: Position) -> Self { 19 | self.position = position; 20 | self 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/hir/src/types/error.rs: -------------------------------------------------------------------------------- 1 | use position::Position; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | #[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] 5 | pub struct Error { 6 | position: Position, 7 | } 8 | 9 | impl Error { 10 | pub fn new(position: Position) -> Self { 11 | Self { position } 12 | } 13 | 14 | pub fn position(&self) -> &Position { 15 | &self.position 16 | } 17 | 18 | pub fn set_position(mut self, position: Position) -> Self { 19 | self.position = position; 20 | self 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/hir/src/types/none.rs: -------------------------------------------------------------------------------- 1 | use position::Position; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | #[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] 5 | pub struct None { 6 | position: Position, 7 | } 8 | 9 | impl None { 10 | pub fn new(position: Position) -> Self { 11 | Self { position } 12 | } 13 | 14 | pub fn position(&self) -> &Position { 15 | &self.position 16 | } 17 | 18 | pub fn set_position(mut self, position: Position) -> Self { 19 | self.position = position; 20 | self 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/hir/src/types/number.rs: -------------------------------------------------------------------------------- 1 | use position::Position; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | #[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] 5 | pub struct Number { 6 | position: Position, 7 | } 8 | 9 | impl Number { 10 | pub fn new(position: Position) -> Self { 11 | Self { position } 12 | } 13 | 14 | pub fn position(&self) -> &Position { 15 | &self.position 16 | } 17 | 18 | pub fn set_position(mut self, position: Position) -> Self { 19 | self.position = position; 20 | self 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/hir/src/types/record_field.rs: -------------------------------------------------------------------------------- 1 | use super::Type; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | #[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] 5 | pub struct RecordField { 6 | name: String, 7 | type_: Type, 8 | } 9 | 10 | impl RecordField { 11 | pub fn new(name: impl Into, type_: impl Into) -> Self { 12 | Self { 13 | name: name.into(), 14 | type_: type_.into(), 15 | } 16 | } 17 | 18 | pub fn name(&self) -> &str { 19 | &self.name 20 | } 21 | 22 | pub fn type_(&self) -> &Type { 23 | &self.type_ 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/infra/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "infra" 3 | version = "0.1.0" 4 | authors = ["Yota Toyama "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | app = { path = "../app" } 9 | glob = "0.3" 10 | regex = "1" 11 | serde = { version = "1", features = ["derive", "rc"] } 12 | serde_json = "1" 13 | termcolor = "1" 14 | test-info = { path = "../test-info" } 15 | url = "2" 16 | which = "7" 17 | -------------------------------------------------------------------------------- /lib/infra/src/command_finder.rs: -------------------------------------------------------------------------------- 1 | use crate::InfrastructureError; 2 | use std::{error::Error, path::PathBuf}; 3 | 4 | pub fn find(command: &str) -> Result> { 5 | Ok(which::which(command).map_err(|_| InfrastructureError::CommandNotFound(command.into()))?) 6 | } 7 | -------------------------------------------------------------------------------- /lib/infra/src/environment_variable_reader.rs: -------------------------------------------------------------------------------- 1 | use crate::InfrastructureError; 2 | use std::env; 3 | 4 | pub fn read(variable: &str) -> Result { 5 | env::var(variable) 6 | .map_err(|_| InfrastructureError::EnvironmentVariableNotFound(variable.into())) 7 | } 8 | -------------------------------------------------------------------------------- /lib/infra/src/llvm_command_finder.rs: -------------------------------------------------------------------------------- 1 | use crate::command_finder; 2 | use std::{error::Error, path::PathBuf}; 3 | 4 | const LLVM_VERSION: usize = 16; 5 | 6 | pub fn find(command: &str) -> Result> { 7 | if let Ok(path) = command_finder::find(&format!("{command}-{LLVM_VERSION}")) { 8 | return Ok(path); 9 | } 10 | 11 | command_finder::find(command) 12 | } 13 | -------------------------------------------------------------------------------- /lib/infra/src/package_script_finder.rs: -------------------------------------------------------------------------------- 1 | use crate::InfrastructureError; 2 | use std::{ 3 | error::Error, 4 | path::{Path, PathBuf}, 5 | }; 6 | 7 | pub fn find( 8 | package_directory: &Path, 9 | script_basename: &str, 10 | ) -> Result, Box> { 11 | let ffi_build_scripts = 12 | glob::glob(&(package_directory.join(script_basename).to_string_lossy() + ".*"))? 13 | .collect::, _>>()?; 14 | 15 | Ok(match ffi_build_scripts.as_slice() { 16 | [] => None, 17 | [script] => Some(script.into()), 18 | _ => { 19 | return Err( 20 | InfrastructureError::MultipleFfiBuildScripts(package_directory.into()).into(), 21 | ) 22 | } 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /lib/interface/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "interface" 3 | version = "0.1.0" 4 | authors = ["Yota Toyama "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | hir = { path = "../hir" } 9 | position = { path = "../position" } 10 | serde = { version = "1", features = ["derive", "rc"] } 11 | -------------------------------------------------------------------------------- /lib/interface/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod function_declaration; 2 | mod module; 3 | mod type_alias; 4 | mod type_definition; 5 | 6 | pub use function_declaration::*; 7 | pub use module::*; 8 | pub use type_alias::*; 9 | pub use type_definition::*; 10 | -------------------------------------------------------------------------------- /lib/mir-fmm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mir-fmm" 3 | description = "MIR to F-- compiler" 4 | version = "0.1.0" 5 | license = "MIT" 6 | authors = ["Yota Toyama "] 7 | edition = "2021" 8 | 9 | [dependencies] 10 | fmm = { git = "https://github.com/raviqqe/fmm", branch = "main" } 11 | fnv = "1" 12 | mir = { path = "../mir" } 13 | plist = { git = "https://github.com/raviqqe/plist-rs", branch = "main" } 14 | 15 | [dev-dependencies] 16 | insta = "1" 17 | tempfile = "3" 18 | fmm-c = { git = "https://github.com/raviqqe/fmm", branch = "main" } 19 | fmm-llvm = { git = "https://github.com/raviqqe/fmm", branch = "main" } 20 | -------------------------------------------------------------------------------- /lib/mir-fmm/src/configuration.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | use std::sync::LazyLock; 3 | 4 | #[cfg(test)] 5 | pub static CONFIGURATION: LazyLock = LazyLock::new(|| Configuration { 6 | yield_function_name: "mir_yield".into(), 7 | }); 8 | 9 | #[derive(Clone, Debug)] 10 | pub struct Configuration { 11 | pub yield_function_name: String, 12 | } 13 | -------------------------------------------------------------------------------- /lib/mir-fmm/src/function_declaration.rs: -------------------------------------------------------------------------------- 1 | use crate::{context::Context, reference_count, type_}; 2 | 3 | pub fn compile(context: &Context, declaration: &mir::ir::FunctionDeclaration) { 4 | context.module_builder().declare_variable( 5 | declaration.name(), 6 | reference_count::block::compile_type(type_::compile_unsized_closure( 7 | context, 8 | declaration.type_(), 9 | )), 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /lib/mir-fmm/src/pointer.rs: -------------------------------------------------------------------------------- 1 | pub fn equal( 2 | lhs: impl Into, 3 | rhs: impl Into, 4 | ) -> Result { 5 | Ok(fmm::build::comparison_operation( 6 | fmm::ir::ComparisonOperator::Equal, 7 | fmm::build::bit_cast(fmm::types::Primitive::PointerInteger, lhs), 8 | fmm::build::bit_cast(fmm::types::Primitive::PointerInteger, rhs), 9 | )? 10 | .into()) 11 | } 12 | -------------------------------------------------------------------------------- /lib/mir-fmm/src/reference_count.rs: -------------------------------------------------------------------------------- 1 | pub mod block; 2 | mod count; 3 | mod expression; 4 | pub mod function; 5 | pub mod heap; 6 | pub mod pointer; 7 | pub mod record; 8 | pub mod string; 9 | pub mod variant; 10 | 11 | pub use expression::*; 12 | use std::sync::LazyLock; 13 | 14 | pub(super) static REFERENCE_COUNT_FUNCTION_DEFINITION_OPTIONS: LazyLock< 15 | fmm::ir::FunctionDefinitionOptions, 16 | > = LazyLock::new(|| { 17 | fmm::ir::FunctionDefinitionOptions::new() 18 | .set_address_named(false) 19 | .set_calling_convention(fmm::types::CallingConvention::Target) 20 | .set_linkage(fmm::ir::Linkage::Weak) 21 | }); 22 | -------------------------------------------------------------------------------- /lib/mir-fmm/src/yield_.rs: -------------------------------------------------------------------------------- 1 | use crate::context::Context; 2 | use std::cell::LazyCell; 3 | 4 | thread_local! { 5 | static YIELD_FUNCTION_TYPE: LazyCell = LazyCell::new(|| { 6 | fmm::types::Function::new( 7 | vec![], 8 | fmm::types::void_type(), 9 | fmm::types::CallingConvention::Source, 10 | ) 11 | }); 12 | } 13 | 14 | pub fn yield_function_type() -> fmm::types::Function { 15 | YIELD_FUNCTION_TYPE.with(|function| (**function).clone()) 16 | } 17 | 18 | pub fn compile_function_declaration(context: &Context) { 19 | context.module_builder().declare_function( 20 | &context.configuration().yield_function_name, 21 | yield_function_type(), 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /lib/mir/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mir" 3 | description = "Mid-level Intermediate Representation" 4 | version = "0.1.0" 5 | license = "MIT" 6 | authors = ["Yota Toyama "] 7 | edition = "2021" 8 | 9 | [dependencies] 10 | fnv = "1" 11 | hamt = { git = "https://github.com/raviqqe/hamt-rs", branch = "main" } 12 | plist = { git = "https://github.com/raviqqe/plist-rs", branch = "main" } 13 | 14 | [dev-dependencies] 15 | pretty_assertions = "1.4" 16 | -------------------------------------------------------------------------------- /lib/mir/src/analysis.rs: -------------------------------------------------------------------------------- 1 | pub mod alpha_conversion; 2 | pub mod environment_inference; 3 | mod expression_conversion; 4 | pub mod free_variable; 5 | pub mod lambda_lifting; 6 | pub mod normalization; 7 | pub mod optimization; 8 | pub mod reference_count; 9 | pub mod type_check; 10 | pub mod type_id; 11 | pub mod variant_type_collection; 12 | -------------------------------------------------------------------------------- /lib/mir/src/analysis/optimization.rs: -------------------------------------------------------------------------------- 1 | pub mod string_concatenation; 2 | -------------------------------------------------------------------------------- /lib/mir/src/analysis/reference_count.rs: -------------------------------------------------------------------------------- 1 | mod error; 2 | mod transformation; 3 | mod validation; 4 | 5 | use crate::ir::Module; 6 | pub use error::ReferenceCountError; 7 | use validation::validate; 8 | 9 | pub fn transform(module: &Module) -> Result { 10 | let module = transformation::transform(module)?; 11 | 12 | validate(&module)?; 13 | 14 | Ok(module) 15 | } 16 | -------------------------------------------------------------------------------- /lib/mir/src/analysis/reference_count/error.rs: -------------------------------------------------------------------------------- 1 | use crate::ir::*; 2 | use fnv::FnvHashMap; 3 | use std::{error::Error, fmt::Display}; 4 | 5 | #[derive(Clone, Debug, PartialEq)] 6 | pub enum ReferenceCountError { 7 | ExpressionNotSupported(Expression), 8 | InvalidLocalVariable(String, isize), 9 | InvalidLocalVariables(FnvHashMap), 10 | UnmatchedVariables(FnvHashMap, FnvHashMap), 11 | } 12 | 13 | impl Display for ReferenceCountError { 14 | fn fmt(&self, formatter: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { 15 | write!(formatter, "{self:#?}") 16 | } 17 | } 18 | 19 | impl Error for ReferenceCountError {} 20 | -------------------------------------------------------------------------------- /lib/mir/src/analysis/type_check/context.rs: -------------------------------------------------------------------------------- 1 | use crate::ir::TypeInformation; 2 | 3 | pub struct Context<'a> { 4 | type_information: &'a TypeInformation, 5 | } 6 | 7 | impl<'a> Context<'a> { 8 | pub fn new(type_information: &'a TypeInformation) -> Self { 9 | Self { type_information } 10 | } 11 | 12 | pub fn type_information(&self) -> &TypeInformation { 13 | self.type_information 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/mir/src/ir/argument.rs: -------------------------------------------------------------------------------- 1 | use crate::types::Type; 2 | 3 | #[derive(Clone, Debug, Eq, PartialEq)] 4 | pub struct Argument { 5 | name: String, 6 | type_: Type, 7 | } 8 | 9 | impl Argument { 10 | pub fn new(name: impl Into, type_: impl Into) -> Self { 11 | Self { 12 | name: name.into(), 13 | type_: type_.into(), 14 | } 15 | } 16 | 17 | pub fn name(&self) -> &str { 18 | &self.name 19 | } 20 | 21 | pub fn type_(&self) -> &Type { 22 | &self.type_ 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/mir/src/ir/arithmetic_operator.rs: -------------------------------------------------------------------------------- 1 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 2 | pub enum ArithmeticOperator { 3 | Add, 4 | Subtract, 5 | Multiply, 6 | Divide, 7 | } 8 | -------------------------------------------------------------------------------- /lib/mir/src/ir/byte_string.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | #[derive(Clone, Debug, Eq, PartialEq)] 4 | pub struct ByteString { 5 | value: Rc<[u8]>, 6 | } 7 | 8 | impl ByteString { 9 | pub fn new(value: impl Into>) -> Self { 10 | Self { 11 | value: value.into().into(), 12 | } 13 | } 14 | 15 | pub fn value(&self) -> &[u8] { 16 | &self.value 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/mir/src/ir/calling_convention.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 2 | pub enum CallingConvention { 3 | Source, 4 | Target, 5 | } 6 | -------------------------------------------------------------------------------- /lib/mir/src/ir/comparison_operator.rs: -------------------------------------------------------------------------------- 1 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 2 | pub enum ComparisonOperator { 3 | Equal, 4 | NotEqual, 5 | LessThan, 6 | GreaterThan, 7 | LessThanOrEqual, 8 | GreaterThanOrEqual, 9 | } 10 | -------------------------------------------------------------------------------- /lib/mir/src/ir/default_alternative.rs: -------------------------------------------------------------------------------- 1 | use super::expression::Expression; 2 | use std::rc::Rc; 3 | 4 | #[derive(Clone, Debug, PartialEq)] 5 | pub struct DefaultAlternative { 6 | name: String, 7 | expression: Rc, 8 | } 9 | 10 | impl DefaultAlternative { 11 | pub fn new(name: impl Into, expression: impl Into) -> Self { 12 | Self { 13 | name: name.into(), 14 | expression: expression.into().into(), 15 | } 16 | } 17 | 18 | pub fn name(&self) -> &str { 19 | &self.name 20 | } 21 | 22 | pub fn expression(&self) -> &Expression { 23 | &self.expression 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/mir/src/ir/function_declaration.rs: -------------------------------------------------------------------------------- 1 | use crate::types; 2 | 3 | #[derive(Clone, Debug, Eq, PartialEq)] 4 | pub struct FunctionDeclaration { 5 | name: String, 6 | type_: types::Function, 7 | } 8 | 9 | impl FunctionDeclaration { 10 | pub fn new(name: impl Into, type_: types::Function) -> Self { 11 | Self { 12 | name: name.into(), 13 | type_, 14 | } 15 | } 16 | 17 | pub fn name(&self) -> &str { 18 | &self.name 19 | } 20 | 21 | pub fn type_(&self) -> &types::Function { 22 | &self.type_ 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/mir/src/ir/global_function_definition.rs: -------------------------------------------------------------------------------- 1 | use super::FunctionDefinition; 2 | 3 | #[derive(Clone, Debug, PartialEq)] 4 | pub struct GlobalFunctionDefinition { 5 | definition: FunctionDefinition, 6 | public: bool, 7 | } 8 | 9 | impl GlobalFunctionDefinition { 10 | pub fn new(definition: FunctionDefinition, public: bool) -> Self { 11 | Self { definition, public } 12 | } 13 | 14 | pub fn definition(&self) -> &FunctionDefinition { 15 | &self.definition 16 | } 17 | 18 | pub fn is_public(&self) -> bool { 19 | self.public 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/mir/src/ir/record.rs: -------------------------------------------------------------------------------- 1 | use super::expression::Expression; 2 | use crate::types; 3 | use std::rc::Rc; 4 | 5 | #[derive(Clone, Debug, PartialEq)] 6 | pub struct Record(Rc); 7 | 8 | #[derive(Debug, PartialEq)] 9 | struct RecordInner { 10 | type_: types::Record, 11 | fields: Vec, 12 | } 13 | 14 | impl Record { 15 | pub fn new(type_: types::Record, fields: Vec) -> Self { 16 | Self(RecordInner { type_, fields }.into()) 17 | } 18 | 19 | pub fn type_(&self) -> &types::Record { 20 | &self.0.type_ 21 | } 22 | 23 | pub fn fields(&self) -> &[Expression] { 24 | &self.0.fields 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/mir/src/ir/record_update_field.rs: -------------------------------------------------------------------------------- 1 | use super::expression::Expression; 2 | 3 | #[derive(Clone, Debug, PartialEq)] 4 | pub struct RecordUpdateField { 5 | index: usize, 6 | expression: Expression, 7 | } 8 | 9 | impl RecordUpdateField { 10 | pub fn new(index: usize, record: impl Into) -> Self { 11 | Self { 12 | index, 13 | expression: record.into(), 14 | } 15 | } 16 | 17 | pub fn index(&self) -> usize { 18 | self.index 19 | } 20 | 21 | pub fn expression(&self) -> &Expression { 22 | &self.expression 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/mir/src/ir/string_concatenation.rs: -------------------------------------------------------------------------------- 1 | use super::expression::Expression; 2 | use std::rc::Rc; 3 | 4 | #[derive(Clone, Debug, PartialEq)] 5 | pub struct StringConcatenation { 6 | operands: Rc<[Expression]>, 7 | } 8 | 9 | impl StringConcatenation { 10 | pub fn new(operands: Vec) -> Self { 11 | Self { 12 | operands: operands.into(), 13 | } 14 | } 15 | 16 | pub fn operands(&self) -> &[Expression] { 17 | &self.operands 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/mir/src/ir/type_definition.rs: -------------------------------------------------------------------------------- 1 | use crate::types; 2 | 3 | #[derive(Clone, Debug, Eq, PartialEq)] 4 | pub struct TypeDefinition { 5 | name: String, 6 | type_: types::RecordBody, 7 | } 8 | 9 | impl TypeDefinition { 10 | pub fn new(name: impl Into, type_: impl Into) -> Self { 11 | Self { 12 | name: name.into(), 13 | type_: type_.into(), 14 | } 15 | } 16 | 17 | pub fn name(&self) -> &str { 18 | &self.name 19 | } 20 | 21 | pub fn type_(&self) -> &types::RecordBody { 22 | &self.type_ 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/mir/src/ir/type_information.rs: -------------------------------------------------------------------------------- 1 | use crate::types::Type; 2 | use fnv::FnvHashMap; 3 | 4 | #[derive(Clone, Debug, Default, PartialEq, Eq)] 5 | pub struct TypeInformation { 6 | information: FnvHashMap, 7 | fallback: String, 8 | } 9 | 10 | impl TypeInformation { 11 | pub fn new(information: FnvHashMap, fallback: String) -> Self { 12 | Self { 13 | information, 14 | fallback, 15 | } 16 | } 17 | 18 | pub fn information(&self) -> &FnvHashMap { 19 | &self.information 20 | } 21 | 22 | pub fn fallback(&self) -> &str { 23 | &self.fallback 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/mir/src/ir/type_information_function.rs: -------------------------------------------------------------------------------- 1 | use super::expression::Expression; 2 | use std::rc::Rc; 3 | 4 | #[derive(Clone, Debug, PartialEq)] 5 | pub struct TypeInformationFunction { 6 | variant: Rc, 7 | } 8 | 9 | impl TypeInformationFunction { 10 | pub fn new(variant: impl Into) -> Self { 11 | Self { 12 | variant: variant.into().into(), 13 | } 14 | } 15 | 16 | pub fn variant(&self) -> &Expression { 17 | &self.variant 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/mir/src/ir/variable.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | #[derive(Clone, Debug, Eq, PartialEq)] 4 | pub struct Variable { 5 | name: Rc, 6 | } 7 | 8 | impl Variable { 9 | pub fn new(name: impl Into) -> Self { 10 | Self { 11 | name: name.into().into(), 12 | } 13 | } 14 | 15 | pub fn name(&self) -> &str { 16 | &self.name 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/mir/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod analysis; 2 | pub mod ir; 3 | pub mod test; 4 | pub mod types; 5 | -------------------------------------------------------------------------------- /lib/mir/src/test.rs: -------------------------------------------------------------------------------- 1 | mod ir; 2 | 3 | pub use ir::*; 4 | -------------------------------------------------------------------------------- /lib/mir/src/test/ir.rs: -------------------------------------------------------------------------------- 1 | mod function_definition; 2 | mod global_function_definition; 3 | mod module; 4 | 5 | pub use function_definition::*; 6 | pub use global_function_definition::*; 7 | pub use module::*; 8 | -------------------------------------------------------------------------------- /lib/mir/src/test/ir/function_definition.rs: -------------------------------------------------------------------------------- 1 | use crate::ir::*; 2 | 3 | pub trait FunctionDefinitionFake { 4 | fn set_environment(&self, environment: Vec) -> Self; 5 | } 6 | 7 | impl FunctionDefinitionFake for FunctionDefinition { 8 | fn set_environment(&self, environment: Vec) -> Self { 9 | Self::with_options( 10 | self.name(), 11 | environment, 12 | self.arguments().to_vec(), 13 | self.result_type().clone(), 14 | self.body().clone(), 15 | self.is_thunk(), 16 | ) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/mir/src/test/ir/global_function_definition.rs: -------------------------------------------------------------------------------- 1 | use crate::ir::*; 2 | 3 | pub trait GlobalFunctionDefinitionFake { 4 | fn fake(definition: FunctionDefinition) -> Self; 5 | } 6 | 7 | impl GlobalFunctionDefinitionFake for GlobalFunctionDefinition { 8 | fn fake(definition: FunctionDefinition) -> Self { 9 | Self::new(definition, false) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/mir/src/types.rs: -------------------------------------------------------------------------------- 1 | mod function; 2 | mod record; 3 | mod record_body; 4 | mod type_; 5 | 6 | pub use function::*; 7 | pub use record::*; 8 | pub use record_body::*; 9 | pub use type_::*; 10 | -------------------------------------------------------------------------------- /lib/mir/src/types/record.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 4 | pub struct Record { 5 | name: Rc, 6 | } 7 | 8 | impl Record { 9 | pub fn new(name: impl Into) -> Self { 10 | Self { 11 | name: name.into().into(), 12 | } 13 | } 14 | 15 | pub fn name(&self) -> &str { 16 | &self.name 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/mir/src/types/record_body.rs: -------------------------------------------------------------------------------- 1 | use super::type_::Type; 2 | 3 | #[derive(Clone, Debug, Eq, PartialEq)] 4 | pub struct RecordBody { 5 | fields: Vec, 6 | } 7 | 8 | impl RecordBody { 9 | pub const fn new(fields: Vec) -> Self { 10 | Self { fields } 11 | } 12 | 13 | pub fn fields(&self) -> &[Type] { 14 | &self.fields 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/mir/src/types/type_information.rs: -------------------------------------------------------------------------------- 1 | use super::Type; 2 | use crate::types; 3 | use fnv::FnvHashMap; 4 | 5 | #[derive(Clone, Debug, Default, PartialEq, Eq)] 6 | pub struct TypeInformation { 7 | types: Vec, 8 | information: FnvHashMap>, 9 | } 10 | 11 | impl TypeInformation { 12 | pub fn new(types: Vec, information: FnvHashMap>) -> Self { 13 | Self { types, information } 14 | } 15 | 16 | pub fn types(&self) -> &[types::Function] { 17 | &self.types 18 | } 19 | 20 | pub fn information(&self) -> &FnvHashMap> { 21 | &self.information 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/parse/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "parse" 3 | version = "0.1.0" 4 | authors = ["Yota Toyama "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | ast = { path = "../ast" } 9 | nom = "7.1.3" 10 | nom_locate = "4.2.0" 11 | position = { path = "../position" } 12 | 13 | [dev-dependencies] 14 | indoc = "2" 15 | insta = "1" 16 | pretty_assertions = "1" 17 | -------------------------------------------------------------------------------- /lib/parse/src/input.rs: -------------------------------------------------------------------------------- 1 | use nom_locate::LocatedSpan; 2 | use position::Position; 3 | use std::str; 4 | 5 | pub type Input<'a> = LocatedSpan<&'a str, &'a str>; 6 | 7 | pub fn input<'a>(source: &'a str, path: &'a str) -> Input<'a> { 8 | LocatedSpan::new_extra(source, path) 9 | } 10 | 11 | pub fn position(input: Input) -> Position { 12 | Position::new( 13 | input.extra, 14 | input.location_line() as usize, 15 | input.get_column(), 16 | str::from_utf8(input.get_line_beginning()).unwrap(), 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /lib/parse/src/snapshots/parse__parser__tests__import__fail_to_parse_private_external_module_directory.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: lib/parse/src/parser.rs 3 | expression: "external_module_path(input(source,\n \"\")).map_err(|error|\n ParseError::new(source, \"\", error)).unwrap_err()" 4 | --- 5 | ParseError { 6 | message: "failed to parse public module path component", 7 | position: Position( 8 | PositionInner { 9 | path: "", 10 | line_number: 1, 11 | column_number: 5, 12 | line: "Foo'bar'Baz", 13 | }, 14 | ), 15 | } 16 | -------------------------------------------------------------------------------- /lib/parse/src/snapshots/parse__parser__tests__import__fail_to_parse_private_external_module_file.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: lib/parse/src/parser.rs 3 | expression: "external_module_path(input(source,\n \"\")).map_err(|error|\n ParseError::new(source, \"\", error)).unwrap_err()" 4 | --- 5 | ParseError { 6 | message: "failed to parse public module path component", 7 | position: Position( 8 | PositionInner { 9 | path: "", 10 | line_number: 1, 11 | column_number: 5, 12 | line: "Foo'bar", 13 | }, 14 | ), 15 | } 16 | -------------------------------------------------------------------------------- /lib/position/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "position" 3 | version = "0.1.0" 4 | authors = ["Yota Toyama "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | serde = { version = "1", features = ["derive", "rc"] } 9 | -------------------------------------------------------------------------------- /lib/position/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod format; 2 | mod position; 3 | pub mod test; 4 | 5 | pub use self::{format::*, position::*}; 6 | -------------------------------------------------------------------------------- /lib/position/src/test.rs: -------------------------------------------------------------------------------- 1 | use crate::Position; 2 | 3 | pub trait PositionFake { 4 | fn fake() -> Self; 5 | } 6 | 7 | impl PositionFake for Position { 8 | fn fake() -> Self { 9 | Self::new("", 1, 1, "") 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/test-info/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test-info" 3 | version = "0.1.0" 4 | authors = ["Yota Toyama "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | position = { path = "../position" } 9 | serde = { version = "1", features = ["derive", "rc"] } 10 | -------------------------------------------------------------------------------- /lib/test-info/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod function; 2 | mod module; 3 | mod package; 4 | 5 | pub use function::*; 6 | pub use module::*; 7 | pub use package::*; 8 | -------------------------------------------------------------------------------- /lib/test-info/src/module.rs: -------------------------------------------------------------------------------- 1 | use crate::function::Function; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | #[derive(Clone, Debug, Deserialize, Serialize)] 5 | pub struct Module { 6 | path: String, 7 | functions: Vec, 8 | } 9 | 10 | impl Module { 11 | pub fn new(path: impl Into, functions: Vec) -> Self { 12 | Self { 13 | path: path.into(), 14 | functions, 15 | } 16 | } 17 | 18 | pub fn path(&self) -> &str { 19 | &self.path 20 | } 21 | 22 | pub fn functions(&self) -> &[Function] { 23 | &self.functions 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/test-info/src/package.rs: -------------------------------------------------------------------------------- 1 | use crate::Module; 2 | use serde::{Deserialize, Serialize}; 3 | use std::collections::BTreeMap; 4 | 5 | #[derive(Clone, Debug, Deserialize, Serialize)] 6 | pub struct Package { 7 | modules: BTreeMap, 8 | } 9 | 10 | impl Package { 11 | pub fn new(modules: BTreeMap) -> Self { 12 | Self { modules } 13 | } 14 | 15 | pub fn modules(&self) -> &BTreeMap { 16 | &self.modules 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/core/Boolean.pen: -------------------------------------------------------------------------------- 1 | # This module provides common boolean operations. 2 | 3 | # Return true if any of given booleans are true or false otherwise. 4 | Any = \(bs [boolean]) boolean { 5 | if [b, ...bs] = bs { 6 | if b() { 7 | true 8 | } else { 9 | Any(bs) 10 | } 11 | } else { 12 | false 13 | } 14 | } 15 | 16 | # Return true if all of given booleans are true or false otherwise. 17 | All = \(bs [boolean]) boolean { 18 | if [b, ...bs] = bs { 19 | if b() { 20 | All(bs) 21 | } else { 22 | false 23 | } 24 | } else { 25 | true 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/core/Character.pen: -------------------------------------------------------------------------------- 1 | import foreign "c" _pen_core_character_to_code_point \(string) number 2 | import foreign "c" _pen_core_character_from_code_point \(number) string 3 | 4 | FromCodePoint = \(n number) string { _pen_core_character_from_code_point(n) } 5 | 6 | ToCodePoint = \(s string) number { _pen_core_character_to_code_point(s) } 7 | -------------------------------------------------------------------------------- /packages/core/Character.test.pen: -------------------------------------------------------------------------------- 1 | import Test'Assert 2 | 3 | import 'Character 4 | 5 | FromCodePoint = \() none | error { 6 | Assert'Equal(Character'FromCodePoint(0x61), "a") 7 | } 8 | 9 | ToCodePoint = \() none | error { 10 | Assert'Equal(Character'ToCodePoint("a"), 0x61) 11 | } 12 | -------------------------------------------------------------------------------- /packages/core/String/Byte.pen: -------------------------------------------------------------------------------- 1 | # This module provides operations for strings as byte arrays. 2 | 3 | import foreign "c" _pen_core_byte_length \(string) number 4 | import foreign "c" _pen_core_byte_slice \(string, number, number) string 5 | 6 | # Return a length of a string. 7 | Length = \(s string) number { 8 | _pen_core_byte_length(s) 9 | } 10 | 11 | # Slice a string. 12 | Slice = \(s string, start number, end number) string { 13 | _pen_core_byte_slice(s, start, end) 14 | } 15 | -------------------------------------------------------------------------------- /packages/core/String/Byte.test.pen: -------------------------------------------------------------------------------- 1 | import Test'Assert 2 | 3 | import 'Number 4 | import 'String'Byte 5 | 6 | LengthEmpty = \() none | error { 7 | Assert'Equal(Byte'Length(""), 0) 8 | } 9 | 10 | Length1 = \() none | error { 11 | Assert'Equal(Byte'Length("a"), 1) 12 | } 13 | 14 | Length2 = \() none | error { 15 | Assert'Equal(Byte'Length("ho"), 2) 16 | } 17 | 18 | SliceStart = \() none | error { 19 | Assert'Equal(Byte'Slice("abc", 1, 2), "ab") 20 | } 21 | 22 | SliceMiddle = \() none | error { 23 | Assert'Equal(Byte'Slice("abc", 2, 2), "b") 24 | } 25 | 26 | SliceEnd = \() none | error { 27 | Assert'Equal(Byte'Slice("abc", 2, 3), "bc") 28 | } 29 | 30 | SliceInfinity = \() none | error { 31 | Assert'Equal(Byte'Slice("abc", 2, Number'Infinity()), "bc") 32 | } 33 | -------------------------------------------------------------------------------- /packages/core/ffi/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "core" 3 | version = "0.1.0" 4 | publish = false 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["staticlib"] 9 | 10 | [dependencies] 11 | ffi = { package = "pen-ffi", version = "*" } 12 | -------------------------------------------------------------------------------- /packages/core/ffi/rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "stable" 3 | targets = [ 4 | "i686-unknown-linux-musl", 5 | "x86_64-unknown-linux-musl", 6 | "aarch64-unknown-linux-musl", 7 | "wasm32-wasip2", 8 | ] 9 | -------------------------------------------------------------------------------- /packages/core/ffi/src/character.rs: -------------------------------------------------------------------------------- 1 | use core::str; 2 | 3 | const INVALID_CODE_POINT: ffi::Number = ffi::Number::new(f64::NAN); 4 | 5 | #[ffi::bindgen] 6 | fn _pen_core_character_from_code_point(code_point: ffi::Number) -> ffi::ByteString { 7 | char::from_u32(f64::from(code_point) as u32) 8 | .map(ffi::ByteString::from) 9 | .unwrap_or_default() 10 | } 11 | 12 | #[ffi::bindgen] 13 | fn _pen_core_character_to_code_point(string: ffi::ByteString) -> ffi::Number { 14 | if let Ok(string) = str::from_utf8(string.as_slice()) { 15 | string 16 | .chars() 17 | .next() 18 | .map(|character| (character as u32 as f64).into()) 19 | .unwrap_or(INVALID_CODE_POINT) 20 | } else { 21 | INVALID_CODE_POINT 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/core/ffi/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | extern crate alloc; 4 | 5 | mod bit; 6 | mod character; 7 | mod number; 8 | mod string; 9 | mod view; 10 | -------------------------------------------------------------------------------- /packages/core/ffi/src/string.rs: -------------------------------------------------------------------------------- 1 | mod builder; 2 | mod byte; 3 | mod utf8; 4 | 5 | #[ffi::bindgen] 6 | fn _pen_core_string_starts_with(string: ffi::ByteString, prefix: ffi::ByteString) -> ffi::Boolean { 7 | let string = string.as_slice(); 8 | let prefix = prefix.as_slice(); 9 | 10 | (string.len() >= prefix.len() && &string[..prefix.len()] == prefix).into() 11 | } 12 | -------------------------------------------------------------------------------- /packages/core/pen-ffi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | while getopts t: option; do 6 | case $option in 7 | t) 8 | target=$OPTARG 9 | ;; 10 | esac 11 | done 12 | 13 | shift $(expr $OPTIND - 1) 14 | 15 | if [ -z $target ]; then 16 | exit 1 17 | fi 18 | 19 | cd $(dirname $0)/ffi 20 | cargo build --release --quiet --target $target 21 | # spell-checker: disable-next-line 22 | cp target/$target/release/libcore.a $1 23 | -------------------------------------------------------------------------------- /packages/core/pen.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "library", 3 | "dependencies": { 4 | "Test": "pen:///test" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/ffi/list.pen: -------------------------------------------------------------------------------- 1 | type firstRest { 2 | ok boolean 3 | first \() any 4 | rest [any] 5 | } 6 | 7 | foreign "c" pen_ffi_list_create = \() [any] { 8 | [any] 9 | } 10 | 11 | foreign "c" pen_ffi_list_prepend = \(x any, xs [any]) [any] { 12 | [any x, ...xs] 13 | } 14 | 15 | foreign "c" pen_ffi_list_lazy = \(xs \() [any]) [any] { 16 | [any ...xs()] 17 | } 18 | 19 | foreign pen_ffi_list_first_rest = \(xs [any]) firstRest { 20 | if [x, ...xs] = xs { 21 | firstRest{ok: true, first: x, rest: xs} 22 | } else { 23 | firstRest{ok: false, first: \() any { none }, rest: [any]} 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/ffi/pen.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "library", 3 | "dependencies": {} 4 | } 5 | -------------------------------------------------------------------------------- /packages/flag/pen.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "library", 3 | "dependencies": { 4 | "Core": "pen:///core", 5 | "Test": "pen:///test" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/html/Node.pen: -------------------------------------------------------------------------------- 1 | # An HTML node 2 | type Node = Element | string 3 | 4 | # An HTML element 5 | type Element { 6 | Tag string 7 | Attributes [Attribute] 8 | Children [Node] 9 | } 10 | 11 | # An HTML attribute 12 | type Attribute { 13 | Key string 14 | Value string 15 | } 16 | -------------------------------------------------------------------------------- /packages/html/ffi/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ffi" 3 | version = "0.1.0" 4 | publish = false 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["staticlib"] 9 | 10 | [dependencies] 11 | ffi = { package = "pen-ffi", version = "*", features = ["std"] } 12 | html-escape = "0.2" 13 | -------------------------------------------------------------------------------- /packages/html/ffi/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | use core::str; 4 | 5 | #[ffi::bindgen] 6 | fn _pen_html_encode_text(string: ffi::ByteString) -> ffi::ByteString { 7 | html_escape::encode_text(str::from_utf8(string.as_slice()).unwrap_or_default()) 8 | .as_bytes() 9 | .into() 10 | } 11 | 12 | #[ffi::bindgen] 13 | fn _pen_html_encode_attribute(string: ffi::ByteString) -> ffi::ByteString { 14 | html_escape::encode_quoted_attribute(str::from_utf8(string.as_slice()).unwrap_or_default()) 15 | .as_bytes() 16 | .into() 17 | } 18 | -------------------------------------------------------------------------------- /packages/html/pen-ffi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | while getopts t: option; do 6 | case $option in 7 | t) 8 | target=$OPTARG 9 | ;; 10 | esac 11 | done 12 | 13 | shift $(expr $OPTIND - 1) 14 | 15 | if [ -z $target ]; then 16 | exit 1 17 | fi 18 | 19 | cd $(dirname $0)/ffi 20 | cargo build --release --quiet --target $target 21 | # spell-checker: disable-next-line 22 | cp target/$target/release/libffi.a $1 23 | -------------------------------------------------------------------------------- /packages/html/pen.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "library", 3 | "dependencies": { 4 | "Core": "pen:///core", 5 | "Test": "pen:///test" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/html/validate.pen: -------------------------------------------------------------------------------- 1 | import Core'Boolean 2 | import Core'Character 3 | import Core'Number 4 | import Core'String'Byte'View { View } 5 | import Core'String'Utf8 6 | 7 | Name = \(s string) boolean { 8 | Utf8'Length(s) != 0 & nameView(View'New(s)) 9 | } 10 | 11 | nameView = \(v View) boolean { 12 | View'Length(v) == 0 13 | | Boolean'Any([boolean View'StartsWith(v, s()) for s in validCharacters()]) 14 | & nameView(View'Seek(v, 1)) 15 | } 16 | 17 | validCharacters = \() [string] { 18 | [string 19 | "-", 20 | ...[string 21 | Character'FromCodePoint(n()) 22 | for n in Number'Range(Character'ToCodePoint("a"), Character'ToCodePoint("z")) 23 | ], 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /packages/html/validate.test.pen: -------------------------------------------------------------------------------- 1 | import Test'Assert 2 | 3 | import 'validate 4 | 5 | ValidateName = \() none | error { 6 | Assert'True(validate'Name("foo")) 7 | } 8 | 9 | ValidateHyphenatedName = \() none | error { 10 | Assert'True(validate'Name("foo-bar")) 11 | } 12 | 13 | ValidateInvalidName = \() none | error { 14 | Assert'True(!validate'Name("")) 15 | } 16 | -------------------------------------------------------------------------------- /packages/http/Client.pen: -------------------------------------------------------------------------------- 1 | # This module provides an HTTP client. 2 | 3 | import 'Context'context { Context } 4 | import 'Request { Request } 5 | import 'Response { Response } 6 | 7 | # Send an HTTP request. 8 | Send = \(ctx Context, r Request) Response | error { 9 | context'Inner(ctx).Send(r) 10 | } 11 | -------------------------------------------------------------------------------- /packages/http/Context.pen: -------------------------------------------------------------------------------- 1 | import 'Context'context 2 | 3 | # An HTTP context 4 | type Context = context'Context 5 | 6 | UnsafeNew = \() Context { context'New() } 7 | -------------------------------------------------------------------------------- /packages/http/Context/ffiResponse.pen: -------------------------------------------------------------------------------- 1 | import 'Context'ffiHeaderMap { FfiHeaderMap } 2 | 3 | type FfiResponse { 4 | Status number 5 | Headers FfiHeaderMap 6 | Body string 7 | } 8 | 9 | foreign "c" _pen_http_response_to_any = \(r FfiResponse) any { r } 10 | -------------------------------------------------------------------------------- /packages/http/Request.pen: -------------------------------------------------------------------------------- 1 | # An HTTP request 2 | type Request { 3 | Method string 4 | Uri string 5 | Headers {string: string} 6 | Body string 7 | } 8 | -------------------------------------------------------------------------------- /packages/http/Response.pen: -------------------------------------------------------------------------------- 1 | # An HTTP response 2 | type Response { 3 | Status number 4 | Headers {string: string} 5 | Body string 6 | } 7 | -------------------------------------------------------------------------------- /packages/http/Server.pen: -------------------------------------------------------------------------------- 1 | # This module provides an HTTP server. 2 | 3 | import 'Context'context { Context } 4 | import 'Request { Request } 5 | import 'Response { Response } 6 | 7 | # Run an HTTP service. 8 | Serve = \(ctx Context, address string, callback \(Request) Response) none | error { 9 | context'Inner(ctx).Serve(address, callback) 10 | } 11 | -------------------------------------------------------------------------------- /packages/http/ffi/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pen-http" 3 | version = "0.1.0" 4 | publish = false 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["staticlib"] 9 | 10 | [dependencies] 11 | ffi = { package = "pen-ffi", version = "*", features = ["runtime", "std"] } 12 | futures = "0.3" 13 | hyper = { version = "0.14", features = [ 14 | "client", 15 | "http1", 16 | "http2", 17 | "server", 18 | "tcp", 19 | ] } 20 | tokio = { version = "1", features = ["full"] } 21 | -------------------------------------------------------------------------------- /packages/http/ffi/rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "stable" 3 | targets = [ 4 | "i686-unknown-linux-musl", 5 | "x86_64-unknown-linux-musl", 6 | "aarch64-unknown-linux-musl", 7 | "wasm32-wasip2", 8 | ] 9 | -------------------------------------------------------------------------------- /packages/http/ffi/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod client; 2 | mod header_map; 3 | mod response; 4 | mod server; 5 | -------------------------------------------------------------------------------- /packages/http/pen-ffi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | while getopts t: option; do 6 | case $option in 7 | t) 8 | target=$OPTARG 9 | ;; 10 | esac 11 | done 12 | 13 | shift $(expr $OPTIND - 1) 14 | 15 | if [ -z $target ]; then 16 | exit 1 17 | fi 18 | 19 | cd $(dirname $0)/ffi 20 | cargo build --release --quiet --target $target 21 | # spell-checker: disable-next-line 22 | cp target/$target/release/libpen_http.a $1 23 | -------------------------------------------------------------------------------- /packages/http/pen.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "system", 3 | "dependencies": {} 4 | } 5 | -------------------------------------------------------------------------------- /packages/json/Value.pen: -------------------------------------------------------------------------------- 1 | # A JSON value 2 | type Value { 3 | raw Raw 4 | } 5 | 6 | # A raw JSON value represented by built-in types 7 | type Raw = boolean | none | number | string | [Value] | {string: Value} 8 | 9 | # Create a JSON value. 10 | New = \(r Raw) Value { 11 | Value{raw: r} 12 | } 13 | 14 | # Get a raw value. 15 | Raw = \(v Value) Raw { 16 | v.raw 17 | } 18 | -------------------------------------------------------------------------------- /packages/json/ffi/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "json" 3 | version = "0.1.0" 4 | publish = false 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["staticlib"] 9 | 10 | [dependencies] 11 | ffi = { package = "pen-ffi", version = "*" } 12 | -------------------------------------------------------------------------------- /packages/json/ffi/rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "stable" 3 | targets = [ 4 | "i686-unknown-linux-musl", 5 | "x86_64-unknown-linux-musl", 6 | "aarch64-unknown-linux-musl", 7 | "wasm32-wasip2", 8 | ] 9 | -------------------------------------------------------------------------------- /packages/json/pen-ffi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | while getopts t: option; do 6 | case $option in 7 | t) 8 | target=$OPTARG 9 | ;; 10 | esac 11 | done 12 | 13 | shift $(expr $OPTIND - 1) 14 | 15 | if [ -z $target ]; then 16 | exit 1 17 | fi 18 | 19 | cd $(dirname $0)/ffi 20 | cargo build --release --quiet --target $target 21 | # spell-checker: disable-next-line 22 | cp target/$target/release/libjson.a $1 23 | -------------------------------------------------------------------------------- /packages/json/pen.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "library", 3 | "dependencies": { 4 | "Core": "pen:///core", 5 | "Test": "pen:///test" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/os-sync/Context.pen: -------------------------------------------------------------------------------- 1 | import 'Context'context 2 | 3 | # A context of an operating system. 4 | type Context = context'Context 5 | 6 | UnsafeNew = \() Context { 7 | context'UnsafeNew() 8 | } 9 | -------------------------------------------------------------------------------- /packages/os-sync/Directory.pen: -------------------------------------------------------------------------------- 1 | import 'Context'context { Context } 2 | 3 | # Read a directory and return file paths it contains. 4 | Read = \(ctx Context, path string) [string] | error { 5 | context'Inner(ctx).ReadDirectory(path) 6 | } 7 | 8 | # Create a directory. 9 | Create = \(ctx Context, path string) none | error { 10 | context'Inner(ctx).CreateDirectory(path) 11 | } 12 | 13 | # Remove a directory. 14 | Remove = \(ctx Context, path string) none | error { 15 | context'Inner(ctx).RemoveDirectory(path) 16 | } 17 | -------------------------------------------------------------------------------- /packages/os-sync/Environment.pen: -------------------------------------------------------------------------------- 1 | import 'Context'context { Context } 2 | 3 | # Get command line arguments. 4 | Arguments = \(ctx Context) [string] { 5 | context'Inner(ctx).GetArguments() 6 | } 7 | 8 | # Get an environment variable. 9 | Variable = \(ctx Context, name string) string | error { 10 | context'Inner(ctx).GetEnvironmentVariable(name) 11 | } 12 | -------------------------------------------------------------------------------- /packages/os-sync/File/Metadata.pen: -------------------------------------------------------------------------------- 1 | # File metadata 2 | type Metadata { 3 | Size number 4 | } 5 | 6 | foreign "c" _pen_os_file_metadata_to_any = \(m Metadata) any { m } 7 | -------------------------------------------------------------------------------- /packages/os-sync/Process.pen: -------------------------------------------------------------------------------- 1 | import 'Context'context { Context } 2 | 3 | # Exit a current process. 4 | Exit = \(ctx Context, code number) none { 5 | context'Inner(ctx).Exit(code) 6 | } 7 | -------------------------------------------------------------------------------- /packages/os-sync/Tcp/AcceptedStream.pen: -------------------------------------------------------------------------------- 1 | import 'Tcp'Stream { Stream } 2 | 3 | # A TCP stream accepted on a server with a client address 4 | type AcceptedStream { 5 | Stream Stream 6 | Address string 7 | } 8 | 9 | foreign "c" _pen_os_tcp_accepted_stream_to_any = \(s AcceptedStream) any { s } 10 | -------------------------------------------------------------------------------- /packages/os-sync/Tcp/Listener.pen: -------------------------------------------------------------------------------- 1 | # A TCP listener to listen for client connections 2 | type Listener { 3 | inner any 4 | } 5 | 6 | foreign "c" _pen_os_tcp_listener_to_any = \(l Listener) any { l } 7 | -------------------------------------------------------------------------------- /packages/os-sync/Tcp/Stream.pen: -------------------------------------------------------------------------------- 1 | # A TCP stream 2 | type Stream { 3 | inner any 4 | } 5 | 6 | foreign "c" _pen_os_tcp_stream_to_any = \(s Stream) any { s } 7 | -------------------------------------------------------------------------------- /packages/os-sync/Time.pen: -------------------------------------------------------------------------------- 1 | import 'Context'context { Context } 2 | 3 | # Fetch a current system time in milliseconds. 4 | Now = \(ctx Context) number { 5 | context'Inner(ctx).GetTime() 6 | } 7 | 8 | # Pause a current execution context for a given amount of time. 9 | Sleep = \(ctx Context, milliseconds number) none { 10 | context'Inner(ctx).Sleep(milliseconds) 11 | } 12 | -------------------------------------------------------------------------------- /packages/os-sync/Udp/Datagram.pen: -------------------------------------------------------------------------------- 1 | # UDP datagram 2 | type Datagram { 3 | Data string 4 | Address string 5 | } 6 | 7 | foreign "c" _pen_os_udp_datagram_to_any = \(d Datagram) any { d } 8 | -------------------------------------------------------------------------------- /packages/os-sync/Udp/Socket.pen: -------------------------------------------------------------------------------- 1 | # UDP socket 2 | type Socket { 3 | inner any 4 | } 5 | 6 | foreign "c" _pen_os_udp_socket_to_any = \(s Socket) any { s } 7 | -------------------------------------------------------------------------------- /packages/os-sync/concurrency.pen: -------------------------------------------------------------------------------- 1 | import foreign "c" _pen_os_unreachable \() none 2 | 3 | foreign _pen_spawn = \(f \() any) \() any { 4 | f 5 | } 6 | 7 | foreign _pen_race = \(xss [[any]]) [any] { 8 | if [xs, ...xss] = xss { 9 | [any ...xs(), ..._pen_race(xss)] 10 | } else { 11 | [any] 12 | } 13 | } 14 | 15 | foreign _pen_yield = \() none { 16 | _pen_os_unreachable() 17 | } 18 | -------------------------------------------------------------------------------- /packages/os-sync/ffi/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = ["application", "library"] 4 | -------------------------------------------------------------------------------- /packages/os-sync/ffi/application/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.i686-unknown-linux-musl] 2 | linker = "rust-lld" 3 | 4 | [target.x86_64-unknown-linux-musl] 5 | linker = "rust-lld" 6 | 7 | [target.aarch64-unknown-linux-musl] 8 | linker = "rust-lld" 9 | -------------------------------------------------------------------------------- /packages/os-sync/ffi/application/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "os-app" 3 | version = "0.1.0" 4 | publish = false 5 | edition = "2021" 6 | 7 | [dependencies] 8 | ffi = { package = "pen-ffi", version = "*" } 9 | -------------------------------------------------------------------------------- /packages/os-sync/ffi/application/src/debug.rs: -------------------------------------------------------------------------------- 1 | use std::str; 2 | 3 | #[ffi::bindgen] 4 | pub fn _pen_debug(message: ffi::ByteString) { 5 | eprintln!( 6 | "{}", 7 | str::from_utf8(message.as_slice()).unwrap_or("failed to decode debug message") 8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /packages/os-sync/ffi/application/src/main.rs: -------------------------------------------------------------------------------- 1 | mod debug; 2 | mod heap; 3 | mod unreachable; 4 | mod utilities; 5 | 6 | const INITIAL_STACK_CAPACITY: usize = 256; 7 | 8 | #[cfg(not(test))] 9 | #[link(name = "main")] 10 | extern "C" { 11 | fn _pen_main( 12 | stack: *mut ffi::cps::Stack, 13 | continuation: extern "C" fn(*mut ffi::cps::Stack, ffi::None), 14 | ); 15 | } 16 | 17 | #[cfg(test)] 18 | unsafe extern "C" fn _pen_main( 19 | _: *mut ffi::cps::Stack, 20 | _: extern "C" fn(*mut ffi::cps::Stack, ffi::None), 21 | ) { 22 | } 23 | 24 | fn main() { 25 | let mut stack = ffi::cps::Stack::new(INITIAL_STACK_CAPACITY); 26 | 27 | unsafe { _pen_main(&mut stack, do_nothing) }; 28 | } 29 | 30 | extern "C" fn do_nothing(_: *mut ffi::cps::Stack, _: ffi::None) {} 31 | -------------------------------------------------------------------------------- /packages/os-sync/ffi/application/src/unreachable.rs: -------------------------------------------------------------------------------- 1 | #[ffi::bindgen] 2 | fn _pen_unreachable() -> ffi::None { 3 | unreachable!("PEN_OS_UNREACHABLE_ERROR") 4 | } 5 | 6 | #[ffi::bindgen] 7 | fn _pen_os_unreachable() { 8 | _pen_unreachable() 9 | } 10 | -------------------------------------------------------------------------------- /packages/os-sync/ffi/application/src/utilities.rs: -------------------------------------------------------------------------------- 1 | use std::{env, sync::LazyLock}; 2 | 3 | static OS_DEBUG: LazyLock = LazyLock::new(|| env::var("PEN_OS_DEBUG").is_ok()); 4 | 5 | pub fn is_os_debug() -> bool { 6 | *OS_DEBUG 7 | } 8 | -------------------------------------------------------------------------------- /packages/os-sync/ffi/library/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "os" 3 | version = "0.1.0" 4 | publish = false 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["staticlib"] 9 | 10 | [dependencies] 11 | ffi = { package = "pen-ffi", version = "*", features = ["std"] } 12 | 13 | [dev-dependencies] 14 | tempfile = "3" 15 | -------------------------------------------------------------------------------- /packages/os-sync/ffi/library/src/argument.rs: -------------------------------------------------------------------------------- 1 | #[ffi::bindgen] 2 | fn _pen_os_get_arguments() -> ffi::List { 3 | std::env::args() 4 | .skip(1) 5 | .map(ffi::ByteString::from) 6 | .collect::>() 7 | .into() 8 | } 9 | -------------------------------------------------------------------------------- /packages/os-sync/ffi/library/src/environment_variable.rs: -------------------------------------------------------------------------------- 1 | use std::{error::Error, ffi::OsString, str}; 2 | 3 | #[ffi::bindgen] 4 | fn _pen_os_get_environment_variable( 5 | name: ffi::ByteString, 6 | ) -> Result> { 7 | Ok(std::env::var(OsString::from(str::from_utf8(name.as_slice())?))?.into()) 8 | } 9 | -------------------------------------------------------------------------------- /packages/os-sync/ffi/library/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod argument; 2 | mod directory; 3 | mod environment_variable; 4 | mod error; 5 | mod file; 6 | mod open_file_options; 7 | mod process; 8 | mod stdio; 9 | mod tcp; 10 | mod time; 11 | mod udp; 12 | mod utilities; 13 | -------------------------------------------------------------------------------- /packages/os-sync/ffi/library/src/process.rs: -------------------------------------------------------------------------------- 1 | use std::process::exit; 2 | 3 | #[ffi::bindgen] 4 | fn _pen_os_exit(code: ffi::Number) -> ffi::None { 5 | exit(f64::from(code) as i32) 6 | } 7 | -------------------------------------------------------------------------------- /packages/os-sync/ffi/library/src/time.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | thread::sleep, 3 | time::{Duration, SystemTime, UNIX_EPOCH}, 4 | }; 5 | 6 | #[ffi::bindgen] 7 | fn _pen_os_get_time() -> ffi::Number { 8 | (SystemTime::now() 9 | .duration_since(UNIX_EPOCH) 10 | .unwrap_or_else(|error| error.duration()) 11 | .as_millis() as f64) 12 | .into() 13 | } 14 | 15 | #[ffi::bindgen] 16 | fn _pen_os_sleep(milliseconds: ffi::Number) { 17 | sleep(Duration::from_millis(f64::from(milliseconds) as u64)); 18 | } 19 | -------------------------------------------------------------------------------- /packages/os-sync/ffi/rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "stable" 3 | targets = [ 4 | "i686-unknown-linux-musl", 5 | "x86_64-unknown-linux-musl", 6 | "aarch64-unknown-linux-musl", 7 | "wasm32-wasip2", 8 | ] 9 | -------------------------------------------------------------------------------- /packages/os-sync/normalFile.pen: -------------------------------------------------------------------------------- 1 | type NormalFile { 2 | inner any 3 | } 4 | 5 | foreign "c" _pen_os_file_to_any = \(f NormalFile) any { f } 6 | -------------------------------------------------------------------------------- /packages/os-sync/pen-ffi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | while getopts t: option; do 6 | case $option in 7 | t) 8 | target=$OPTARG 9 | ;; 10 | esac 11 | done 12 | 13 | shift $(expr $OPTIND - 1) 14 | 15 | if [ -z $target ]; then 16 | exit 1 17 | fi 18 | 19 | cd $(dirname $0)/ffi/library 20 | cargo build --release --quiet --target $target 21 | # spell-checker: disable-next-line 22 | cp ../target/$target/release/libos.a $1 23 | -------------------------------------------------------------------------------- /packages/os-sync/pen.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "system", 3 | "dependencies": {} 4 | } 5 | -------------------------------------------------------------------------------- /packages/os/Context.pen: -------------------------------------------------------------------------------- 1 | import 'Context'context 2 | 3 | # A context of an operating system. 4 | type Context = context'Context 5 | 6 | UnsafeNew = \() Context { 7 | context'UnsafeNew() 8 | } 9 | -------------------------------------------------------------------------------- /packages/os/Directory.pen: -------------------------------------------------------------------------------- 1 | import 'Context'context { Context } 2 | 3 | # Read a directory and return file paths it contains. 4 | Read = \(ctx Context, path string) [string] | error { 5 | context'Inner(ctx).ReadDirectory(path) 6 | } 7 | 8 | # Create a directory. 9 | Create = \(ctx Context, path string) none | error { 10 | context'Inner(ctx).CreateDirectory(path) 11 | } 12 | 13 | # Remove a directory. 14 | Remove = \(ctx Context, path string) none | error { 15 | context'Inner(ctx).RemoveDirectory(path) 16 | } 17 | -------------------------------------------------------------------------------- /packages/os/Environment.pen: -------------------------------------------------------------------------------- 1 | import 'Context'context { Context } 2 | 3 | # Get command line arguments. 4 | Arguments = \(ctx Context) [string] { 5 | context'Inner(ctx).GetArguments() 6 | } 7 | 8 | # Get an environment variable. 9 | Variable = \(ctx Context, name string) string | error { 10 | context'Inner(ctx).GetEnvironmentVariable(name) 11 | } 12 | -------------------------------------------------------------------------------- /packages/os/File/Metadata.pen: -------------------------------------------------------------------------------- 1 | # File metadata 2 | type Metadata { 3 | Size number 4 | } 5 | 6 | foreign "c" _pen_os_file_metadata_to_any = \(m Metadata) any { m } 7 | -------------------------------------------------------------------------------- /packages/os/Process.pen: -------------------------------------------------------------------------------- 1 | import 'Context'context { Context } 2 | 3 | # Exit a current process. 4 | Exit = \(ctx Context, code number) none { 5 | context'Inner(ctx).Exit(code) 6 | } 7 | 8 | # Run a command. 9 | Run = \(ctx Context, cmd string, args [string]) none | error { 10 | context'Inner(ctx).RunCommand(cmd, args) 11 | } 12 | -------------------------------------------------------------------------------- /packages/os/Tcp/AcceptedStream.pen: -------------------------------------------------------------------------------- 1 | import 'Tcp'Stream { Stream } 2 | 3 | # A TCP stream accepted on a server with a client address 4 | type AcceptedStream { 5 | Stream Stream 6 | Address string 7 | } 8 | 9 | foreign "c" _pen_os_tcp_accepted_stream_to_any = \(s AcceptedStream) any { s } 10 | -------------------------------------------------------------------------------- /packages/os/Tcp/Listener.pen: -------------------------------------------------------------------------------- 1 | # A TCP listener to listen for client connections 2 | type Listener { 3 | inner any 4 | } 5 | 6 | foreign "c" _pen_os_tcp_listener_to_any = \(l Listener) any { l } 7 | -------------------------------------------------------------------------------- /packages/os/Tcp/Stream.pen: -------------------------------------------------------------------------------- 1 | # A TCP stream 2 | type Stream { 3 | inner any 4 | } 5 | 6 | foreign "c" _pen_os_tcp_stream_to_any = \(s Stream) any { s } 7 | -------------------------------------------------------------------------------- /packages/os/Time.pen: -------------------------------------------------------------------------------- 1 | import 'Context'context { Context } 2 | 3 | # Fetch a current system time in milliseconds. 4 | Now = \(ctx Context) number { 5 | context'Inner(ctx).GetTime() 6 | } 7 | 8 | # Pause a current execution context for a given amount of time. 9 | Sleep = \(ctx Context, milliseconds number) none { 10 | context'Inner(ctx).Sleep(milliseconds) 11 | } 12 | -------------------------------------------------------------------------------- /packages/os/Udp/Datagram.pen: -------------------------------------------------------------------------------- 1 | # UDP datagram 2 | type Datagram { 3 | Data string 4 | Address string 5 | } 6 | 7 | foreign "c" _pen_os_udp_datagram_to_any = \(d Datagram) any { d } 8 | -------------------------------------------------------------------------------- /packages/os/Udp/Socket.pen: -------------------------------------------------------------------------------- 1 | # UDP socket 2 | type Socket { 3 | inner any 4 | } 5 | 6 | foreign "c" _pen_os_udp_socket_to_any = \(s Socket) any { s } 7 | -------------------------------------------------------------------------------- /packages/os/ffi/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | rustflags = ["-A", "improper_ctypes", "-D", "improper_ctypes_definitions"] 3 | 4 | [target.i686-unknown-linux-musl] 5 | linker = "rust-lld" 6 | 7 | [target.x86_64-unknown-linux-musl] 8 | linker = "rust-lld" 9 | 10 | [target.aarch64-unknown-linux-musl] 11 | linker = "rust-lld" 12 | -------------------------------------------------------------------------------- /packages/os/ffi/application/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "os-app" 3 | version = "0.1.0" 4 | publish = false 5 | edition = "2021" 6 | 7 | [dependencies] 8 | async-stream = "0.3" 9 | ffi = { package = "pen-ffi", version = "*", features = ["runtime"] } 10 | futures = "0.3" 11 | tokio = { "version" = "1", features = ["full"] } 12 | -------------------------------------------------------------------------------- /packages/os/ffi/application/rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "stable" 3 | targets = [ 4 | "i686-unknown-linux-musl", 5 | "x86_64-unknown-linux-musl", 6 | "aarch64-unknown-linux-musl", 7 | "wasm32-wasip2", 8 | ] 9 | -------------------------------------------------------------------------------- /packages/os/ffi/application/src/debug.rs: -------------------------------------------------------------------------------- 1 | use std::str; 2 | 3 | #[ffi::bindgen] 4 | pub fn _pen_debug(message: ffi::ByteString) { 5 | eprintln!( 6 | "{}", 7 | str::from_utf8(message.as_slice()).unwrap_or("failed to decode debug message") 8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /packages/os/ffi/application/src/main.rs: -------------------------------------------------------------------------------- 1 | mod concurrency; 2 | mod debug; 3 | mod heap; 4 | mod unreachable; 5 | mod utilities; 6 | 7 | use std::time::Duration; 8 | use tokio::time::sleep; 9 | 10 | ffi::import!(_pen_main, async fn() -> ffi::None); 11 | 12 | #[tokio::main] 13 | async fn main() { 14 | _pen_main().await; 15 | 16 | // HACK Wait for all I/O buffers to be flushed (hopefully.) 17 | sleep(Duration::from_millis(50)).await; 18 | } 19 | -------------------------------------------------------------------------------- /packages/os/ffi/application/src/unreachable.rs: -------------------------------------------------------------------------------- 1 | #[no_mangle] 2 | pub extern "C" fn _pen_unreachable() { 3 | unreachable!("PEN_OS_UNREACHABLE_ERROR") 4 | } 5 | -------------------------------------------------------------------------------- /packages/os/ffi/application/src/utilities.rs: -------------------------------------------------------------------------------- 1 | use std::{env, sync::LazyLock}; 2 | 3 | static OS_DEBUG: LazyLock = LazyLock::new(|| env::var("PEN_OS_DEBUG").is_ok()); 4 | 5 | pub fn is_os_debug() -> bool { 6 | *OS_DEBUG 7 | } 8 | -------------------------------------------------------------------------------- /packages/os/ffi/library/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "os" 3 | version = "0.1.0" 4 | publish = false 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["staticlib"] 9 | 10 | [dependencies] 11 | ffi = { package = "pen-ffi", version = "*", features = ["runtime", "std"] } 12 | futures = "0.3" 13 | tokio = { version = "1", features = ["full"] } 14 | -------------------------------------------------------------------------------- /packages/os/ffi/library/rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "stable" 3 | targets = [ 4 | "i686-unknown-linux-musl", 5 | "x86_64-unknown-linux-musl", 6 | "aarch64-unknown-linux-musl", 7 | "wasm32-wasip2", 8 | ] 9 | -------------------------------------------------------------------------------- /packages/os/ffi/library/src/argument.rs: -------------------------------------------------------------------------------- 1 | #[ffi::bindgen] 2 | fn _pen_os_get_arguments() -> ffi::List { 3 | std::env::args() 4 | .skip(1) 5 | .map(ffi::ByteString::from) 6 | .collect::>() 7 | .into() 8 | } 9 | -------------------------------------------------------------------------------- /packages/os/ffi/library/src/environment_variable.rs: -------------------------------------------------------------------------------- 1 | use std::{error::Error, ffi::OsString, str}; 2 | 3 | #[ffi::bindgen] 4 | fn _pen_os_get_environment_variable( 5 | name: ffi::ByteString, 6 | ) -> Result> { 7 | Ok(std::env::var(OsString::from(str::from_utf8(name.as_slice())?))?.into()) 8 | } 9 | -------------------------------------------------------------------------------- /packages/os/ffi/library/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod argument; 2 | mod directory; 3 | mod environment_variable; 4 | mod error; 5 | mod file; 6 | mod open_file_options; 7 | mod process; 8 | mod stdio; 9 | mod tcp; 10 | mod time; 11 | mod udp; 12 | mod utilities; 13 | -------------------------------------------------------------------------------- /packages/os/ffi/library/src/time.rs: -------------------------------------------------------------------------------- 1 | use std::time::{Duration, SystemTime, UNIX_EPOCH}; 2 | use tokio::time::sleep; 3 | 4 | #[ffi::bindgen] 5 | fn _pen_os_get_time() -> ffi::Number { 6 | (SystemTime::now() 7 | .duration_since(UNIX_EPOCH) 8 | .unwrap_or_else(|error| error.duration()) 9 | .as_millis() as f64) 10 | .into() 11 | } 12 | 13 | #[ffi::bindgen] 14 | async fn _pen_os_sleep(milliseconds: ffi::Number) { 15 | sleep(Duration::from_millis(f64::from(milliseconds) as u64)).await; 16 | } 17 | -------------------------------------------------------------------------------- /packages/os/normalFile.pen: -------------------------------------------------------------------------------- 1 | type NormalFile { 2 | inner any 3 | } 4 | 5 | foreign "c" _pen_os_file_to_any = \(f NormalFile) any { f } 6 | -------------------------------------------------------------------------------- /packages/os/pen-ffi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | while getopts t: option; do 6 | case $option in 7 | t) 8 | target=$OPTARG 9 | ;; 10 | esac 11 | done 12 | 13 | shift $(expr $OPTIND - 1) 14 | 15 | if [ -z $target ]; then 16 | exit 1 17 | fi 18 | 19 | cd $(dirname $0)/ffi/library 20 | cargo build --release --quiet --target $target 21 | # spell-checker: disable-next-line 22 | cp target/$target/release/libos.a $1 23 | -------------------------------------------------------------------------------- /packages/os/pen.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "system", 3 | "dependencies": {} 4 | } 5 | -------------------------------------------------------------------------------- /packages/prelude/Hash.pen: -------------------------------------------------------------------------------- 1 | import foreign "c" _pen_prelude_combine_hashes \(number, number) number 2 | import foreign "c" _pen_prelude_hash_number \(number) number 3 | import foreign "c" _pen_prelude_hash_string \(string) number 4 | 5 | CombineHashes = \(x number, y number) number { 6 | _pen_prelude_combine_hashes(x, y) 7 | } 8 | 9 | HashNumber = \(x number) number { 10 | _pen_prelude_hash_number(x) 11 | } 12 | 13 | HashString = \(x string) number { 14 | _pen_prelude_hash_string(x) 15 | } 16 | -------------------------------------------------------------------------------- /packages/prelude/Number.pen: -------------------------------------------------------------------------------- 1 | import foreign "c" _pen_prelude_debug_number \(number) string 2 | 3 | DebugNumber = \(n number) string { 4 | _pen_prelude_debug_number(n) 5 | } 6 | -------------------------------------------------------------------------------- /packages/prelude/String.pen: -------------------------------------------------------------------------------- 1 | import foreign "c" _pen_prelude_equal_strings \(string, string) boolean 2 | 3 | EqualStrings = \(x string, y string) boolean { 4 | _pen_prelude_equal_strings(x, y) 5 | } 6 | -------------------------------------------------------------------------------- /packages/prelude/ffi/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "prelude" 3 | version = "0.1.0" 4 | publish = false 5 | edition = "2021" 6 | 7 | [lib] 8 | name = "prelude" 9 | crate-type = ["staticlib"] 10 | 11 | [dependencies] 12 | ffi = { package = "pen-ffi", "version" = "*" } 13 | siphasher = "1.0" 14 | -------------------------------------------------------------------------------- /packages/prelude/ffi/rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "stable" 3 | targets = [ 4 | "i686-unknown-linux-musl", 5 | "x86_64-unknown-linux-musl", 6 | "aarch64-unknown-linux-musl", 7 | "wasm32-wasip2", 8 | ] 9 | -------------------------------------------------------------------------------- /packages/prelude/pen-ffi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | while getopts t: option; do 6 | case $option in 7 | t) 8 | target=$OPTARG 9 | ;; 10 | esac 11 | done 12 | 13 | shift $(expr $OPTIND - 1) 14 | 15 | if [ -z $target ]; then 16 | exit 1 17 | fi 18 | 19 | cd $(dirname $0)/ffi 20 | cargo build --release --quiet --target $target 21 | # spell-checker: disable-next-line 22 | cp target/$target/release/libprelude.a $1 23 | -------------------------------------------------------------------------------- /packages/prelude/pen.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "library", 3 | "dependencies": {} 4 | } 5 | -------------------------------------------------------------------------------- /packages/random/Context.pen: -------------------------------------------------------------------------------- 1 | import 'Context'context 2 | 3 | # A context of random number generation. 4 | type Context = context'Context 5 | 6 | UnsafeNew = \() Context { 7 | context'UnsafeNew() 8 | } 9 | -------------------------------------------------------------------------------- /packages/random/Context/context.pen: -------------------------------------------------------------------------------- 1 | import foreign "c" _pen_random_number \() number 2 | 3 | type Context { 4 | inner InnerContext 5 | } 6 | 7 | type InnerContext { 8 | Number \() number 9 | } 10 | 11 | UnsafeNew = \() Context { 12 | Context{ 13 | inner: InnerContext{ 14 | Number: _pen_random_number, 15 | }, 16 | } 17 | } 18 | 19 | Inner = \(ctx Context) InnerContext { 20 | ctx.inner 21 | } 22 | -------------------------------------------------------------------------------- /packages/random/Random.pen: -------------------------------------------------------------------------------- 1 | import 'Context'context { Context } 2 | 3 | # Generate a random number in a range of [0, 1). 4 | Number = \(ctx Context) number { 5 | context'Inner(ctx).Number() 6 | } 7 | -------------------------------------------------------------------------------- /packages/random/ffi/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "random" 3 | version = "0.1.0" 4 | publish = false 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["staticlib"] 9 | 10 | [dependencies] 11 | ffi = { package = "pen-ffi", version = "*" } 12 | rand = "0.9" 13 | -------------------------------------------------------------------------------- /packages/random/ffi/src/lib.rs: -------------------------------------------------------------------------------- 1 | use rand::random; 2 | 3 | #[ffi::bindgen] 4 | fn _pen_random_number() -> ffi::Number { 5 | random::().into() 6 | } 7 | -------------------------------------------------------------------------------- /packages/random/pen-ffi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | while getopts t: option; do 6 | case $option in 7 | t) 8 | target=$OPTARG 9 | ;; 10 | esac 11 | done 12 | 13 | shift $(expr $OPTIND - 1) 14 | 15 | if [ -z $target ]; then 16 | exit 1 17 | fi 18 | 19 | cd $(dirname $0)/ffi 20 | cargo build --release --quiet --target $target 21 | # spell-checker: disable-next-line 22 | cp target/$target/release/librandom.a $1 23 | -------------------------------------------------------------------------------- /packages/random/pen.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "system", 3 | "dependencies": {} 4 | } 5 | -------------------------------------------------------------------------------- /packages/reflect/Any.pen: -------------------------------------------------------------------------------- 1 | # This module provides utility functions for the `any` type. 2 | 3 | # Pretty-print a value. 4 | Debug = \(x any) string { 5 | _reflect_debug(x) 6 | } 7 | 8 | # Check if values are equal. It returns `none` if values are not comparable. 9 | Equal = \(x any, y any) boolean | none { 10 | _reflect_equal(x, y) 11 | } 12 | -------------------------------------------------------------------------------- /packages/reflect/pen.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "library", 3 | "dependencies": {} 4 | } 5 | -------------------------------------------------------------------------------- /packages/regex/ffi/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ffi" 3 | version = "0.1.0" 4 | publish = false 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["staticlib"] 9 | 10 | [dependencies] 11 | ffi = { package = "pen-ffi", version = "*", features = ["std"] } 12 | regex = "1" 13 | -------------------------------------------------------------------------------- /packages/regex/pen-ffi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | while getopts t: option; do 6 | case $option in 7 | t) 8 | target=$OPTARG 9 | ;; 10 | esac 11 | done 12 | 13 | shift $(expr $OPTIND - 1) 14 | 15 | if [ -z $target ]; then 16 | exit 1 17 | fi 18 | 19 | cd $(dirname $0)/ffi 20 | cargo build --release --quiet --target $target 21 | # spell-checker: disable-next-line 22 | cp target/$target/release/libffi.a $1 23 | -------------------------------------------------------------------------------- /packages/regex/pen.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "library", 3 | "dependencies": { 4 | "Test": "pen:///test" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/sql/Context.pen: -------------------------------------------------------------------------------- 1 | import 'Context'context 2 | 3 | # A SQL database context 4 | type Context = context'Context 5 | 6 | UnsafeNew = \() Context { context'New() } 7 | -------------------------------------------------------------------------------- /packages/sql/Pool/Options.pen: -------------------------------------------------------------------------------- 1 | # Connection pool options 2 | type Options { 3 | MinConnections number 4 | MaxConnections number 5 | ConnectTimeout number 6 | } 7 | -------------------------------------------------------------------------------- /packages/sql/Pool/pool.pen: -------------------------------------------------------------------------------- 1 | type Pool { 2 | inner any 3 | } 4 | 5 | foreign "c" _pen_sql_pool_to_any = \(p Pool) any { p } 6 | -------------------------------------------------------------------------------- /packages/sql/Value.pen: -------------------------------------------------------------------------------- 1 | # A value in a column 2 | type Value = boolean | none | number | string 3 | -------------------------------------------------------------------------------- /packages/sql/ffi/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pen-sql" 3 | version = "0.1.0" 4 | publish = false 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["staticlib"] 9 | 10 | [dependencies] 11 | ffi = { package = "pen-ffi", version = "*", features = ["runtime", "std"] } 12 | futures = "0.3" 13 | sqlx = { version = "0.6", features = [ 14 | "any", 15 | "mysql", 16 | "postgres", 17 | "sqlite", 18 | "runtime-tokio-rustls", 19 | ] } 20 | -------------------------------------------------------------------------------- /packages/sql/ffi/rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "stable" 3 | targets = [ 4 | "i686-unknown-linux-musl", 5 | "x86_64-unknown-linux-musl", 6 | "aarch64-unknown-linux-musl", 7 | "wasm32-wasip2", 8 | ] 9 | -------------------------------------------------------------------------------- /packages/sql/ffi/src/error.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | error::Error, 3 | fmt::{self, Display, Formatter}, 4 | }; 5 | 6 | #[derive(Clone, Debug)] 7 | pub enum SqlError { 8 | TypeNotSupported, 9 | } 10 | 11 | impl Display for SqlError { 12 | fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { 13 | match self { 14 | Self::TypeNotSupported => write!(formatter, "type not supported"), 15 | } 16 | } 17 | } 18 | 19 | impl Error for SqlError {} 20 | -------------------------------------------------------------------------------- /packages/sql/ffi/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod error; 2 | mod pool; 3 | -------------------------------------------------------------------------------- /packages/sql/pen-ffi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | while getopts t: option; do 6 | case $option in 7 | t) 8 | target=$OPTARG 9 | ;; 10 | esac 11 | done 12 | 13 | shift $(expr $OPTIND - 1) 14 | 15 | if [ -z $target ]; then 16 | exit 1 17 | fi 18 | 19 | cd $(dirname $0)/ffi 20 | cargo build --release --quiet --target $target 21 | # spell-checker: disable-next-line 22 | cp target/$target/release/libpen_sql.a $1 23 | -------------------------------------------------------------------------------- /packages/sql/pen.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "system", 3 | "dependencies": {} 4 | } 5 | -------------------------------------------------------------------------------- /packages/test/ffi/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test" 3 | version = "0.1.0" 4 | publish = false 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["staticlib"] 9 | 10 | [dependencies] 11 | crossbeam = "0.8.4" 12 | ffi = { package = "pen-ffi", version = "*" } 13 | -------------------------------------------------------------------------------- /packages/test/pen-ffi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | while getopts t: option; do 6 | case $option in 7 | t) 8 | target=$OPTARG 9 | ;; 10 | esac 11 | done 12 | 13 | shift $(expr $OPTIND - 1) 14 | 15 | if [ -z $target ]; then 16 | exit 1 17 | fi 18 | 19 | cd $(dirname $0)/ffi 20 | cargo build --release --quiet --target $target 21 | # spell-checker: disable-next-line 22 | cp target/$target/release/libtest.a $1 23 | -------------------------------------------------------------------------------- /packages/test/pen.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "library", 3 | "dependencies": { 4 | "Reflect": "pen:///reflect" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "stable" 3 | components = ["clippy", "rustfmt", "rust-analyzer", "rust-src"] 4 | targets = [ 5 | "i686-unknown-linux-musl", 6 | "x86_64-unknown-linux-musl", 7 | "aarch64-unknown-linux-musl", 8 | "wasm32-wasip2", 9 | ] 10 | -------------------------------------------------------------------------------- /test/prelude/function.test.pen: -------------------------------------------------------------------------------- 1 | nestedFunction = \(x number) \() number { 2 | \() number { 3 | if x == 0 { 4 | 0 5 | } else { 6 | # This should have no effect. But it gets into an infinite loop 7 | # when it's actually calling the innermost closure due to a compiler bug! 8 | nestedFunction(x - 1) 9 | 10 | 0 11 | } 12 | } 13 | } 14 | 15 | CallNestedFunction = \() none | error { 16 | nestedFunction(1)() 17 | 18 | none 19 | } 20 | 21 | functionWithShadowedVariable = \() [none] { 22 | x = [none] 23 | 24 | [none \(x none) none { x }(none)] 25 | } 26 | 27 | ShadowVariableByClosureInListComprehension = \() none | error { 28 | functionWithShadowedVariable() 29 | 30 | none 31 | } 32 | -------------------------------------------------------------------------------- /test/prelude/pen.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "library", 3 | "dependencies": { 4 | "Core": "pen:///core", 5 | "Test": "pen:///test" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/prelude/record.test.pen: -------------------------------------------------------------------------------- 1 | import Test'Assert 2 | 3 | type foo {} 4 | 5 | type bar { 6 | x number 7 | } 8 | 9 | type baz { 10 | x number 11 | y string 12 | } 13 | 14 | EqualRecord = \() none | error { 15 | Assert'True(foo{} == foo{})? 16 | Assert'True(bar{x: 0} == bar{x: 0})? 17 | Assert'True(bar{x: 0} != bar{x: 1})? 18 | Assert'True(baz{x: 0, y: "foo"} == baz{x: 0, y: "foo"})? 19 | Assert'True(baz{x: 0, y: "foo"} != baz{x: 1, y: "foo"})? 20 | Assert'True(baz{x: 0, y: "foo"} != baz{x: 0, y: "bar"})? 21 | } 22 | -------------------------------------------------------------------------------- /test/prelude/record/qualifiedImport.pen: -------------------------------------------------------------------------------- 1 | import 'record'record 2 | 3 | f = \() record'Foo { record'Foo{} } 4 | -------------------------------------------------------------------------------- /test/prelude/record/record.pen: -------------------------------------------------------------------------------- 1 | type Foo {} 2 | -------------------------------------------------------------------------------- /test/prelude/record/unqualifiedImport.pen: -------------------------------------------------------------------------------- 1 | import 'record'record { Foo } 2 | 3 | f = \() Foo { Foo{} } 4 | -------------------------------------------------------------------------------- /test/prelude/string.test.pen: -------------------------------------------------------------------------------- 1 | import Core'Number 2 | import Test'Assert 3 | 4 | CompareStrings = \() none | error { 5 | Assert'Equal("foo", "foo") 6 | } 7 | 8 | CompareDifferentStrings = \() none | error { 9 | Assert'True("foo" != "bar") 10 | } 11 | -------------------------------------------------------------------------------- /test/prelude/union.test.pen: -------------------------------------------------------------------------------- 1 | import Test'Assert 2 | 3 | DowncastUnionToList = \() none | error { 4 | x = if true { [none] } else { none } 5 | 6 | Assert'True( 7 | if x = x as [none] { true } else if none { false }, 8 | ) 9 | } 10 | 11 | DowncastUnionToMap = \() none | error { 12 | x = if true { {none: none} } else { none } 13 | 14 | Assert'True( 15 | if x = x as {none: none} { true } else if none { false }, 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /test/reflect/pen.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "library", 3 | "dependencies": { 4 | "Reflect": "pen:///reflect", 5 | "Test": "pen:///test" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tools/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | cargo build --release --locked 6 | -------------------------------------------------------------------------------- /tools/build_asset.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | version=$($(dirname $0)/version.sh) 6 | target=$(rustc -vV | grep host: | sed 's/host: //') 7 | tarball=pen-$version-$target.tar.xz 8 | 9 | cd $(dirname $0)/.. 10 | 11 | $(dirname $0)/build.sh 12 | 13 | tar caf $tarball \ 14 | README.md LICENSE-MIT LICENSE-APACHE \ 15 | cmd doc lib rust-toolchain.toml target/release/pen 16 | 17 | echo $tarball 18 | -------------------------------------------------------------------------------- /tools/check_memory_leak.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -ex 4 | 5 | . $(dirname $0)/utilities.sh 6 | 7 | if ! which valgrind; then 8 | "$@" 9 | exit 10 | fi 11 | 12 | valgrind --log-file=valgrind.log "$@" 13 | 14 | if ! test_valgrind_log valgrind.log; then 15 | cat valgrind.log 16 | exit 1 17 | fi 18 | -------------------------------------------------------------------------------- /tools/ci/github/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | llvm_version=16 6 | 7 | brew install jq llvm@$llvm_version ninja sccache 8 | 9 | if [ $(uname) = Linux ]; then 10 | brew install python 11 | fi 12 | 13 | llvm_prefix=$(brew --prefix)/opt/llvm@$llvm_version 14 | 15 | echo LLVM_SYS_${llvm_version}0_PREFIX=$llvm_prefix >>$GITHUB_ENV 16 | echo PATH=$llvm_prefix/bin:$PATH >>$GITHUB_ENV 17 | 18 | if [ $(uname) = Linux ]; then 19 | sudo apt update --fix-missing 20 | sudo apt install libc6-dbg # for valgrind 21 | brew install valgrind 22 | else 23 | echo LIBRARY_PATH=$(brew --prefix)/lib:$LIBRARY_PATH >>$GITHUB_ENV 24 | fi 25 | -------------------------------------------------------------------------------- /tools/ci/github/setup/action.yaml: -------------------------------------------------------------------------------- 1 | name: pen/setup 2 | description: Sets up a build environment 3 | inputs: {} 4 | outputs: {} 5 | runs: 6 | using: composite 7 | steps: 8 | - uses: ruby/setup-ruby@v1 9 | - uses: swatinem/rust-cache@v2 10 | - uses: homebrew/actions/setup-homebrew@master 11 | with: 12 | stable: true 13 | - run: tools/ci/github/setup.sh 14 | shell: bash 15 | -------------------------------------------------------------------------------- /tools/coverage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | rustup component add llvm-tools-preview 6 | 7 | cargo install cargo-llvm-cov 8 | cargo llvm-cov --workspace --lcov --output-path lcov.info 9 | -------------------------------------------------------------------------------- /tools/document/serve.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | $(dirname $0)/build.sh 6 | 7 | cd doc 8 | mkdocs serve "$@" 9 | -------------------------------------------------------------------------------- /tools/format.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | . $(dirname $0)/utilities.sh 6 | 7 | # spell-checker: disable-next-line 8 | install_nightly_component rustfmt 9 | 10 | $(dirname $0)/run_all_crates.sh rustup run nightly cargo fmt "$@" 11 | -------------------------------------------------------------------------------- /tools/integration_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | . $(dirname $0)/utilities.sh 6 | 7 | prepare_integration_test $(dirname $PWD/$0)/.. 8 | 9 | for target in \ 10 | i686-unknown-linux-musl \ 11 | x86_64-unknown-linux-musl \ 12 | aarch64-unknown-linux-musl \ 13 | wasm32-wasip2; do 14 | rustup target add $target 15 | done 16 | 17 | bundler install 18 | 19 | cd $(dirname $0)/.. 20 | 21 | cucumber --publish-quiet --strict-undefined "$@" 22 | -------------------------------------------------------------------------------- /tools/lint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | while getopts f option; do 6 | case $option in 7 | f) 8 | options=--fix 9 | ;; 10 | esac 11 | done 12 | 13 | shift $(expr $OPTIND - 1) 14 | 15 | $(dirname $0)/run_all_crates.sh \ 16 | cargo clippy $options --all-features -- \ 17 | -D clippy::mod_module_files \ 18 | -D clippy::use_self \ 19 | "$@" 20 | -------------------------------------------------------------------------------- /tools/run_all_crates.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -ex 4 | 5 | if [ $# -eq 0 ]; then 6 | exit 1 7 | fi 8 | 9 | cd $(dirname $0)/.. 10 | 11 | for file in $(git ls-files Cargo.lock '**/Cargo.lock'); do 12 | ( 13 | cd $(dirname $file) 14 | "$@" 15 | ) 16 | done 17 | -------------------------------------------------------------------------------- /tools/unit_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -ex 4 | 5 | $(dirname $0)/run_all_crates.sh cargo test "$@" 6 | -------------------------------------------------------------------------------- /tools/unused_dependencies.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | . $(dirname $0)/utilities.sh 6 | 7 | cargo install cargo-machete 8 | 9 | $(dirname $0)/run_all_crates.sh cargo machete 10 | -------------------------------------------------------------------------------- /tools/update_dependencies.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | $(dirname $0)/run_all_crates.sh cargo update 6 | -------------------------------------------------------------------------------- /tools/utilities.sh: -------------------------------------------------------------------------------- 1 | test_valgrind_log() { 2 | grep 'All heap blocks were freed -- no leaks are possible' $1 || 3 | ( 4 | grep 'definitely lost: 0 bytes in 0 blocks' $1 && 5 | grep 'indirectly lost: 0 bytes in 0 blocks' $1 && 6 | grep '0 errors from 0 contexts' $1 7 | ) 8 | } 9 | 10 | install_nightly_component() { 11 | rustup install nightly 12 | rustup component add --toolchain nightly $1 13 | } 14 | 15 | prepare_integration_test() { 16 | directory=$1 17 | 18 | export PATH=$directory/target/release:$directory/tools:$PATH 19 | export RUSTC_WRAPPER=sccache 20 | export PEN_ROOT=$directory 21 | 22 | cargo install turtle-build 23 | } 24 | -------------------------------------------------------------------------------- /tools/version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | cat $(dirname $0)/../cmd/pen/Cargo.toml | grep '^version = ' | cut -f 2 -d '"' 6 | --------------------------------------------------------------------------------