├── .circleci └── config.yml ├── .cirrus.yml ├── .dockerignore ├── .github └── workflows │ ├── ci.yaml │ └── release-notify.yaml ├── .gitignore ├── .lldbinit ├── CODE_OF_CONDUCT.md ├── Dockerfile ├── GOALS.md ├── LICENSE ├── LICENSE-crystal ├── LICENSE-pony ├── Makefile ├── README.md ├── assets └── savi-logo-rect.png ├── bin └── .gitkeep ├── core ├── Any.savi ├── Array.savi ├── AsioEvent.savi ├── BinaryReadable.savi ├── BitArray.savi ├── Bool.savi ├── Bytes.Format.savi ├── Bytes.savi ├── CPointer.savi ├── Comparable.savi ├── Env.Root.TicketIssuer.savi ├── Env.savi ├── Equatable.savi ├── FloatingPoint.Format.savi ├── FloatingPoint.savi ├── Indexable.savi ├── InhibitOptimization.savi ├── Inspect.savi ├── Integer.Format.savi ├── Integer.savi ├── IntoString.savi ├── None.savi ├── Numeric.savi ├── Pair.savi ├── Platform.savi ├── Reflection.savi ├── SourceCodePos.savi ├── String.DecodeUTF8.savi ├── String.Format.savi ├── String.savi ├── TraceData.savi ├── U64.BCD.savi ├── _EmptyConstructable.savi ├── _FFI.savi ├── _Ryu.F64.savi ├── _Unsafe.MultiByteAccess.savi ├── _Unsafe.RapidHash.savi └── declarators │ ├── declarators.savi │ └── meta │ └── meta_declarators.savi ├── docker └── make ├── docs └── intro-vs-pony.md ├── examples ├── adventofcode │ └── 2018 │ │ ├── manifest.savi │ │ └── src │ │ ├── day_1.savi │ │ ├── day_2.savi │ │ ├── day_3.savi │ │ ├── day_4.savi │ │ ├── day_5.savi │ │ └── main.savi ├── bar │ ├── manifest.savi │ ├── spec │ │ ├── Bar.Spec.savi │ │ └── Main.savi │ └── src │ │ └── Bar.savi ├── foo │ ├── manifest.savi │ └── src │ │ └── Main.savi ├── projecteuler │ └── problem_1 │ │ ├── manifest.savi │ │ └── src │ │ └── main.savi └── verona │ ├── manifest.savi │ └── src │ └── main.savi ├── flake.lock ├── flake.nix ├── lib ├── .shards.info ├── capnproto │ ├── .gitignore │ ├── README.md │ ├── lib │ ├── shard.yml │ ├── spec │ │ ├── pointer_struct_list_spec.cr │ │ ├── pointer_struct_spec.cr │ │ ├── pointer_u8_list_spec.cr │ │ ├── segment_spec.cr │ │ └── spec_helper.cr │ └── src │ │ ├── CapnProto.Meta.capnp │ │ ├── CapnProto.Savi.Meta.capnp │ │ ├── capnpc-crystal │ │ ├── gen.cr │ │ └── main.cr │ │ ├── capnproto.cr │ │ └── capnproto │ │ ├── list.cr │ │ ├── meta.cr │ │ ├── pointer_far.cr │ │ ├── pointer_struct.cr │ │ ├── pointer_struct_list.cr │ │ ├── pointer_u8_list.cr │ │ └── segment.cr ├── clang │ ├── LICENSE │ ├── Makefile │ ├── README.md │ ├── lib │ ├── samples │ │ ├── c2cr.cr │ │ ├── c2cr │ │ │ ├── constant.cr │ │ │ ├── parser.cr │ │ │ └── type.cr │ │ └── debug.cr │ ├── shard.yml │ └── src │ │ ├── clang-c │ │ ├── BuildSystem.cr │ │ ├── CXErrorCode.cr │ │ ├── CXString.cr │ │ ├── Documentation.cr │ │ ├── Index.cr │ │ └── Platform.cr │ │ ├── clang.cr │ │ ├── cursor.cr │ │ ├── cursor_kind.cr │ │ ├── eval_result.cr │ │ ├── file.cr │ │ ├── index.cr │ │ ├── lib_clang.cr │ │ ├── macros.cr │ │ ├── platform_availability.cr │ │ ├── printing_policy.cr │ │ ├── source_location.cr │ │ ├── token.cr │ │ ├── translation_unit.cr │ │ ├── type.cr │ │ ├── type_kind.cr │ │ └── unsaved_file.cr ├── clim │ ├── .github │ │ └── workflows │ │ │ ├── format.yml │ │ │ ├── spec1.yml │ │ │ ├── spec2.yml │ │ │ ├── spec3.yml │ │ │ ├── spec4.yml │ │ │ └── spec5.yml │ ├── .gitignore │ ├── .vscode │ │ └── tasks.json │ ├── LICENSE │ ├── Makefile │ ├── README.md │ ├── lib │ ├── shard.yml │ ├── spec │ │ ├── clim │ │ │ ├── command │ │ │ │ ├── arguments_spec.cr │ │ │ │ ├── options_spec.cr │ │ │ │ └── sub_commands_spec.cr │ │ │ ├── command_spec.cr │ │ │ ├── compile_time_error_spec │ │ │ │ ├── compile_time_error_spec.cr │ │ │ │ ├── files │ │ │ │ │ ├── bool_with_required_true.cr │ │ │ │ │ ├── duplicate_argument_name_main.cr │ │ │ │ │ ├── duplicate_argument_name_sub.cr │ │ │ │ │ ├── duplicate_argument_name_sub_2.cr │ │ │ │ │ ├── duplicate_argument_name_sub_sub.cr │ │ │ │ │ ├── duplicate_main.cr │ │ │ │ │ ├── duplicate_main_in_sub_command.cr │ │ │ │ │ ├── duplicate_sub_command.cr │ │ │ │ │ ├── empty_argument_name.cr │ │ │ │ │ ├── empty_option_name.cr │ │ │ │ │ ├── exception_message.cr │ │ │ │ │ ├── help_directive_does_not_have_a_short_argument_for_main.cr │ │ │ │ │ ├── main_default_help.cr │ │ │ │ │ ├── main_is_not_defined.cr │ │ │ │ │ ├── main_with_alias_name.cr │ │ │ │ │ ├── main_with_help_template.cr │ │ │ │ │ ├── main_with_help_template2.cr │ │ │ │ │ ├── main_without_run_block.cr │ │ │ │ │ ├── not_supported_argument_type.cr │ │ │ │ │ ├── not_supported_option_type.cr │ │ │ │ │ ├── run_block_execution.cr │ │ │ │ │ ├── sub_2_command_without_run_block.cr │ │ │ │ │ ├── sub_command_with_help_template.cr │ │ │ │ │ ├── sub_command_without_run_block.cr │ │ │ │ │ └── sub_sub_command_without_run_block.cr │ │ │ │ └── stdout_spec.cr │ │ │ ├── dsl_spec.cr │ │ │ ├── dsl_spec │ │ │ │ ├── arguments │ │ │ │ │ ├── default_spec.cr │ │ │ │ │ ├── required_spec.cr │ │ │ │ │ └── types │ │ │ │ │ │ ├── bool_spec.cr │ │ │ │ │ │ ├── float_spec.cr │ │ │ │ │ │ ├── int_spec.cr │ │ │ │ │ │ ├── string_spec.cr │ │ │ │ │ │ └── uint_spec.cr │ │ │ │ ├── help │ │ │ │ │ └── help_spec.cr │ │ │ │ ├── main_command │ │ │ │ │ ├── main_command_spec.cr │ │ │ │ │ └── main_spec.cr │ │ │ │ ├── options │ │ │ │ │ ├── array_01_spec.cr │ │ │ │ │ ├── array_02_spec.cr │ │ │ │ │ ├── bool_01_spec.cr │ │ │ │ │ ├── bool_02_spec.cr │ │ │ │ │ ├── bool_03_spec.cr │ │ │ │ │ ├── bool_04_spec.cr │ │ │ │ │ ├── bool_05_spec.cr │ │ │ │ │ ├── bool_06_spec.cr │ │ │ │ │ ├── bool_07_spec.cr │ │ │ │ │ ├── bool_08_spec.cr │ │ │ │ │ ├── bool_09_spec.cr │ │ │ │ │ ├── bool_10_spec.cr │ │ │ │ │ ├── bool_11_spec.cr │ │ │ │ │ ├── bool_12_spec.cr │ │ │ │ │ ├── bool_13_spec.cr │ │ │ │ │ ├── bool_14_spec.cr │ │ │ │ │ ├── bool_15_spec.cr │ │ │ │ │ ├── string_01_spec.cr │ │ │ │ │ ├── string_02_spec.cr │ │ │ │ │ ├── string_03_spec.cr │ │ │ │ │ ├── string_04_spec.cr │ │ │ │ │ ├── string_05_spec.cr │ │ │ │ │ ├── string_06_spec.cr │ │ │ │ │ ├── string_07_spec.cr │ │ │ │ │ ├── string_08_spec.cr │ │ │ │ │ ├── string_09_spec.cr │ │ │ │ │ └── types │ │ │ │ │ │ ├── array │ │ │ │ │ │ ├── float_spec.cr │ │ │ │ │ │ ├── int_spec.cr │ │ │ │ │ │ ├── string_spec.cr │ │ │ │ │ │ └── uint_spec.cr │ │ │ │ │ │ ├── bool_spec.cr │ │ │ │ │ │ ├── default_spec.cr │ │ │ │ │ │ ├── float_spec.cr │ │ │ │ │ │ ├── int_spec.cr │ │ │ │ │ │ ├── string_spec.cr │ │ │ │ │ │ └── uint_spec.cr │ │ │ │ └── sub_command │ │ │ │ │ ├── sub.cr │ │ │ │ │ ├── sub_01_01_a_spec.cr │ │ │ │ │ ├── sub_01_01_b_spec.cr │ │ │ │ │ ├── sub_01_02_spec.cr │ │ │ │ │ ├── sub_01_03_spec.cr │ │ │ │ │ ├── sub_02_01_spec.cr │ │ │ │ │ ├── sub_02_02_spec.cr │ │ │ │ │ ├── sub_02_03_spec.cr │ │ │ │ │ ├── sub_02_04_spec.cr │ │ │ │ │ ├── sub_02_05_spec.cr │ │ │ │ │ ├── sub_02_06_spec.cr │ │ │ │ │ ├── sub_command.cr │ │ │ │ │ ├── sub_command_01_01_spec.cr │ │ │ │ │ ├── sub_command_01_02_spec.cr │ │ │ │ │ ├── sub_command_01_03_spec.cr │ │ │ │ │ ├── sub_command_02_01_spec.cr │ │ │ │ │ ├── sub_command_02_02_spec.cr │ │ │ │ │ ├── sub_command_02_03_spec.cr │ │ │ │ │ ├── sub_command_03_01_spec.cr │ │ │ │ │ ├── sub_command_03_02_spec.cr │ │ │ │ │ ├── sub_command_03_03_spec.cr │ │ │ │ │ ├── sub_command_04_01_spec.cr │ │ │ │ │ ├── sub_command_04_02_spec.cr │ │ │ │ │ ├── sub_command_04_03_spec.cr │ │ │ │ │ ├── sub_command_alias.cr │ │ │ │ │ └── sub_command_raise_spec.cr │ │ │ ├── extension_spec.cr │ │ │ └── readme │ │ │ │ ├── files │ │ │ │ ├── alias_name.cr │ │ │ │ ├── argument.cr │ │ │ │ ├── fake-crystal-command.cr │ │ │ │ ├── hello.cr │ │ │ │ ├── help_short.cr │ │ │ │ ├── help_template.cr │ │ │ │ ├── io_in_run_block.cr │ │ │ │ ├── minimum.cr │ │ │ │ ├── version.cr │ │ │ │ └── version_short.cr │ │ │ │ └── readme_spec.cr │ │ ├── clim_spec.cr │ │ └── spec_helper.cr │ └── src │ │ ├── clim.cr │ │ └── clim │ │ ├── command.cr │ │ ├── command │ │ ├── arguments.cr │ │ ├── arguments │ │ │ └── argument.cr │ │ ├── options.cr │ │ ├── options │ │ │ └── option.cr │ │ └── sub_commands.cr │ │ ├── exception.cr │ │ ├── extension.cr │ │ ├── types.cr │ │ └── version.cr ├── lsp │ ├── lib │ ├── shard.yml │ ├── spec │ │ ├── codec_spec.cr │ │ ├── message_spec.cr │ │ ├── spec_helper.cr │ │ └── wire_spec.cr │ └── src │ │ ├── lsp.cr │ │ └── lsp │ │ ├── codec.cr │ │ ├── data.cr │ │ ├── data │ │ ├── client_capabilities.cr │ │ ├── code_action_kind_set.cr │ │ ├── command.cr │ │ ├── completion_context.cr │ │ ├── completion_item.cr │ │ ├── completion_item_kind.cr │ │ ├── completion_item_kind_set.cr │ │ ├── completion_list.cr │ │ ├── completion_trigger_kind.cr │ │ ├── diagnostic.cr │ │ ├── document_filter.cr │ │ ├── dynamic_registration.cr │ │ ├── formatting_options.cr │ │ ├── insert_text_format.cr │ │ ├── location.cr │ │ ├── markup_content.cr │ │ ├── message_action_item.cr │ │ ├── message_type.cr │ │ ├── parameter_information.cr │ │ ├── position.cr │ │ ├── range.cr │ │ ├── response_error.cr │ │ ├── server_capabilities.cr │ │ ├── signature_information.cr │ │ ├── symbol_kind.cr │ │ ├── symbol_kind_set.cr │ │ ├── text_document_client_capabilities.cr │ │ ├── text_document_content_change_event.cr │ │ ├── text_document_identifier.cr │ │ ├── text_document_item.cr │ │ ├── text_document_registration_options.cr │ │ ├── text_document_save_reason.cr │ │ ├── text_document_sync_kind.cr │ │ ├── text_document_versioned_identifier.cr │ │ ├── text_edit.cr │ │ ├── workspace_client_capabilities.cr │ │ └── workspace_folder.cr │ │ ├── json_util.cr │ │ ├── message.cr │ │ └── wire.cr └── pegmatite │ ├── .github │ └── workflows │ │ └── ci.yml │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── lib │ ├── shard.lock │ ├── shard.yml │ ├── spec │ ├── dynamics_spec.cr │ ├── fixtures │ │ ├── heredoc.cr │ │ └── json.cr │ ├── pegmatite_spec.cr │ └── spec_helper.cr │ └── src │ ├── pegmatite.cr │ └── pegmatite │ ├── dsl.cr │ ├── pattern.cr │ ├── pattern │ ├── choice.cr │ ├── dynamic_match.cr │ ├── dynamic_pop.cr │ ├── dynamic_push.cr │ ├── eof.cr │ ├── forward.cr │ ├── label.cr │ ├── literal.cr │ ├── not.cr │ ├── optional.cr │ ├── repeat.cr │ ├── sequence.cr │ ├── unicode_any.cr │ ├── unicode_char.cr │ └── unicode_range.cr │ ├── token.cr │ └── token_iterator.cr ├── main.cr ├── platform.sh ├── self-hosted ├── README.md ├── manifest.savi ├── spec │ ├── savi-lang-parse.sh │ └── savi-lang-parse │ │ ├── 01-parse-hello-world.savi │ │ ├── 01-parse-hello-world.savi.ast.yaml │ │ ├── 02-parse-example-class.savi │ │ ├── 02-parse-example-class.savi.ast.yaml │ │ ├── 03-parse-values.savi │ │ ├── 03-parse-values.savi.ast.yaml │ │ ├── 04-parse-operators.savi │ │ ├── 04-parse-operators.savi.ast.yaml │ │ ├── 05-parse-nested-bracket-strings.savi │ │ ├── 05-parse-nested-bracket-strings.savi.ast.yaml │ │ ├── 06-parse-groups.savi │ │ ├── 06-parse-groups.savi.ast.yaml │ │ ├── 07-parse-annotations.savi │ │ ├── 07-parse-annotations.savi.ast.yaml │ │ ├── 08-parse-yield-blocks.savi │ │ └── 08-parse-yield-blocks.savi.ast.yaml └── src │ ├── SaviProto │ ├── SaviProto.AST.capnp │ ├── SaviProto.AST.capnp.savi │ ├── SaviProto.Source.capnp │ └── SaviProto.Source.capnp.savi │ └── savi-lang-parse │ ├── Main.savi │ ├── _Error.savi │ ├── _FFI.savi │ ├── _Grammar.savi │ ├── _StringLiterals.savi │ ├── _Token.savi │ ├── _TokenPrinter.savi │ ├── _TreeBuilder.Data.savi │ ├── _TreeBuilder.savi │ └── _TreePrinter.savi ├── shard.lock ├── shard.yml ├── spec ├── all.cr ├── compiler │ ├── binary_verona_spec.cr │ ├── completeness.savi.spec.md │ ├── declarator.interpreter.savi.spec.md │ ├── ffigen.savi.spec.md │ ├── flow.savi.spec.md │ ├── format.brackets.savi.spec.md │ ├── format.commas.savi.spec.md │ ├── format.dots.savi.spec.md │ ├── format.indent.savi.spec.md │ ├── format.numbers.savi.spec.md │ ├── infer │ │ └── meta_type_spec.cr │ ├── local.savi.spec.md │ ├── macros │ │ ├── as_not_spec.cr │ │ ├── assertion_spec.cr │ │ ├── break_spec.cr │ │ ├── case_spec.cr │ │ ├── error_spec.cr │ │ ├── identity_digest_of_spec.cr │ │ ├── if_spec.cr │ │ ├── next_spec.cr │ │ ├── reflection_of_runtime_type_name_spec.cr │ │ ├── reflection_of_type_spec.cr │ │ ├── return_spec.cr │ │ ├── source_code_position_of_argument_spec.cr │ │ ├── stack_address_of_variable_spec.cr │ │ ├── static_address_of_function_spec.cr │ │ ├── try_spec.cr │ │ ├── while_spec.cr │ │ └── yield_spec.cr │ ├── namespace.savi.spec.md │ ├── namespace_spec.cr │ ├── populate_spec.cr │ ├── privacy.savi.spec.md │ ├── refer_spec.cr │ ├── refer_type_spec.cr │ ├── reparse_spec.cr │ ├── serve_definition_spec.cr │ ├── serve_hover_spec.cr │ ├── source_pos_spec.cr │ ├── sugar_spec.cr │ ├── t_infer │ │ └── meta_type_spec.cr │ ├── t_type_check.address.savi.spec.md │ ├── t_type_check.aliases.savi.spec.md │ ├── t_type_check.calls.savi.spec.md │ ├── t_type_check.completeness.savi.spec.md │ ├── t_type_check.constraints.savi.spec.md │ ├── t_type_check.errors.savi.spec.md │ ├── t_type_check.generics.savi.spec.md │ ├── t_type_check.identifiers.savi.spec.md │ ├── t_type_check.inference.savi.spec.md │ ├── t_type_check.match.savi.spec.md │ ├── t_type_check.subtyping.savi.spec.md │ ├── t_type_check.validation.savi.spec.md │ ├── t_type_check.viewpoints.savi.spec.md │ ├── t_type_check.yields.savi.spec.md │ ├── type_check.address.savi.spec.md │ ├── type_check.aliases.savi.spec.md │ ├── type_check.calls.savi.spec.md │ ├── type_check.completeness.savi.spec.md │ ├── type_check.constraints.savi.spec.md │ ├── type_check.errors.savi.spec.md │ ├── type_check.generics.savi.spec.md │ ├── type_check.identifiers.savi.spec.md │ ├── type_check.inference.savi.spec.md │ ├── type_check.match.savi.spec.md │ ├── type_check.subtyping.savi.spec.md │ ├── type_check.validation.savi.spec.md │ ├── type_check.viewpoints.savi.spec.md │ ├── type_check.yields.savi.spec.md │ ├── type_context_spec.cr │ ├── types_graph.savi.spec.md │ ├── verify.savi.spec.md │ ├── verify_spec.cr │ ├── xtypes.savi.spec.md │ ├── xtypes │ │ └── cap_spec.cr │ └── xtypes_graph.savi.spec.md ├── core │ ├── Array.Spec.savi │ ├── BitArray.Spec.savi │ ├── Bool.Spec.savi │ ├── Bytes.Format.Spec.savi │ ├── Bytes.Spec.savi │ ├── CPointer.Spec.savi │ ├── FloatingPoint.Arithmetic.Spec.savi │ ├── FloatingPoint.Bounded.Spec.savi │ ├── FloatingPoint.Comparable.Spec.savi │ ├── FloatingPoint.Format.Spec.savi │ ├── Indexable.Spec.savi │ ├── InhibitOptimization.Spec.savi │ ├── Inspect.Spec.savi │ ├── Integer.BitwiseArithmetic.Spec.savi │ ├── Integer.Countable.Spec.savi │ ├── Integer.Format.Spec.savi │ ├── Integer.SafeArithmetic.Spec.savi │ ├── Integer.WideArithmetic.Spec.savi │ ├── Main.savi │ ├── None.Spec.savi │ ├── Numeric.Arithmetic.Spec.savi │ ├── Numeric.Bounded.Spec.savi │ ├── Numeric.Comparable.Spec.savi │ ├── Numeric.Convertible.Spec.savi │ ├── Numeric.Representable.Spec.savi │ ├── Numeric.Spec.savi │ ├── Pair.Spec.savi │ ├── Platform.Spec.savi │ ├── String.DecodeUTF8.Spec.savi │ ├── String.Spec.savi │ ├── TraceData.Mutable.Spec.savi │ ├── U64.BCD.Spec.savi │ └── manifest.savi ├── integration │ ├── README.md │ ├── error-manifests-copies-recursive │ │ ├── manifest.savi │ │ └── savi.errors.txt │ ├── error-manifests-copies-wrong-name │ │ ├── manifest.savi │ │ └── savi.errors.txt │ ├── error-manifests-many-but-no-main │ │ ├── another.manifest.savi │ │ ├── manifest.savi │ │ └── savi.errors.txt │ ├── error-manifests-many-main │ │ ├── another.manifest.savi │ │ ├── manifest.savi │ │ └── savi.errors.txt │ ├── error-manifests-no-sources │ │ ├── manifest.savi │ │ └── savi.errors.txt │ ├── error-manifests-non-unique-names │ │ ├── manifest.savi │ │ └── savi.errors.txt │ ├── error-manifests-none │ │ ├── manifest.savi │ │ ├── not-a-manifest.savi │ │ └── savi.errors.txt │ ├── error-manifests-reserved-names │ │ ├── manifest.savi │ │ └── savi.errors.txt │ ├── error-namespace-conflicts-and-sources-exclusions │ │ ├── manifest.savi │ │ ├── savi.errors.txt │ │ ├── src │ │ │ ├── exclude-a.savi │ │ │ ├── exclude-b.savi │ │ │ ├── exclude-extra.savi │ │ │ ├── include-a.savi │ │ │ └── include-b.savi │ │ └── src2 │ │ │ ├── exclude-a.savi │ │ │ ├── exclude-b.savi │ │ │ ├── exclude-extra.savi │ │ │ ├── include-a.savi │ │ │ └── include-b.savi │ ├── fix-manifests-missing-transitive-deps │ │ ├── savi.errors.txt │ │ ├── savi.fix.after.dir │ │ │ └── manifest.savi │ │ └── savi.fix.before.dir │ │ │ ├── main.savi │ │ │ └── manifest.savi │ ├── run-all.sh │ ├── run-ffi-globals │ │ ├── manifest.savi │ │ ├── savi.run.output.txt │ │ ├── savi.run.test.sh │ │ ├── src │ │ │ └── Main.savi │ │ └── vendor │ │ │ └── mylib_globals.c │ ├── run-ffi-link-c-files │ │ ├── manifest.savi │ │ ├── savi.run.output.txt │ │ ├── savi.run.test.sh │ │ ├── src │ │ │ └── Main.savi │ │ └── vendor │ │ │ ├── mylib_add.c │ │ │ └── mylib_sub.c │ ├── run-ffi-link-cpp-files │ │ ├── manifest.savi │ │ ├── savi.run.output.txt │ │ ├── savi.run.test.sh │ │ ├── src │ │ │ └── Main.savi │ │ └── vendor │ │ │ └── mylib_wraps_cpp.cpp │ ├── run-one.sh │ ├── run-overlapping-manifest-sources │ │ ├── manifest.savi │ │ ├── savi.run.output.txt │ │ ├── savi.run.test.sh │ │ └── src │ │ │ ├── Main.Example.savi │ │ │ ├── Main.savi │ │ │ └── Message.Example.savi │ └── run-stdout │ │ ├── manifest.savi │ │ ├── savi.run.output.txt │ │ ├── savi.run.test.sh │ │ └── src │ │ └── Main.savi ├── language │ ├── manifest.savi │ ├── micro_test │ │ └── micro_test.savi │ └── semantics │ │ ├── displacing_assignment_spec.savi │ │ ├── enum_spec.savi │ │ ├── identity_spec.savi │ │ ├── number_spec.savi │ │ ├── reflection_spec.savi │ │ ├── regression_spec.savi │ │ ├── return_spec.savi │ │ ├── source_code_spec.savi │ │ ├── stack_address_of_variable_spec.savi │ │ ├── static_address_of_function_spec.savi │ │ ├── string_spec.savi │ │ ├── struct_spec.savi │ │ ├── test.savi │ │ ├── trait_non.savi │ │ ├── try_spec.savi │ │ ├── while_spec.savi │ │ └── yielding_call_spec.savi ├── parser_spec.cr └── spec_helper.cr ├── src ├── savi.cr └── savi │ ├── ast.cr │ ├── ast │ ├── extract.cr │ ├── format.cr │ ├── format │ │ └── indent_state.cr │ └── gather.cr │ ├── compiler.cr │ ├── compiler │ ├── binary.cr │ ├── binary_object.cr │ ├── binary_verona.cr │ ├── classify.cr │ ├── code_gen.cr │ ├── code_gen │ │ ├── continuation_info.cr │ │ ├── debug_info.cr │ │ ├── gen_func.cr │ │ ├── gen_type.cr │ │ ├── ponyrt.cr │ │ └── veronart.cr │ ├── common │ │ └── find_by_pos.cr │ ├── completeness.cr │ ├── context.cr │ ├── flow.cr │ ├── functions.cr.stash │ ├── infer.cr │ ├── infer │ │ ├── conduit.cr │ │ ├── info.cr │ │ ├── meta_type.cr │ │ ├── meta_type │ │ │ ├── anti_nominal.cr │ │ │ ├── capability.cr │ │ │ ├── intersection.cr │ │ │ ├── nominal.cr │ │ │ ├── unconstrained.cr │ │ │ ├── union.cr │ │ │ └── unsatisfiable.cr │ │ ├── reified.cr │ │ └── span.cr │ ├── inventory.cr │ ├── jumps.cr │ ├── lifetime.cr │ ├── load.cr │ ├── local.cr │ ├── macros.cr │ ├── macros │ │ └── util.cr │ ├── manifests.cr │ ├── namespace.cr │ ├── paint.cr │ ├── pass │ │ └── analyze.cr │ ├── populate.cr │ ├── populate_types.cr │ ├── pre_infer.cr │ ├── pre_subtyping.cr │ ├── pre_t_infer.cr │ ├── privacy.cr │ ├── reach.cr │ ├── refer.cr │ ├── refer │ │ └── info.cr │ ├── refer_type.cr │ ├── reparse.cr │ ├── run.cr │ ├── serve_definition.cr │ ├── serve_hover.cr │ ├── source_service.cr │ ├── subtyping_cache.cr │ ├── sugar.cr │ ├── t_infer.cr │ ├── t_infer │ │ ├── conduit.cr │ │ ├── info.cr │ │ ├── meta_type.cr │ │ ├── meta_type │ │ │ ├── anti_nominal.cr │ │ │ ├── intersection.cr │ │ │ ├── nominal.cr │ │ │ ├── unconstrained.cr │ │ │ ├── union.cr │ │ │ └── unsatisfiable.cr │ │ ├── reified.cr │ │ └── span.cr │ ├── t_subtyping_cache.cr │ ├── t_type_check.cr │ ├── target.cr │ ├── type_check.cr │ ├── type_context.cr │ ├── types │ │ ├── edge.cr │ │ ├── graph.cr │ │ └── type.cr │ ├── verify.cr │ ├── xtypes.cr │ └── xtypes │ │ ├── algebraic_type.cr │ │ ├── cap.cr │ │ ├── graph.cr │ │ └── type_variable.cr │ ├── error.cr │ ├── ext │ ├── array_map_cow.cr │ ├── bit_array.cr │ ├── clang.cr │ ├── clang │ │ └── cursor.cr │ ├── file.cr │ ├── lib_gc.cr │ ├── llvm.cr │ ├── llvm │ │ ├── basic_block.cr │ │ ├── basic_block_collection.cr │ │ ├── builder.cr │ │ ├── context.cr │ │ ├── for_savi │ │ │ ├── LLVMCompileClangForSavi.cc │ │ │ ├── LLVMDefaultClangFlagsForSavi.cc │ │ │ ├── LLVMLinkForSavi.cc │ │ │ ├── LLVMOptimizeForSavi.cc │ │ │ ├── LLVMRemapDIDirectoryForSavi.cc │ │ │ └── main.cc │ │ ├── function.cr │ │ ├── function_collection.cr │ │ ├── lib_llvm.cr │ │ ├── module.cr │ │ ├── target_data.cr │ │ ├── type.cr │ │ └── value_methods.cr │ ├── string.cr │ ├── struct_ref.cr │ └── time.cr │ ├── ffi_gen.cr │ ├── init.cr │ ├── packaging │ ├── dependency.cr │ ├── manifest.cr │ └── remote_service.cr │ ├── parser.cr │ ├── parser │ ├── builder.cr │ ├── builder │ │ └── state.cr │ └── grammar.cr │ ├── program.cr │ ├── program │ ├── declarator.cr │ └── declarator │ │ ├── bootstrap.cr │ │ ├── interpreter.cr │ │ ├── intrinsic.cr │ │ ├── scope.cr │ │ └── term_acceptor.cr │ ├── server.cr │ ├── source.cr │ ├── spec_markdown.cr │ └── spec_markdown │ └── format.cr └── tooling ├── coc-nvim ├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── README.md ├── package-lock.json ├── package.json ├── src │ ├── @types │ │ └── tmp │ │ │ └── index.d.ts │ └── index.ts ├── tsconfig.json └── webpack.config.js ├── emacs └── savi-mode │ ├── README.md │ └── savi-mode.el ├── lldb └── savi_commands.py ├── pygments ├── README.md ├── lexers │ └── savi.py └── tests │ └── examplefiles │ └── savi │ ├── example.savi │ └── example.savi.output ├── rouge ├── README.md ├── demos │ └── savi ├── lexers │ └── savi.rb └── spec │ ├── lexers │ └── savi_spec.rb │ └── visual │ └── samples │ └── savi ├── vis └── lexers │ └── savi.lua └── vscode ├── .gitignore ├── .vscode └── launch.json ├── .vscodeignore ├── CHANGELOG.md ├── README.md ├── language-configuration.json ├── package-lock.json ├── package.json ├── src ├── @types │ └── tmp │ │ └── index.d.ts └── extension.ts ├── syntaxes ├── Savi.YAML-tmLanguage ├── Savi.tmLanguage └── codeblock.json ├── tsconfig.json └── vsc-extension-quickstart.md /.dockerignore: -------------------------------------------------------------------------------- 1 | .hg 2 | .git 3 | *.jemc 4 | /run.sh 5 | /assets 6 | /example 7 | /spec 8 | /tooling 9 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: Savi 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | CI: 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | include: 14 | - crystal: "1.10.1" 15 | os: ubuntu-20.04 16 | deps: sudo apt-get install -y capnproto libgc-dev 17 | - crystal: "1.10.1" 18 | os: macos-12 # upgrade to macos-13 when available 19 | deps: brew install libgc capnp 20 | runs-on: ${{matrix.os}} 21 | steps: 22 | - name: Checkout Repository 23 | uses: actions/checkout@v2 24 | 25 | - name: Install Dependencies 26 | run: ${{matrix.deps}} 27 | 28 | - name: Install Crystal 29 | uses: crystal-lang/install-crystal@v1 30 | with: 31 | crystal: ${{matrix.crystal}} 32 | 33 | - name: Full CI Suite 34 | run: make ci 35 | -------------------------------------------------------------------------------- /.github/workflows/release-notify.yaml: -------------------------------------------------------------------------------- 1 | # This workflow is responsible for sending out notifications to various feeds 2 | # whenever a new release of the language and compiler is published. 3 | # 4 | # The workflow is triggered on each published release in GitHub Releases. 5 | 6 | name: release-notify 7 | 8 | on: 9 | release: 10 | types: [published] 11 | 12 | jobs: 13 | # Publish a message in the notifications stream of the Savi instance of 14 | # the Zulip chat service using the given bot credentials. 15 | zulip: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: zulip/github-actions-zulip/send-message@v1 19 | with: 20 | api-key: ${{ secrets.BOT_ZULIP_API_KEY }} 21 | email: ${{ secrets.BOT_ZULIP_EMAIL }} 22 | organization-url: https://savi.zulipchat.com 23 | to: notifications 24 | type: stream 25 | topic: ${{ github.event.repository.full_name }} releases 26 | content: " 27 | [${{ github.event.release.name }}](${{ github.event.release.html_url }}) 28 | of 29 | [${{ github.event.repository.full_name }}](${{ github.event.repository.html_url }}) 30 | has been released!\n\n 31 | ${{ github.event.release.body }}" 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | deps 3 | /.make-var-cache 4 | /.make-var-cache-in-docker 5 | /build 6 | /build-in-docker 7 | /lib/libsavi_runtime* 8 | /.tool-versions 9 | -------------------------------------------------------------------------------- /.lldbinit: -------------------------------------------------------------------------------- 1 | # Remap source filenames under /opt/code to be under the current working dir. 2 | settings set target.source-map /opt/code . 3 | 4 | # Add nice printing of TYPE and TYPESTRING builtin types. 5 | type summary add TYPE --summary-string "${var.name._ptr} (id=${var.id})" 6 | type summary add TYPESTRING --summary-string "${var._ptr}" 7 | 8 | # Load custom python script commands. 9 | command script import ./tooling/lldb/savi_commands.py 10 | command script add -f savi_commands.get_command get 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 Joe Eli McIlvain 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 10 | 11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12 | -------------------------------------------------------------------------------- /LICENSE-crystal: -------------------------------------------------------------------------------- 1 | Parts of this source code are based on code from `crystal`, whose license is: 2 | 3 | Copyright 2012-2019 Manas Technology Solutions. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | -------------------------------------------------------------------------------- /assets/savi-logo-rect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/savi-lang/savi/946dc4eab2e2a98e7b6c1d22f1013b04d5adf949/assets/savi-logo-rect.png -------------------------------------------------------------------------------- /bin/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/savi-lang/savi/946dc4eab2e2a98e7b6c1d22f1013b04d5adf949/bin/.gitkeep -------------------------------------------------------------------------------- /core/Any.savi: -------------------------------------------------------------------------------- 1 | :trait non Any 2 | -------------------------------------------------------------------------------- /core/Bool.savi: -------------------------------------------------------------------------------- 1 | :enum Bool 2 | :bit_width 1 3 | :member noprefix False 0 4 | :member noprefix True 1 5 | :fun val is_true: @ 6 | :fun val is_false: @invert 7 | :fun val not: @invert 8 | 9 | :is TraceData 10 | :fun trace_data(trace TraceData.Observer) 11 | trace.primitive_bool(@as_val) 12 | -------------------------------------------------------------------------------- /core/Comparable.savi: -------------------------------------------------------------------------------- 1 | :trait Comparable(A Comparable(A)'read) 2 | :is Equatable(A) 3 | :fun "<"(other A'box) Bool 4 | :fun "<="(other A'box) Bool: (@ == other) || (@ < other) 5 | :fun ">="(other A'box) Bool: (@ < other).is_false 6 | :fun ">"(other A'box) Bool: (@ <= other).is_false 7 | -------------------------------------------------------------------------------- /core/Env.Root.TicketIssuer.savi: -------------------------------------------------------------------------------- 1 | :actor Env.Root.TicketIssuer 2 | :new _new // private constructor for security 3 | 4 | // TODO: Use a generic Accessible trait 5 | :be access(courier Env.Root.TicketIssuer.Courier.Any): courier.call(@) 6 | 7 | :var _issued_ticket_types Array(Any'non): [] 8 | 9 | :fun ref mark_as_issued_if_available!(ticket_type Any'non) 10 | error! if @_issued_ticket_types.includes(ticket_type) 11 | @_issued_ticket_types.push(ticket_type) 12 | 13 | :trait iso Env.Root.TicketIssuer.Ticket 14 | :new iso new_from_issuer!(issuer Env.Root.TicketIssuer'ref) 15 | 16 | // TODO: Probably remove this glue trait when we have proper lambdas. 17 | :trait val Env.Root.TicketIssuer.Courier.Any 18 | :fun val call(issuer Env.Root.TicketIssuer'ref) None 19 | 20 | // TODO: Probably remove this glue class when we have proper lambdas. 21 | :class val Env.Root.TicketIssuer.Courier(T Env.Root.TicketIssuer.Ticket'iso) 22 | :let recipient Env.Root.TicketIssuer.Recipient(T) 23 | :new val (@recipient) 24 | :fun val call(issuer Env.Root.TicketIssuer'ref) None 25 | @recipient.accept_ticket(try T.new_from_issuer!(issuer)) 26 | 27 | // TODO: Probably remove this glue trait when we have proper lambdas. 28 | :trait tag Env.Root.TicketIssuer.Recipient(T Any'iso) 29 | :be accept_ticket(ticket (T | None)) 30 | -------------------------------------------------------------------------------- /core/Equatable.savi: -------------------------------------------------------------------------------- 1 | :trait Equatable(A Equatable(A)'read) 2 | :fun "=="(other A'box) Bool 3 | :fun "!="(other A'box) Bool: (@ == other).is_false 4 | -------------------------------------------------------------------------------- /core/IntoString.savi: -------------------------------------------------------------------------------- 1 | :: A trait for emitting a representation of the value into a given `String`. 2 | :: 3 | :: These methods are used by string interpolation syntax, so ensuring that 4 | :: a type implements this trait will make it directly usable in interpolation. 5 | :trait box IntoString 6 | :: Emit a representation of this value into the given `String`. 7 | :: 8 | :: This method is expected by convention to append some bytes into the 9 | :: `String` but not to modify any earlier portion of the `String`. 10 | :fun box into_string(out String'ref) None 11 | 12 | :: Return a conservative estimate for how much many bytes are required to hold 13 | :: the string representation of this value when emitted with `into_string`. 14 | :: 15 | :: Here, "conservative estimate" means that if a perfectly accurate estimate 16 | :: is not possible, the function should prefer to over-estimate the amount 17 | :: of space needed, because an under-estimation would result in a potentially 18 | :: costly re-allocation and copy of data in the underlying string buffer. 19 | :fun box into_string_space USize 20 | -------------------------------------------------------------------------------- /core/None.savi: -------------------------------------------------------------------------------- 1 | :module None 2 | :is IntoString 3 | // TODO: These shouldn't need to be `:fun box` - `:fun non` should be okay. 4 | :: When emitting into a string, emit nothing (i.e. an empty string). 5 | :fun box into_string(out String'ref): None 6 | :fun box into_string_space USize: 0 7 | 8 | :: When inspecting, print explicitly using the name `None`. 9 | :fun box inspect_into(out String'ref) None: out << "None" 10 | 11 | :is TraceData 12 | :fun trace_data(trace TraceData.Observer) 13 | trace.primitive_none 14 | -------------------------------------------------------------------------------- /core/Pair.savi: -------------------------------------------------------------------------------- 1 | :struct Pair(A, B = A) 2 | :let first A 3 | :let second B 4 | :new (@first, @second) 5 | 6 | // Convenience alias for referring to the second element as the last element. 7 | :fun last: @second 8 | 9 | // Convenience aliases for when this is used as a key/value pair. 10 | :fun key: @first 11 | :fun value: @second 12 | 13 | // Convenience aliases for when this is used as a head/tail pair. 14 | :fun head: @first 15 | :fun tail: @second 16 | 17 | // Convenience aliases for when this is used as a high/low pair. 18 | :fun high: @first 19 | :fun low: @second 20 | :fun hi: @high 21 | :fun lo: @low 22 | 23 | :is TraceData 24 | :fun trace_data(trace TraceData.Observer) 25 | trace.object(0) -> ( 26 | trace.property("first" 27 | case A <: ( 28 | | TraceData'read | @first 29 | | Any'read | TraceData.Untraceable.Maybe[@first] 30 | | TraceData.Untraceable 31 | ) 32 | ) 33 | trace.property("second" 34 | case A <: ( 35 | | TraceData'read | @second 36 | | Any'read | TraceData.Untraceable.Maybe[@second] 37 | | TraceData.Untraceable 38 | ) 39 | ) 40 | ) 41 | -------------------------------------------------------------------------------- /core/Reflection.savi: -------------------------------------------------------------------------------- 1 | :class val ReflectionOfType(A) 2 | :: The string used in compiler messages to represent the type with capability. 3 | :: If the capability is the same as the default for that type, it will be 4 | :: omitted for brevity. Type arguments will also be included if present. 5 | :: 6 | :: $ (reflection_of_type "example").string 7 | :: > "String" 8 | :: $ (reflection_of_type String.new).string 9 | :: > "String'ref" 10 | :: $ (reflection_of_type Array(U8).new).string 11 | :: > "Array(U8)" 12 | :let string String: "" 13 | 14 | :let features Array(ReflectionFeatureOfType(A)): [] 15 | 16 | :is IntoString 17 | :fun into_string_space USize: @string.size 18 | :fun into_string(out String'ref) None: @string.into_string(out) 19 | 20 | :class val ReflectionFeatureOfType(A) 21 | :let name String: "" 22 | :let tags Array(String): [] 23 | :let mutator (ReflectionMutatorOfType(A) | None): None 24 | 25 | :fun maybe_call_mutator(a A) Bool 26 | try ( 27 | @mutator.not!(None).call(--a) 28 | True 29 | | 30 | False 31 | ) 32 | 33 | :trait non ReflectionMutatorOfType(A) 34 | :fun non call(a A) None: None 35 | -------------------------------------------------------------------------------- /core/SourceCodePos.savi: -------------------------------------------------------------------------------- 1 | :class val SourceCodePosition 2 | // TODO: include more properties from the original Savi::Source::Pos? 3 | :var string String: "" 4 | :var filename String: "" 5 | :: The filepath relative to the package 6 | :var filepath_pkgrel String: "" 7 | :: The filepath relative to the root package 8 | :var filepath_rootpkgrel String: "" 9 | :var dirname String: "" 10 | :var pkgname String: "" 11 | :var pkgpath String: "" 12 | :var row USize: 0 13 | :var col USize: 0 14 | -------------------------------------------------------------------------------- /core/_EmptyConstructable.savi: -------------------------------------------------------------------------------- 1 | :trait ref _EmptyConstructableRef // TODO: Should we make this a public/standard name? If so, we need a better name than this junk... 2 | :new ref () 3 | -------------------------------------------------------------------------------- /core/_FFI.savi: -------------------------------------------------------------------------------- 1 | :module _FFI 2 | :ffi puts(string CPointer(U8)) I32 3 | :ffi strlen(string CPointer(U8)) USize 4 | :ffi memset(pointer CPointer(U8), char I32, count USize) None 5 | :ffi variadic snprintf(buffer CPointer(U8), buffer_size I32, fmt CPointer(U8)) I32 6 | :ffi strtod(start_pointer CPointer(U8), end_pointer CPointer(CPointer(U8))) F64 7 | 8 | :ffi pony_exitcode(code I32) None 9 | :ffi pony_os_stdout_setup() None 10 | :ffi pony_os_stdout() CPointer(None)'ref 11 | :ffi pony_os_stderr() CPointer(None)'ref 12 | :ffi pony_os_std_print( 13 | fp CPointer(None)'ref, buffer CPointer(U8), length USize 14 | ) None 15 | :ffi pony_os_std_write( 16 | fp CPointer(None)'ref, buffer CPointer(U8), length USize 17 | ) None 18 | :ffi pony_os_std_flush(fp CPointer(None)'ref) None 19 | 20 | :module _FFI.Cast(A, B) 21 | :: An FFI-only utility function for bit-casting type A to B. 22 | :: 23 | :: This is only meant to be used for pointer types, and will 24 | :: fail badly if either A or B is not an ABI pointer type 25 | :: 26 | :: Obviously this utility function makes it easy to break 27 | :: memory safety, so it should be used with great care. 28 | :: 29 | :: Being private, It is only accessible from within the core library, 30 | :: though other libraries can set up similar mechanisms as well, 31 | :: provided that they are explicitly allowed by the root manifest to use FFI. 32 | :ffi pointer(input A) B 33 | :foreign_name savi_cast_pointer 34 | -------------------------------------------------------------------------------- /examples/adventofcode/2018/manifest.savi: -------------------------------------------------------------------------------- 1 | :manifest "adventofcode-2018" 2 | :sources "src/*.savi" 3 | 4 | :dependency Spec v0 5 | :from "github:savi-lang/Spec" 6 | :depends on Map 7 | :depends on Time 8 | :depends on Timer 9 | 10 | :dependency Map v0 11 | :from "github:savi-lang/Map" 12 | 13 | :dependency Time v0 14 | :from "github:savi-lang/Time" 15 | 16 | :transitive dependency Timer v0 17 | :from "github:savi-lang/Timer" 18 | :depends on Time 19 | -------------------------------------------------------------------------------- /examples/adventofcode/2018/src/main.savi: -------------------------------------------------------------------------------- 1 | :actor Main 2 | :new (env) 3 | Spec.Process.run(env, [ 4 | Spec.Run(Day1Spec).new(env) 5 | Spec.Run(Day2Spec).new(env) 6 | Spec.Run(Day3Spec).new(env) 7 | Spec.Run(Day4Spec).new(env) 8 | Spec.Run(Day5Spec).new(env) 9 | ]) 10 | -------------------------------------------------------------------------------- /examples/bar/manifest.savi: -------------------------------------------------------------------------------- 1 | :manifest lib Bar 2 | :sources "src/*.savi" 3 | 4 | :manifest bin "spec" 5 | :copies Bar 6 | :sources "spec/*.savi" 7 | 8 | :dependency Spec v0 9 | :from "github:savi-lang/Spec" 10 | :depends on Map 11 | :depends on Time 12 | :depends on Timer 13 | 14 | :transitive dependency Map v0 15 | :from "github:savi-lang/Map" 16 | 17 | :transitive dependency Time v0 18 | :from "github:savi-lang/Time" 19 | 20 | :transitive dependency Timer v0 21 | :from "github:savi-lang/Timer" 22 | :depends on Time 23 | -------------------------------------------------------------------------------- /examples/bar/spec/Bar.Spec.savi: -------------------------------------------------------------------------------- 1 | :class Bar.Spec 2 | :is Spec 3 | :const describes: "Bar" 4 | 5 | :it "has a placeholder method for demonstrating testing" 6 | assert: Bar.placeholder == True 7 | -------------------------------------------------------------------------------- /examples/bar/spec/Main.savi: -------------------------------------------------------------------------------- 1 | :actor Main 2 | :new (env Env) 3 | Spec.Process.run(env, [ 4 | Spec.Run(Bar.Spec).new(env) 5 | ]) 6 | -------------------------------------------------------------------------------- /examples/bar/src/Bar.savi: -------------------------------------------------------------------------------- 1 | :module Bar 2 | :fun placeholder 3 | True 4 | -------------------------------------------------------------------------------- /examples/foo/manifest.savi: -------------------------------------------------------------------------------- 1 | :manifest bin "example" 2 | :sources "src/*.savi" 3 | 4 | :dependency Bar 5 | :from "relative:../bar" 6 | -------------------------------------------------------------------------------- /examples/foo/src/Main.savi: -------------------------------------------------------------------------------- 1 | :actor Main 2 | :new (env Env) 3 | env.out.print("Hello, World!") 4 | -------------------------------------------------------------------------------- /examples/projecteuler/problem_1/manifest.savi: -------------------------------------------------------------------------------- 1 | :manifest "projecteuler-1" 2 | :sources "src/*.savi" 3 | -------------------------------------------------------------------------------- /examples/projecteuler/problem_1/src/main.savi: -------------------------------------------------------------------------------- 1 | // https://projecteuler.net/problem=1 2 | 3 | :actor Counter 4 | :var count USize 5 | 6 | :new (@count = 0) 7 | 8 | :be increment(number = 1) 9 | @count = @count + number 10 | None 11 | 12 | :be display 13 | Inspect.out(@count) 14 | None 15 | 16 | :actor Main 17 | :new (env) 18 | counter = Counter.new 19 | 20 | USize[1000].times -> (number | 21 | if @multiple_of_three_or_five(number) counter.increment(number) 22 | ) 23 | 24 | // [SPOILER ALERT] sum is 233168. 25 | counter.display 26 | 27 | :fun multiple_of_three_or_five(number USize) 28 | number % 3 == 0 || number % 5 == 0 29 | -------------------------------------------------------------------------------- /examples/verona/manifest.savi: -------------------------------------------------------------------------------- 1 | :manifest "verona-example" 2 | :sources "src/*.savi" 3 | -------------------------------------------------------------------------------- /examples/verona/src/main.savi: -------------------------------------------------------------------------------- 1 | :module _FFI 2 | :ffi variadic printf(format CPointer(U8)) I32 3 | 4 | :class Greeting 5 | :let message1 String 6 | :let message2 String 7 | :new iso (@message1, @message2) 8 | :fun say 9 | _FFI.printf("%s\n".cstring, @message1.cstring) 10 | _FFI.printf("%s\n".cstring, @message2.cstring) 11 | 12 | :actor Greeter 13 | :new (g Greeting'val) 14 | greeting val = Greeting.new("unused", "unused") 15 | greeting = Greeting.new(g.message1, g.message2) 16 | greeting.say 17 | 18 | :actor Main 19 | :new (env) 20 | greeting val = Greeting.new("What goes", "up") 21 | Greeter.new(greeting) 22 | 23 | greeting = Greeting.new("Must come", "down") 24 | Greeter.new(greeting) 25 | 26 | greeting = Greeting.new("Savi can", "now") 27 | Greeter.new(greeting) 28 | 29 | greeting = Greeting.new("Schedule a", "cown") 30 | Greeter.new(greeting) 31 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "locked": { 5 | "lastModified": 1637547400, 6 | "narHash": "sha256-Mo/bwhZYAAMeb+RGXYpwzxHd4GKaSIDwlCBJh1vULk8=", 7 | "owner": "NixOS", 8 | "repo": "nixpkgs", 9 | "rev": "7ea3e0f3f24780c8344492a494c905947e038d7f", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "id": "nixpkgs", 14 | "type": "indirect" 15 | } 16 | }, 17 | "root": { 18 | "inputs": { 19 | "nixpkgs": "nixpkgs" 20 | } 21 | } 22 | }, 23 | "root": "root", 24 | "version": 7 25 | } 26 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Savi dev environment flake"; 3 | outputs = { self, nixpkgs }: { 4 | 5 | devShell.x86_64-linux = 6 | with import nixpkgs { system = "x86_64-linux"; }; 7 | let 8 | stdenv = clang9Stdenv; 9 | # An FHS environment is the easiest way to use the static LLVM for now 10 | fhsenv = buildFHSUserEnv.override { stdenv = stdenv; } { 11 | name = "savi-fhs"; 12 | targetPkgs = pkgs: with pkgs; [ 13 | stdenv.cc.libc_dev.dev 14 | clang-tools 15 | crystal 16 | llvm 17 | 18 | # used by Crystal 19 | boehmgc.dev 20 | libevent.dev 21 | pcre.dev 22 | 23 | # necessary for some build steps 24 | curl util-linux 25 | 26 | # dev tooling 27 | lldb 28 | ]; 29 | }; 30 | in 31 | clang9Stdenv.mkDerivation { 32 | name = "savi-env"; 33 | buildInputs = with nixpkgs.legacyPackages; [ 34 | fhsenv 35 | ]; 36 | shellHook = '' 37 | savi-fhs 38 | ''; 39 | }; 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /lib/.shards.info: -------------------------------------------------------------------------------- 1 | --- 2 | version: 1.0 3 | shards: 4 | pegmatite: 5 | git: https://github.com/jemc/crystal-pegmatite.git 6 | version: 0.2.3+git.commit.e7470d6eb1135cfd8a5353302edc5cad96f2c562 7 | lsp: 8 | git: https://github.com/jemc/crystal-lsp.git 9 | version: 0.1.0+git.commit.f7af03eed5b63974c04e92c0854b7c4a5b92c7f9 10 | clim: 11 | git: https://github.com/at-grandpa/clim.git 12 | version: 0.13.0 13 | clang: 14 | git: https://github.com/crystal-lang/clang.cr.git 15 | version: 0.3.0 16 | capnproto: 17 | git: https://github.com/jemc/crystal-capnproto.git 18 | version: 1.0.0+git.commit.61c3cf19167eb71398a1f36da2032c4560ebcdc9 19 | -------------------------------------------------------------------------------- /lib/capnproto/.gitignore: -------------------------------------------------------------------------------- 1 | /bin 2 | -------------------------------------------------------------------------------- /lib/capnproto/README.md: -------------------------------------------------------------------------------- 1 | # crystal-capnproto 2 | 3 | Library for reading [Cap'n Proto](https://capnproto.org/) serialized data structures in the [Crystal language](https://crystal-lang.org/). 4 | -------------------------------------------------------------------------------- /lib/capnproto/lib: -------------------------------------------------------------------------------- 1 | .. -------------------------------------------------------------------------------- /lib/capnproto/shard.yml: -------------------------------------------------------------------------------- 1 | name: capnproto 2 | version: 1.0.0 3 | 4 | authors: 5 | - Joe Eli McIlvain 6 | 7 | crystal: '>= 1.10.1' 8 | 9 | license: MIT 10 | -------------------------------------------------------------------------------- /lib/capnproto/spec/spec_helper.cr: -------------------------------------------------------------------------------- 1 | require "spec" 2 | require "../src/capnproto" 3 | -------------------------------------------------------------------------------- /lib/capnproto/src/CapnProto.Savi.Meta.capnp: -------------------------------------------------------------------------------- 1 | @0xbf772c07f494658e; 2 | $namespace("CapnProto.Savi.Meta"); 3 | 4 | annotation namespace(file): Text; 5 | -------------------------------------------------------------------------------- /lib/capnproto/src/capnpc-crystal/main.cr: -------------------------------------------------------------------------------- 1 | require "../capnproto" 2 | require "./gen" 3 | 4 | reader = CapnProto::Segment::Reader.new 5 | segments : Array(CapnProto::Segment)? = nil 6 | until segments 7 | segments = reader.read(STDIN) 8 | end 9 | 10 | req = CapnProto::Meta::CodeGeneratorRequest.read_from_pointer( 11 | CapnProto::Pointer::Struct.parse_from( 12 | segments[0], 0, segments[0].u64(0) 13 | ) 14 | ) 15 | 16 | req.requested_files.each { |file| 17 | req.nodes.each { |node| 18 | if node.id == file.id 19 | print(Gen.new(req, node).take_string) 20 | end 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/capnproto/src/capnproto.cr: -------------------------------------------------------------------------------- 1 | require "./capnproto/segment" 2 | require "./capnproto/pointer_far" 3 | require "./capnproto/pointer_u8_list" 4 | require "./capnproto/pointer_struct" 5 | require "./capnproto/pointer_struct_list" 6 | require "./capnproto/list" 7 | require "./capnproto/meta" 8 | -------------------------------------------------------------------------------- /lib/capnproto/src/capnproto/list.cr: -------------------------------------------------------------------------------- 1 | struct CapnProto::List(A) 2 | def initialize(@p : CapnProto::Pointer::StructList) 3 | end 4 | private def self.new; end 5 | def self.read_from_pointer(p); obj = allocate; obj.initialize(p); obj; end 6 | 7 | def size 8 | @p.list_count.to_i32 9 | end 10 | 11 | def empty? 12 | self.size == 0 13 | end 14 | 15 | def [](n : Int32) 16 | p = @p[n.to_u32] 17 | p ? A.read_from_pointer(p) : nil 18 | end 19 | 20 | def each 21 | @p.each { |p| yield A.read_from_pointer(p) } 22 | end 23 | 24 | def find 25 | self.each { |elem| return elem if (yield elem) } 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/clang/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2016-2017 Julien Portalier 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /lib/clang/Makefile: -------------------------------------------------------------------------------- 1 | .POSIX: 2 | 3 | CRYSTAL = crystal 4 | CRFLAGS = --release 5 | LLVM_INCLUDE = /usr/lib/llvm-8/include 6 | 7 | all: bin/c2cr 8 | 9 | bin/c2cr: samples/c2cr.cr samples/c2cr/*.cr src/*.cr src/clang-c/* 10 | @mkdir -p bin 11 | $(CRYSTAL) build $(CRFLAGS) samples/c2cr.cr -o bin/c2cr 12 | 13 | libclang: .phony 14 | @mkdir -p src/clang-c/ 15 | bin/c2cr -I$(LLVM_INCLUDE) --remove-enum-prefix clang-c/BuildSystem.h > src/clang-c/BuildSystem.cr 16 | #bin/c2cr -I$(LLVM_INCLUDE) --remove-enum-prefix clang-c/CXCompilationDatabase.h > src/clang-c/CXCompilationDatabase.cr 17 | bin/c2cr -I$(LLVM_INCLUDE) --remove-enum-prefix clang-c/CXErrorCode.h > src/clang-c/CXErrorCode.cr 18 | bin/c2cr -I$(LLVM_INCLUDE) --remove-enum-prefix clang-c/CXString.h > src/clang-c/CXString.cr 19 | bin/c2cr -I$(LLVM_INCLUDE) --remove-enum-prefix clang-c/Documentation.h > src/clang-c/Documentation.cr 20 | bin/c2cr -I$(LLVM_INCLUDE) --remove-enum-prefix clang-c/Index.h > src/clang-c/Index.cr 21 | bin/c2cr -I$(LLVM_INCLUDE) --remove-enum-prefix clang-c/Platform.h > src/clang-c/Platform.cr 22 | 23 | .phony: 24 | -------------------------------------------------------------------------------- /lib/clang/README.md: -------------------------------------------------------------------------------- 1 | # libclang bindings for Crystal 2 | 3 | Usage: 4 | 5 | ```crystal 6 | require "clang" 7 | 8 | index = Clang::Index.new 9 | 10 | files = [ 11 | #Clang::UnsavedFile.new("input.c", "#include \n"), 12 | Clang::UnsavedFile.new("input.c", "#include \n"), 13 | ] 14 | tu = Clang::TranslationUnit.from_source(index, files, [ 15 | "-I/usr/include", 16 | "-I/usr/lib/llvm-5.0/include", 17 | ]) 18 | 19 | tu.cursor.visit_children do |cursor| 20 | p cursor 21 | 22 | Clang::ChildVisitResult::Continue 23 | end 24 | ``` 25 | 26 | ## Samples 27 | 28 | See the `samples` folder for some example usages: 29 | 30 | - `samples/debug.cr` will print the AST of C or C++ headers as they are parsed; 31 | - `samples/c2cr.cr` will automatically generate Crystal bindings for a C header. 32 | 33 | For example: 34 | 35 | ```sh 36 | $ shards build --release 37 | 38 | $ bin/c2cr -I/usr/lib/llvm-5.0/include llvm-c/Core.h \ 39 | --remove-enum-prefix=LLVM --remove-enum-suffix > llvm-c/Core.cr 40 | 41 | $ bin/c2cr -I/usr/lib/llvm-5.0/include clang-c/Index.h \ 42 | --remove-enum-prefix > clang-c/Index.cr 43 | 44 | $ bin/c2cr gtk-2.0/gtk/gtkenums.h --remove-enum-prefix > gtk/enums.cr 45 | ``` 46 | 47 | ## Reference 48 | 49 | - [C interface to Clang](http://clang.llvm.org/doxygen/group__CINDEX.html) 50 | 51 | ## License 52 | 53 | Distributed under the Apache 2.0 license. 54 | -------------------------------------------------------------------------------- /lib/clang/lib: -------------------------------------------------------------------------------- 1 | .. -------------------------------------------------------------------------------- /lib/clang/samples/c2cr/constant.cr: -------------------------------------------------------------------------------- 1 | module C2CR 2 | module Constant 3 | def self.to_crystal(spelling) 4 | case spelling 5 | when "uint8_t" then "UInt8" 6 | when "int8_t" then "Int8" 7 | when "uint16_t" then "UInt16" 8 | when "int16_t" then "Int16" 9 | when "uint32_t" then "UInt32" 10 | when "int32_t" then "Int32" 11 | when "uint64_t" then "UInt64" 12 | when "int64_t" then "Int64" 13 | else 14 | spelling = spelling[6..-1] if spelling.starts_with?("const ") 15 | spelling = spelling.lstrip('_') if spelling.starts_with?('_') 16 | 17 | if spelling[0]?.try(&.ascii_uppercase?) 18 | spelling 19 | else 20 | spelling.camelcase 21 | end 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/clang/shard.yml: -------------------------------------------------------------------------------- 1 | name: clang 2 | version: 0.3.0 3 | 4 | authors: 5 | - Julien Portalier 6 | 7 | description: | 8 | Bindings for libclang 9 | 10 | targets: 11 | c2cr: 12 | main: samples/c2cr.cr 13 | 14 | license: Apache-2.0 15 | -------------------------------------------------------------------------------- /lib/clang/src/clang-c/BuildSystem.cr: -------------------------------------------------------------------------------- 1 | lib LibC 2 | # LLVM_CLANG_C_BUILDSYSTEM_H = 3 | fun clang_getBuildSessionTimestamp() : ULongLong 4 | type CXVirtualFileOverlayImpl = Void 5 | alias CXVirtualFileOverlay = CXVirtualFileOverlayImpl* 6 | fun clang_VirtualFileOverlay_create(UInt) : CXVirtualFileOverlay 7 | fun clang_VirtualFileOverlay_addFileMapping(CXVirtualFileOverlay, Char*, Char*) : CXErrorCode 8 | fun clang_VirtualFileOverlay_setCaseSensitivity(CXVirtualFileOverlay, Int) : CXErrorCode 9 | fun clang_VirtualFileOverlay_writeToBuffer(CXVirtualFileOverlay, UInt, Char**, UInt*) : CXErrorCode 10 | fun clang_free(Void*) : Void 11 | fun clang_VirtualFileOverlay_dispose(CXVirtualFileOverlay) : Void 12 | type CXModuleMapDescriptorImpl = Void 13 | alias CXModuleMapDescriptor = CXModuleMapDescriptorImpl* 14 | fun clang_ModuleMapDescriptor_create(UInt) : CXModuleMapDescriptor 15 | fun clang_ModuleMapDescriptor_setFrameworkModuleName(CXModuleMapDescriptor, Char*) : CXErrorCode 16 | fun clang_ModuleMapDescriptor_setUmbrellaHeader(CXModuleMapDescriptor, Char*) : CXErrorCode 17 | fun clang_ModuleMapDescriptor_writeToBuffer(CXModuleMapDescriptor, UInt, Char**, UInt*) : CXErrorCode 18 | fun clang_ModuleMapDescriptor_dispose(CXModuleMapDescriptor) : Void 19 | end 20 | -------------------------------------------------------------------------------- /lib/clang/src/clang-c/CXErrorCode.cr: -------------------------------------------------------------------------------- 1 | lib LibC 2 | # LLVM_CLANG_C_CXERRORCODE_H = 3 | enum CXErrorCode : UInt 4 | Success = 0 5 | Failure = 1 6 | Crashed = 2 7 | InvalidArguments = 3 8 | ASTReadError = 4 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/clang/src/clang-c/CXString.cr: -------------------------------------------------------------------------------- 1 | lib LibC 2 | # LLVM_CLANG_C_CXSTRING_H = 3 | struct CXString 4 | data : Void* 5 | private_flags : UInt 6 | end 7 | struct CXStringSet 8 | strings : CXString* 9 | count : UInt 10 | end 11 | fun clang_getCString(CXString) : Char* 12 | fun clang_disposeString(CXString) : Void 13 | fun clang_disposeStringSet(CXStringSet*) : Void 14 | end 15 | -------------------------------------------------------------------------------- /lib/clang/src/clang-c/Platform.cr: -------------------------------------------------------------------------------- 1 | lib LibC 2 | # LLVM_CLANG_C_PLATFORM_H = 3 | # CINDEX_LINKAGE = 4 | # CINDEX_DEPRECATED = __attribute__(( deprecated)) 5 | end 6 | -------------------------------------------------------------------------------- /lib/clang/src/cursor_kind.cr: -------------------------------------------------------------------------------- 1 | enum LibC::CXCursorKind 2 | def declaration? 3 | LibC.clang_isDeclaration(self) == 1 4 | end 5 | 6 | def reference? 7 | LibC.clang_isReference(self) == 1 8 | end 9 | 10 | def expression? 11 | LibC.clang_isExpression(self) == 1 12 | end 13 | 14 | def statement? 15 | LibC.clang_isStatement(self) == 1 16 | end 17 | 18 | def attribute? 19 | LibC.clang_isAttribute(self) == 1 20 | end 21 | 22 | def invalid? 23 | LibC.clang_isInvalid(self) == 1 24 | end 25 | 26 | def translation_unit? 27 | LibC.clang_isTranslationUnit(self) == 1 28 | end 29 | 30 | def preprocessing? 31 | LibC.clang_isPreprocessing(self) == 1 32 | end 33 | 34 | def unexposed? 35 | LibC.clang_isUnexposed(self) == 1 36 | end 37 | 38 | def spelling 39 | Clang.string LibC.clang_getCursorKindSpelling(self) 40 | end 41 | end 42 | 43 | module Clang 44 | alias CursorKind = LibC::CXCursorKind 45 | end 46 | -------------------------------------------------------------------------------- /lib/clang/src/eval_result.cr: -------------------------------------------------------------------------------- 1 | module Clang 2 | class EvalResult 3 | alias Kind = LibC::CXEvalResultKind 4 | 5 | def initialize(@result : LibC::CXEvalResult) 6 | end 7 | 8 | def kind 9 | LibC.clang_evalResult_getKind(self) 10 | end 11 | 12 | def as_int 13 | LibC.clang_evalResult_getAsInt(self) 14 | end 15 | 16 | def unsigned? 17 | LibC.clang_evalResult_isUnsignedInt(self) != 0 18 | end 19 | 20 | def as_unsigned 21 | LibC.clang_evalResult_getAsUnsigned(self) 22 | end 23 | 24 | def as_long_long 25 | LibC.clang_evalResult_getAsLongLong(self) 26 | end 27 | 28 | def as_double 29 | LibC.clang_evalResult_getAsDouble(self) 30 | end 31 | 32 | def as_str 33 | Clang.string(LibC.clang_evalResult_getAsStr(self)) 34 | end 35 | 36 | def finalize 37 | LibC.clang_evalResult_dispose(self) 38 | end 39 | 40 | def to_unsafe 41 | @result 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/clang/src/file.cr: -------------------------------------------------------------------------------- 1 | module Clang 2 | struct File 3 | def initialize(@file : LibC::CXFile) 4 | end 5 | 6 | def ==(other : File) 7 | LibC.clang_File_isEqual(self, other) != 0 8 | end 9 | 10 | def ==(other) 11 | false 12 | end 13 | 14 | def name 15 | Clang.string(LibC.clang_getFileName(self)) 16 | end 17 | 18 | def time 19 | Time.epoch(LibC.clang_getFileTime(self)) 20 | end 21 | 22 | def unique_id 23 | ret = LibC.clang_getFileUniqueID(self, out uid) 24 | raise Error.new("clang_getFileUniqueID failure") unless ret == 0 25 | uid 26 | end 27 | 28 | # NOTE: since clang 7+ 29 | def try_get_real_path_name 30 | Clang.string(LibC.clang_File_tryGetRealPathName(self)) 31 | end 32 | 33 | def to_unsafe 34 | @file 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/clang/src/index.cr: -------------------------------------------------------------------------------- 1 | module Clang 2 | class Index 3 | alias GlobalOptions = LibC::CXGlobalOptFlags 4 | 5 | # Creates a new Index. 6 | # 7 | # - *exclude_declarations_from_pch*: allow enumeration of "local" 8 | # declarations (when loading any new translation units). A "local" 9 | # declaration is one that belongs in the translation unit itself and not 10 | # in a precompiled header that was used by the translation unit. If false, 11 | # all declarations will be enumerated. 12 | def initialize(exclude_declarations_from_pch = false, display_diagnostics = true) 13 | @index = LibC.clang_createIndex(exclude_declarations_from_pch ? 1 : 0, display_diagnostics ? 1 : 0) 14 | end 15 | 16 | def finalize 17 | LibC.clang_disposeIndex(self) 18 | end 19 | 20 | def global_options 21 | LibC.clang_CXIndex_getGlobalOptions(self) 22 | end 23 | 24 | def global_options=(value : GlobalOptions) 25 | LibC.clang_CXIndex_setGlobalOptions(self, value) 26 | value 27 | end 28 | 29 | def set_invocation_emission_path_option(path : String) 30 | LibC.clang_CXIndex_setInvocationEmissionPathOption(self, path) 31 | end 32 | 33 | def to_unsafe 34 | @index 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/clang/src/lib_clang.cr: -------------------------------------------------------------------------------- 1 | require "./clang-c/*" 2 | 3 | {% begin %} 4 | lib LibC 5 | LLVM_CONFIG = {{ 6 | `[ -n "$LLVM_CONFIG" ] && command -v "$LLVM_CONFIG" || \ 7 | command -v llvm-config-8 || \ 8 | command -v llvm-config-7 || \ 9 | command -v llvm-config-6.0 || command -v llvm-config60 || \ 10 | command -v llvm-config-5.0 || command -v llvm-config50 || \ 11 | command -v llvm-config-4.0 || command -v llvm-config40 || \ 12 | command -v llvm-config 13 | `.chomp.stringify 14 | }} 15 | end 16 | {% end %} 17 | 18 | {% begin %} 19 | {% if flag?(:static) %} 20 | @[Link("clang", ldflags: "`{{LibC::LLVM_CONFIG.id}} --ldflags --link-static 2> /dev/null`")] 21 | {% else %} 22 | @[Link("clang", ldflags: "`{{LibC::LLVM_CONFIG.id}} --ldflags 2> /dev/null`")] 23 | {% end %} 24 | {% end %} 25 | lib LibC 26 | end 27 | -------------------------------------------------------------------------------- /lib/clang/src/macros.cr: -------------------------------------------------------------------------------- 1 | macro c_include(path, remove_enum_prefix = false, remove_enum_suffix = false) 2 | {{ `bin/c2cr #{path} --remove-enum-prefix=#{remove_enum_prefix} --remove-enum-suffix=#{remove_enum_suffix}` }} 3 | end 4 | -------------------------------------------------------------------------------- /lib/clang/src/platform_availability.cr: -------------------------------------------------------------------------------- 1 | module Clang 2 | class PlatformAvailability 3 | def initialize(@platform : LibC::CXPlatformAvailability) 4 | end 5 | 6 | def finalize 7 | LibC.clang_disposeCXPlatformAvailability(self) 8 | end 9 | 10 | def platform 11 | Clang.string(@platform.platform) 12 | end 13 | 14 | def introduced 15 | @platform.introduced 16 | end 17 | 18 | def deprecated 19 | @platform.deprecated 20 | end 21 | 22 | def obsoleted 23 | @platform.obsoleted 24 | end 25 | 26 | def unavailable 27 | @platform.unavailable == 1 28 | end 29 | 30 | def message 31 | Clang.string(@platform.message) 32 | end 33 | 34 | def to_unsafe 35 | @platform 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/clang/src/printing_policy.cr: -------------------------------------------------------------------------------- 1 | module Clang 2 | # NOTE: since clang 7+ 3 | class PrintingPolicy 4 | alias Property = LibC::CXPrintingPolicyProperty 5 | 6 | def initialize(@printing_policy = LibC::CXPrintingPolicy) 7 | end 8 | 9 | def finalize 10 | LibC.clang_PrintingPolicy_dispose(self) 11 | end 12 | 13 | def get_property(property : Property) 14 | LibC.clang_PrintingPolicy_getProperty(self, property) 15 | end 16 | 17 | def set_property(property : Property, value : UInt32) 18 | LibC.clang_PrintingPolicy_setProperty(self, property, value) 19 | end 20 | 21 | def to_unsafe 22 | @printing_policy 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/clang/src/token.cr: -------------------------------------------------------------------------------- 1 | require "./source_location" 2 | 3 | module Clang 4 | struct Token 5 | Kind = LibC::CXKind 6 | 7 | def initialize(@translation_unit : TranslationUnit, @token : LibC::CXToken) 8 | end 9 | 10 | def kind 11 | LibC.clang_getTokenKind(self) 12 | end 13 | 14 | def spelling 15 | Clang.string(LibC.clang_getTokenSpelling(@translation_unit, self)) 16 | end 17 | 18 | def location 19 | SourceLocation.new(LibC.clang_getTokenLocation(@translation_unit, self)) 20 | end 21 | 22 | def extent 23 | LibC.clang_getTokenExtent(@translation_unit, self) 24 | end 25 | 26 | def to_unsafe 27 | @token 28 | end 29 | 30 | def inspect(io) 31 | io << "<##{self.class.name} kind=" 32 | io << kind 33 | io << " spelling=" 34 | io << spelling 35 | io << '>' 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/clang/src/unsaved_file.cr: -------------------------------------------------------------------------------- 1 | module Clang 2 | struct UnsavedFile 3 | def initialize(filename, contents) 4 | @file = LibC::CXUnsavedFile.new 5 | @file.filename = filename 6 | @file.contents = contents 7 | @file.length = contents.bytesize 8 | end 9 | 10 | def filename 11 | String.new(@file.filename) 12 | end 13 | 14 | def to_unsafe 15 | @file 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/clim/.github/workflows/format.yml: -------------------------------------------------------------------------------- 1 | name: clim format-check 2 | 3 | on: [push] 4 | 5 | jobs: 6 | format-check: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: checkout 10 | uses: actions/checkout@v2 11 | - name: format-check 12 | run: make format-check 13 | -------------------------------------------------------------------------------- /lib/clim/.github/workflows/spec1.yml: -------------------------------------------------------------------------------- 1 | name: clim spec1 2 | 3 | on: [push] 4 | 5 | jobs: 6 | spec: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: checkout 10 | uses: actions/checkout@v2 11 | - name: spec 12 | run: make spec/1 NUM_OF_JOBS=5 13 | -------------------------------------------------------------------------------- /lib/clim/.github/workflows/spec2.yml: -------------------------------------------------------------------------------- 1 | name: clim spec2 2 | 3 | on: [push] 4 | 5 | jobs: 6 | spec: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: checkout 10 | uses: actions/checkout@v2 11 | - name: spec 12 | run: make spec/2 NUM_OF_JOBS=5 13 | -------------------------------------------------------------------------------- /lib/clim/.github/workflows/spec3.yml: -------------------------------------------------------------------------------- 1 | name: clim spec3 2 | 3 | on: [push] 4 | 5 | jobs: 6 | spec: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: checkout 10 | uses: actions/checkout@v2 11 | - name: spec 12 | run: make spec/3 NUM_OF_JOBS=5 13 | -------------------------------------------------------------------------------- /lib/clim/.github/workflows/spec4.yml: -------------------------------------------------------------------------------- 1 | name: clim spec4 2 | 3 | on: [push] 4 | 5 | jobs: 6 | spec: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: checkout 10 | uses: actions/checkout@v2 11 | - name: spec 12 | run: make spec/4 NUM_OF_JOBS=5 13 | -------------------------------------------------------------------------------- /lib/clim/.github/workflows/spec5.yml: -------------------------------------------------------------------------------- 1 | name: clim spec5 2 | 3 | on: [push] 4 | 5 | jobs: 6 | spec: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: checkout 10 | uses: actions/checkout@v2 11 | - name: spec 12 | run: make spec/5 NUM_OF_JOBS=5 13 | -------------------------------------------------------------------------------- /lib/clim/.gitignore: -------------------------------------------------------------------------------- 1 | /doc/ 2 | /bin/ 3 | /lib/ 4 | /.shards/ 5 | !/.vscode/ -------------------------------------------------------------------------------- /lib/clim/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 at-grandpa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/clim/Makefile: -------------------------------------------------------------------------------- 1 | NUM_OF_JOBS := 1 2 | SPEC_FILES := $(shell find spec -name '*_spec.cr' -print | sort -n) 3 | SPEC_TARGETS := $(shell seq -s " " -f "spec/%g" $(NUM_OF_JOBS)) 4 | 5 | spec: 6 | make -j $(SPEC_TARGETS) DOCKER_OPTIONS=-it 7 | 8 | format-check: 9 | docker run \ 10 | --rm \ 11 | $(DOCKER_OPTIONS) \ 12 | -v $(PWD):/workdir \ 13 | -w /workdir \ 14 | crystallang/crystal:latest \ 15 | /bin/sh -c "crystal tool format --check" 16 | 17 | .PHONY: spec format-check 18 | 19 | spec/%: 20 | docker run \ 21 | --rm \ 22 | $(DOCKER_OPTIONS) \ 23 | -v $(PWD):/workdir \ 24 | -w /workdir \ 25 | crystallang/crystal:latest \ 26 | /bin/sh -c "crystal eval 'array = \"$(SPEC_FILES)\".split(\" \"); puts array.map_with_index{|e,i| {index: i, value: e}}.group_by{|e| e[:index] % ($(NUM_OF_JOBS))}[$* - 1].map(&.[](:value)).join(\" \")' | xargs -d \" \" -I{} /bin/sh -c 'echo \"\n\n=========================\n{}\"; crystal spec {}'" 27 | -------------------------------------------------------------------------------- /lib/clim/lib: -------------------------------------------------------------------------------- 1 | .. -------------------------------------------------------------------------------- /lib/clim/shard.yml: -------------------------------------------------------------------------------- 1 | name: clim 2 | version: 0.13.0 3 | 4 | authors: 5 | - at-grandpa <@at_grandpa> 6 | 7 | targets: 8 | clim: 9 | main: src/clim.cr 10 | 11 | crystal: 0.35.1 12 | 13 | license: MIT 14 | -------------------------------------------------------------------------------- /lib/clim/spec/clim/compile_time_error_spec/files/bool_with_required_true.cr: -------------------------------------------------------------------------------- 1 | require "./../../../../src/clim" 2 | 3 | class MyCli < Clim 4 | main do 5 | desc "main command." 6 | option "-b", type: Bool, desc: "your bool.", required: true 7 | run do |options, arguments| 8 | end 9 | end 10 | end 11 | 12 | MyCli.start(ARGV) 13 | -------------------------------------------------------------------------------- /lib/clim/spec/clim/compile_time_error_spec/files/duplicate_argument_name_main.cr: -------------------------------------------------------------------------------- 1 | require "./../../../../src/clim" 2 | 3 | class MyCli < Clim 4 | main do 5 | argument "foo" 6 | argument "bar" 7 | argument "foo" # duplicate 8 | run do |opts, args| 9 | end 10 | sub "sub_command" do 11 | argument "bar" 12 | run do |opts, args| 13 | end 14 | end 15 | end 16 | end 17 | 18 | MyCli.start(ARGV) 19 | -------------------------------------------------------------------------------- /lib/clim/spec/clim/compile_time_error_spec/files/duplicate_argument_name_sub.cr: -------------------------------------------------------------------------------- 1 | require "./../../../../src/clim" 2 | 3 | class MyCli < Clim 4 | main do 5 | argument "foo" 6 | argument "bar" 7 | run do |opts, args| 8 | end 9 | sub "sub_command" do 10 | argument "foo" 11 | argument "bar" 12 | argument "foo" # duplicate 13 | run do |opts, args| 14 | end 15 | end 16 | end 17 | end 18 | 19 | MyCli.start(ARGV) 20 | -------------------------------------------------------------------------------- /lib/clim/spec/clim/compile_time_error_spec/files/duplicate_argument_name_sub_2.cr: -------------------------------------------------------------------------------- 1 | require "./../../../../src/clim" 2 | 3 | class MyCli < Clim 4 | main do 5 | argument "foo" 6 | argument "bar" 7 | run do |opts, args| 8 | end 9 | sub "sub_command" do 10 | argument "foo" 11 | argument "bar" 12 | run do |opts, args| 13 | end 14 | sub "sub_sub_command" do 15 | argument "foo" 16 | argument "bar" 17 | run do |opts, args| 18 | end 19 | end 20 | end 21 | sub "sub_command_2" do 22 | argument "foo" 23 | argument "bar" 24 | argument "foo" # deplicate 25 | run do |opts, args| 26 | end 27 | end 28 | end 29 | end 30 | 31 | MyCli.start(ARGV) 32 | -------------------------------------------------------------------------------- /lib/clim/spec/clim/compile_time_error_spec/files/duplicate_argument_name_sub_sub.cr: -------------------------------------------------------------------------------- 1 | require "./../../../../src/clim" 2 | 3 | class MyCli < Clim 4 | main do 5 | argument "foo" 6 | argument "bar" 7 | run do |opts, args| 8 | end 9 | sub "sub_command" do 10 | argument "foo" 11 | argument "bar" 12 | run do |opts, args| 13 | end 14 | sub "sub_sub_command" do 15 | argument "foo" 16 | argument "bar" 17 | argument "foo" # duplicate 18 | run do |opts, args| 19 | end 20 | end 21 | end 22 | end 23 | end 24 | 25 | MyCli.start(ARGV) 26 | -------------------------------------------------------------------------------- /lib/clim/spec/clim/compile_time_error_spec/files/duplicate_main.cr: -------------------------------------------------------------------------------- 1 | require "./../../../../src/clim" 2 | 3 | class MyCli < Clim 4 | main do 5 | run do |opts, args| 6 | end 7 | end 8 | 9 | main do 10 | end 11 | end 12 | 13 | MyCli.start(ARGV) 14 | -------------------------------------------------------------------------------- /lib/clim/spec/clim/compile_time_error_spec/files/duplicate_main_in_sub_command.cr: -------------------------------------------------------------------------------- 1 | require "./../../../../src/clim" 2 | 3 | class MyCli < Clim 4 | main do 5 | run do |opts, args| 6 | end 7 | main do 8 | end 9 | end 10 | end 11 | 12 | MyCli.start(ARGV) 13 | -------------------------------------------------------------------------------- /lib/clim/spec/clim/compile_time_error_spec/files/duplicate_sub_command.cr: -------------------------------------------------------------------------------- 1 | require "./../../../../src/clim" 2 | 3 | class MyCli < Clim 4 | main do 5 | run do |opts, args| 6 | end 7 | command "sub_command" do 8 | run do |opts, args| 9 | end 10 | end 11 | # Duplicate name. 12 | command "sub_command" do 13 | run do |opts, args| 14 | end 15 | end 16 | end 17 | end 18 | 19 | MyCli.start(ARGV) 20 | -------------------------------------------------------------------------------- /lib/clim/spec/clim/compile_time_error_spec/files/empty_argument_name.cr: -------------------------------------------------------------------------------- 1 | require "./../../../../src/clim" 2 | require "big" 3 | 4 | class MyCli < Clim 5 | main do 6 | argument "", type: String, desc: "empty option name." 7 | run do |opts, args| 8 | end 9 | end 10 | end 11 | 12 | MyCli.start(ARGV) 13 | -------------------------------------------------------------------------------- /lib/clim/spec/clim/compile_time_error_spec/files/empty_option_name.cr: -------------------------------------------------------------------------------- 1 | require "./../../../../src/clim" 2 | require "big" 3 | 4 | class MyCli < Clim 5 | main do 6 | option "", type: String, desc: "empty option name." 7 | run do |opts, args| 8 | end 9 | end 10 | end 11 | 12 | MyCli.start(ARGV) 13 | -------------------------------------------------------------------------------- /lib/clim/spec/clim/compile_time_error_spec/files/exception_message.cr: -------------------------------------------------------------------------------- 1 | require "./../../../../src/clim" 2 | 3 | module Hello 4 | class Cli < Clim 5 | main do 6 | usage "hello " 7 | option "--prefix ", type: String, desc: "Prefix.", required: true 8 | argument "arg1", type: String, desc: "argument1", required: true 9 | run do |opts, args| 10 | puts "ok" 11 | end 12 | end 13 | end 14 | end 15 | 16 | Hello::Cli.start(ARGV) 17 | -------------------------------------------------------------------------------- /lib/clim/spec/clim/compile_time_error_spec/files/help_directive_does_not_have_a_short_argument_for_main.cr: -------------------------------------------------------------------------------- 1 | require "./../../../../src/clim" 2 | 3 | class MyCli < Clim 4 | main do 5 | help 6 | run do |opts, args| 7 | end 8 | end 9 | end 10 | 11 | MyCli.start(ARGV) 12 | -------------------------------------------------------------------------------- /lib/clim/spec/clim/compile_time_error_spec/files/main_default_help.cr: -------------------------------------------------------------------------------- 1 | require "./../../../../src/clim" 2 | 3 | class MyCli < Clim 4 | main do 5 | run do |opts, args| 6 | end 7 | sub "sub_command" do 8 | desc "sub_comand." 9 | option "-n NUM", type: Int32, desc: "Number.", default: 0 10 | argument "arg1", type: Bool, desc: "argument1" 11 | run do |opts, args| 12 | end 13 | sub "sub_sub_command" do 14 | desc "sub_sub_comand description." 15 | option "-p PASSWORD", type: String, desc: "Password.", required: true 16 | run do |opts, args| 17 | end 18 | end 19 | end 20 | end 21 | end 22 | 23 | MyCli.start(ARGV) 24 | -------------------------------------------------------------------------------- /lib/clim/spec/clim/compile_time_error_spec/files/main_is_not_defined.cr: -------------------------------------------------------------------------------- 1 | require "./../../../../src/clim" 2 | 3 | class MyCli < Clim 4 | sub do 5 | run do |opts, args| 6 | end 7 | end 8 | end 9 | 10 | MyCli.start(ARGV) 11 | -------------------------------------------------------------------------------- /lib/clim/spec/clim/compile_time_error_spec/files/main_with_alias_name.cr: -------------------------------------------------------------------------------- 1 | require "./../../../../src/clim" 2 | 3 | class MyCli < Clim 4 | main do 5 | alias_name "main2" 6 | run do |opts, args| 7 | end 8 | end 9 | end 10 | 11 | MyCli.start(ARGV) 12 | -------------------------------------------------------------------------------- /lib/clim/spec/clim/compile_time_error_spec/files/main_without_run_block.cr: -------------------------------------------------------------------------------- 1 | require "./../../../../src/clim" 2 | 3 | class MyCli < Clim 4 | main do 5 | end 6 | end 7 | 8 | MyCli.start(ARGV) 9 | -------------------------------------------------------------------------------- /lib/clim/spec/clim/compile_time_error_spec/files/not_supported_argument_type.cr: -------------------------------------------------------------------------------- 1 | require "./../../../../src/clim" 2 | require "big" 3 | 4 | class MyCli < Clim 5 | main do 6 | argument "not", type: BigInt, desc: "my big int.", default: 0 7 | run do |opts, args| 8 | end 9 | end 10 | end 11 | 12 | MyCli.start(ARGV) 13 | -------------------------------------------------------------------------------- /lib/clim/spec/clim/compile_time_error_spec/files/not_supported_option_type.cr: -------------------------------------------------------------------------------- 1 | require "./../../../../src/clim" 2 | require "big" 3 | 4 | class MyCli < Clim 5 | main do 6 | option "-n", type: BigInt, desc: "my big int.", default: 0 7 | run do |opts, args| 8 | end 9 | end 10 | end 11 | 12 | MyCli.start(ARGV) 13 | -------------------------------------------------------------------------------- /lib/clim/spec/clim/compile_time_error_spec/files/sub_2_command_without_run_block.cr: -------------------------------------------------------------------------------- 1 | require "./../../../../src/clim" 2 | 3 | class MyCli < Clim 4 | main do 5 | run do |opts, args| 6 | end 7 | sub "sub" do 8 | run do |opts, args| 9 | end 10 | sub "sub_sub" do 11 | run do |opts, args| 12 | end 13 | end 14 | end 15 | sub "sub2" do 16 | end 17 | end 18 | end 19 | 20 | MyCli.start(ARGV) 21 | -------------------------------------------------------------------------------- /lib/clim/spec/clim/compile_time_error_spec/files/sub_command_with_help_template.cr: -------------------------------------------------------------------------------- 1 | require "./../../../../src/clim" 2 | 3 | class MyCli < Clim 4 | main do 5 | run do |opts, args| 6 | end 7 | sub "sub_command" do 8 | help_template do |desc, usage, options, sub_commands| 9 | <<-MY_HELP 10 | 11 | command description: #{desc} 12 | command usage: #{usage} 13 | 14 | options: 15 | #{options.map(&.[](:help_line)).join("\n")} 16 | 17 | sub_commands: 18 | #{sub_commands.map(&.[](:help_line)).join("\n")} 19 | 20 | 21 | MY_HELP 22 | end 23 | desc "sub_comand." 24 | option "-n NUM", type: Int32, desc: "Number.", default: 0 25 | run do |opts, args| 26 | end 27 | sub "sub_sub_command" do 28 | desc "sub_sub_comand description." 29 | option "-p PASSWORD", type: String, desc: "Password.", required: true 30 | run do |opts, args| 31 | end 32 | end 33 | end 34 | end 35 | end 36 | 37 | MyCli.start(ARGV) 38 | -------------------------------------------------------------------------------- /lib/clim/spec/clim/compile_time_error_spec/files/sub_command_without_run_block.cr: -------------------------------------------------------------------------------- 1 | require "./../../../../src/clim" 2 | 3 | class MyCli < Clim 4 | main do 5 | run do |opts, args| 6 | end 7 | sub "sub" do 8 | end 9 | end 10 | end 11 | 12 | MyCli.start(ARGV) 13 | -------------------------------------------------------------------------------- /lib/clim/spec/clim/compile_time_error_spec/files/sub_sub_command_without_run_block.cr: -------------------------------------------------------------------------------- 1 | require "./../../../../src/clim" 2 | 3 | class MyCli < Clim 4 | main do 5 | run do |opts, args| 6 | end 7 | sub "sub" do 8 | run do |opts, args| 9 | end 10 | sub "sub_sub" do 11 | end 12 | end 13 | end 14 | end 15 | 16 | MyCli.start(ARGV) 17 | -------------------------------------------------------------------------------- /lib/clim/spec/clim/dsl_spec/options/bool_07_spec.cr: -------------------------------------------------------------------------------- 1 | require "../../dsl_spec" 2 | 3 | {% begin %} 4 | {% 5 | main_help_message = <<-HELP_MESSAGE 6 | 7 | Command Line Interface Tool. 8 | 9 | Usage: 10 | 11 | main_of_clim_library [options] [arguments] 12 | 13 | Options: 14 | 15 | -b, --bool Bool option description. [type:Bool] 16 | --help Show this help. 17 | 18 | 19 | HELP_MESSAGE 20 | %} 21 | 22 | spec( 23 | spec_class_name: MainCommandWithBoolDesc, 24 | spec_dsl_lines: [ 25 | "option \"-b\", \"--bool\", type: Bool, desc: \"Bool option description.\"", 26 | ], 27 | spec_desc: "main command with Bool option,", 28 | spec_cases: [ 29 | { 30 | argv: ["--help"], 31 | expect_help: {{main_help_message}}, 32 | }, 33 | { 34 | argv: ["--help", "ignore-arg"], 35 | expect_help: {{main_help_message}}, 36 | }, 37 | { 38 | argv: ["ignore-arg", "--help"], 39 | expect_help: {{main_help_message}}, 40 | }, 41 | ] 42 | ) 43 | {% end %} 44 | -------------------------------------------------------------------------------- /lib/clim/spec/clim/dsl_spec/options/string_04_spec.cr: -------------------------------------------------------------------------------- 1 | require "../../dsl_spec" 2 | 3 | {% begin %} 4 | {% 5 | main_help_message = <<-HELP_MESSAGE 6 | 7 | Command Line Interface Tool. 8 | 9 | Usage: 10 | 11 | main_of_clim_library [options] [arguments] 12 | 13 | Options: 14 | 15 | -s ARG, --string=ARG String option description. [type:String] 16 | --help Show this help. 17 | 18 | 19 | HELP_MESSAGE 20 | %} 21 | 22 | spec( 23 | spec_class_name: MainCommandWithStringDesc, 24 | spec_dsl_lines: [ 25 | "option \"-s ARG\", \"--string=ARG\", type: String, desc: \"String option description.\"", 26 | ], 27 | spec_desc: "main command with String options,", 28 | spec_cases: [ 29 | { 30 | argv: ["--help"], 31 | expect_help: {{main_help_message}}, 32 | }, 33 | { 34 | argv: ["--help", "ignore-arg"], 35 | expect_help: {{main_help_message}}, 36 | }, 37 | { 38 | argv: ["ignore-arg", "--help"], 39 | expect_help: {{main_help_message}}, 40 | }, 41 | ] 42 | ) 43 | {% end %} 44 | -------------------------------------------------------------------------------- /lib/clim/spec/clim/dsl_spec/sub_command/sub_command.cr: -------------------------------------------------------------------------------- 1 | require "../../dsl_spec" 2 | 3 | macro spec_for_sub_command(spec_class_name, spec_cases) 4 | {% for spec_case, index in spec_cases %} 5 | {% class_name = (spec_class_name.stringify + index.stringify).id %} 6 | 7 | # define dsl 8 | class {{class_name}} < Clim 9 | main do 10 | version "version 1.0.0", short: "-v" 11 | run do |opts, args| 12 | assert_opts_and_args({{spec_case}}) 13 | end 14 | sub "sub_command_1" do 15 | version "version 1.0.0", short: "-v" 16 | option "-a ARG", "--array=ARG", desc: "Option test.", type: Array(String), default: ["default string"] 17 | run do |opts, args| 18 | assert_opts_and_args({{spec_case}}) 19 | end 20 | sub "sub_sub_command_1" do 21 | option "-b", "--bool", type: Bool, desc: "Bool test." 22 | run do |opts, args| 23 | assert_opts_and_args({{spec_case}}) 24 | end 25 | end 26 | end 27 | sub "sub_command_2" do 28 | run do |opts, args| 29 | assert_opts_and_args({{spec_case}}) 30 | end 31 | end 32 | end 33 | end 34 | 35 | # spec 36 | describe "alias name case," do 37 | describe "if argv is " + {{spec_case["argv"].stringify}} + "," do 38 | it_blocks({{class_name}}, {{spec_case}}) 39 | end 40 | end 41 | {% end %} 42 | end 43 | -------------------------------------------------------------------------------- /lib/clim/spec/clim/extension_spec.cr: -------------------------------------------------------------------------------- 1 | require "./../spec_helper" 2 | 3 | describe Array do 4 | describe "#duplicate_value" do 5 | it "retuens duplicate value." do 6 | ["a", "b", "a"].duplicate_value.should eq ["a"] 7 | ["a", "b", "a", "b", "c"].duplicate_value.should eq ["a", "b"] 8 | [1, 1, 2, 2, 3].duplicate_value.should eq [1, 2] 9 | [1, 1, "a", "a", 3].duplicate_value.should eq [1, "a"] 10 | [1, "a", 2, "b"].duplicate_value.should eq [] of String | Int32 11 | ([] of String | Int32).duplicate_value.should eq [] of String | Int32 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/clim/spec/clim/readme/files/alias_name.cr: -------------------------------------------------------------------------------- 1 | require "./../../../../src/clim" 2 | 3 | class MyCli < Clim 4 | main do 5 | run do |opts, args| 6 | # ... 7 | end 8 | sub "sub" do 9 | alias_name "alias1", "alias2" 10 | run do |opts, args| 11 | puts "sub_command run!!" 12 | end 13 | end 14 | end 15 | end 16 | 17 | MyCli.start(ARGV) 18 | -------------------------------------------------------------------------------- /lib/clim/spec/clim/readme/files/argument.cr: -------------------------------------------------------------------------------- 1 | require "./../../../../src/clim" 2 | 3 | class MyCli < Clim 4 | main do 5 | desc "argument sample" 6 | usage "command [options] [arguments]" 7 | 8 | option "--dummy=WORDS", 9 | desc: "dummy option" 10 | 11 | argument "first-arg", 12 | desc: "first argument!", 13 | type: String, 14 | default: "default value" 15 | 16 | argument "second-arg", 17 | desc: "second argument!", 18 | type: Int32, 19 | default: 999 20 | 21 | run do |opts, args| 22 | puts "typeof(args.first_arg) => #{typeof(args.first_arg)}" 23 | puts " args.first_arg => #{args.first_arg}" 24 | puts "typeof(args.second_arg) => #{typeof(args.second_arg)}" 25 | puts " args.second_arg => #{args.second_arg}" 26 | puts "typeof(args.all_args) => #{typeof(args.all_args)}" 27 | puts " args.all_args => #{args.all_args}" 28 | puts "typeof(args.unknown_args) => #{typeof(args.unknown_args)}" 29 | puts " args.unknown_args => #{args.unknown_args}" 30 | puts "typeof(args.argv) => #{typeof(args.argv)}" 31 | puts " args.argv => #{args.argv}" 32 | end 33 | end 34 | end 35 | 36 | MyCli.start(ARGV) 37 | -------------------------------------------------------------------------------- /lib/clim/spec/clim/readme/files/fake-crystal-command.cr: -------------------------------------------------------------------------------- 1 | require "./../../../../src/clim" 2 | 3 | module FakeCrystalCommand 4 | class Cli < Clim 5 | main do 6 | desc "Fake Crystal command." 7 | usage "fcrystal [sub_command] [arguments]" 8 | run do |opts, args| 9 | puts opts.help_string # => help string. 10 | end 11 | sub "tool" do 12 | desc "run a tool" 13 | usage "fcrystal tool [tool] [arguments]" 14 | run do |opts, args| 15 | puts "Fake Crystal tool!!" 16 | end 17 | sub "format" do 18 | desc "format project, directories and/or files" 19 | usage "fcrystal tool format [options] [file or directory]" 20 | run do |opts, args| 21 | puts "Fake Crystal tool format!!" 22 | end 23 | end 24 | end 25 | sub "spec" do 26 | desc "build and run specs" 27 | usage "fcrystal spec [options] [files]" 28 | run do |opts, args| 29 | puts "Fake Crystal spec!!" 30 | end 31 | end 32 | end 33 | end 34 | end 35 | 36 | FakeCrystalCommand::Cli.start(ARGV) 37 | -------------------------------------------------------------------------------- /lib/clim/spec/clim/readme/files/hello.cr: -------------------------------------------------------------------------------- 1 | require "./../../../../src/clim" 2 | 3 | module Hello 4 | class Cli < Clim 5 | main do 6 | desc "Hello CLI tool." 7 | usage "hello [options] [arguments] ..." 8 | version "Version 0.1.0" 9 | option "-g WORDS", "--greeting=WORDS", type: String, desc: "Words of greetings.", default: "Hello" 10 | argument "first_member", type: String, desc: "first member name.", default: "member1" 11 | argument "second_member", type: String, desc: "second member name.", default: "member2" 12 | run do |opts, args| 13 | print "#{opts.greeting}, " 14 | print "#{args.first_member} & #{args.second_member} !\n" 15 | print "And #{args.unknown_args.join(", ")} !" 16 | print "\n" 17 | end 18 | end 19 | end 20 | end 21 | 22 | Hello::Cli.start(ARGV) 23 | -------------------------------------------------------------------------------- /lib/clim/spec/clim/readme/files/help_short.cr: -------------------------------------------------------------------------------- 1 | require "./../../../../src/clim" 2 | 3 | class MyCli < Clim 4 | main do 5 | desc "help directive test." 6 | usage "mycli [options] [arguments]" 7 | help short: "-h" 8 | run do |opts, args| 9 | # ... 10 | end 11 | end 12 | end 13 | 14 | MyCli.start(ARGV) 15 | -------------------------------------------------------------------------------- /lib/clim/spec/clim/readme/files/io_in_run_block.cr: -------------------------------------------------------------------------------- 1 | require "./../../../../src/clim" 2 | 3 | class IoCommand < Clim 4 | main do 5 | run do |opts, args, io| 6 | io.puts "in main" 7 | end 8 | end 9 | end 10 | 11 | io = IO::Memory.new 12 | IoCommand.start([] of String, io: io) 13 | puts io.to_s # => "in main\n" 14 | -------------------------------------------------------------------------------- /lib/clim/spec/clim/readme/files/minimum.cr: -------------------------------------------------------------------------------- 1 | require "./../../../../src/clim" 2 | 3 | class MyCli < Clim 4 | main do 5 | run do |opts, args| 6 | puts "#{args.all_args.join(", ")}!" 7 | end 8 | end 9 | end 10 | 11 | MyCli.start(ARGV) 12 | -------------------------------------------------------------------------------- /lib/clim/spec/clim/readme/files/version.cr: -------------------------------------------------------------------------------- 1 | require "./../../../../src/clim" 2 | 3 | class MyCli < Clim 4 | main do 5 | version "mycli version: 1.0.1" 6 | run do |opts, args| 7 | # ... 8 | end 9 | end 10 | end 11 | 12 | MyCli.start(ARGV) 13 | -------------------------------------------------------------------------------- /lib/clim/spec/clim/readme/files/version_short.cr: -------------------------------------------------------------------------------- 1 | require "./../../../../src/clim" 2 | 3 | class MyCli < Clim 4 | main do 5 | version "mycli version: 1.0.1", short: "-v" 6 | run do |opts, args| 7 | # ... 8 | end 9 | end 10 | end 11 | 12 | MyCli.start(ARGV) 13 | -------------------------------------------------------------------------------- /lib/clim/spec/spec_helper.cr: -------------------------------------------------------------------------------- 1 | require "spec" 2 | require "../src/clim" 3 | -------------------------------------------------------------------------------- /lib/clim/src/clim.cr: -------------------------------------------------------------------------------- 1 | require "./clim/*" 2 | 3 | class Clim 4 | include Types 5 | 6 | macro main(&block) 7 | 8 | Clim::Command.command "main_of_clim_library" do 9 | {{ yield }} 10 | end 11 | 12 | def self.command 13 | Command_Main_of_clim_library.create 14 | end 15 | 16 | def self.start_parse(argv, io : IO = STDOUT) 17 | command.parse(argv).run(io) 18 | end 19 | 20 | def self.start(argv, io : IO = STDOUT) 21 | start_parse(argv, io) 22 | rescue ex : ClimException 23 | puts "ERROR: #{ex.message}" 24 | rescue ex : ClimInvalidOptionException | ClimInvalidTypeCastException 25 | puts "ERROR: #{ex.message}" 26 | puts "" 27 | puts "Please see the `--help`." 28 | end 29 | 30 | {% if @type.constants.map(&.id.stringify).includes?("Command_Main_of_clim_library") %} 31 | {% raise "Main command is already defined." %} 32 | {% end %} 33 | 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/clim/src/clim/command/sub_commands.cr: -------------------------------------------------------------------------------- 1 | require "./options/*" 2 | require "../exception" 3 | require "option_parser" 4 | 5 | class Clim 6 | abstract class Command 7 | class SubCommands 8 | @sub_commands : Array(Command) 9 | 10 | def initialize(@sub_commands : Array(Command) = [] of Command) 11 | end 12 | 13 | def <<(command : Command) 14 | @sub_commands << command 15 | end 16 | 17 | def to_a 18 | @sub_commands 19 | end 20 | 21 | def help_info 22 | @sub_commands.map do |cmd| 23 | { 24 | names: cmd.names, 25 | desc: cmd.desc, 26 | help_line: help_line_of(cmd), 27 | } 28 | end 29 | end 30 | 31 | private def help_line_of(cmd) 32 | names_and_spaces = cmd.names.join(", ") + 33 | "#{" " * (max_name_length - cmd.names.join(", ").size)}" 34 | " #{names_and_spaces} #{cmd.desc}" 35 | end 36 | 37 | private def max_name_length 38 | @sub_commands.empty? ? 0 : @sub_commands.map(&.names.join(", ").size).max 39 | end 40 | 41 | def find_by_name(name) : Array(Command) 42 | @sub_commands.select do |cmd| 43 | cmd.name == name || cmd.alias_name.includes?(name) 44 | end 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/clim/src/clim/exception.cr: -------------------------------------------------------------------------------- 1 | class Clim 2 | class ClimException < Exception 3 | end 4 | 5 | class ClimInvalidOptionException < Exception 6 | end 7 | 8 | class ClimInvalidTypeCastException < Exception 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/clim/src/clim/extension.cr: -------------------------------------------------------------------------------- 1 | class Array(T) 2 | def duplicate_value 3 | group_by { |i| i }.reject { |_, v| v.size == 1 }.keys 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/clim/src/clim/version.cr: -------------------------------------------------------------------------------- 1 | class Clim 2 | VERSION = "0.13.0" 3 | end 4 | -------------------------------------------------------------------------------- /lib/lsp/lib: -------------------------------------------------------------------------------- 1 | .. -------------------------------------------------------------------------------- /lib/lsp/shard.yml: -------------------------------------------------------------------------------- 1 | name: lsp 2 | version: 0.1.0 3 | 4 | authors: 5 | - Joe Eli McIlvain 6 | 7 | crystal: 1.0.0 8 | 9 | license: MPLv2 10 | -------------------------------------------------------------------------------- /lib/lsp/spec/spec_helper.cr: -------------------------------------------------------------------------------- 1 | require "spec" 2 | require "../src/lsp" 3 | -------------------------------------------------------------------------------- /lib/lsp/src/lsp.cr: -------------------------------------------------------------------------------- 1 | require "./lsp/*" 2 | 3 | module LSP 4 | VERSION = "0.1.0" 5 | end 6 | -------------------------------------------------------------------------------- /lib/lsp/src/lsp/data.cr: -------------------------------------------------------------------------------- 1 | require "./data/*" 2 | -------------------------------------------------------------------------------- /lib/lsp/src/lsp/data/code_action_kind_set.cr: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | module LSP::Data 4 | struct CodeActionKindSet 5 | include JSON::Serializable 6 | 7 | # The code action kind values the client supports. When this 8 | # property exists the client also guarantees that it will 9 | # handle values outside its set gracefully and falls back 10 | # to a default value when unknown. 11 | @[JSON::Field(key: "valueSet")] 12 | property value_set : Array(String) = [] of String 13 | 14 | def initialize 15 | @value_set = [] of String 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/lsp/src/lsp/data/command.cr: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | module LSP::Data 4 | # Represents a reference to a command. Provides a title which will be used 5 | # to represent a command in the UI. Commands are identified by a string 6 | # identifier. 7 | # The protocol currently doesn't specify a set of well-known commands. 8 | # So executing a command requires some tool extension code. 9 | struct Command 10 | include JSON::Serializable 11 | 12 | # Title of the command, like `save`. 13 | property title : String 14 | 15 | # The identifier of the actual command handler. 16 | property command : String 17 | 18 | # Arguments that the command handler should be invoked with. 19 | property arguments : Array(JSON::Any) = [] of JSON::Any 20 | 21 | def initialize( 22 | @title = "", 23 | @command = "", 24 | @arguments = [] of JSON::Any 25 | ) 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/lsp/src/lsp/data/completion_context.cr: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | module LSP::Data 4 | # Contains additional information about the context in which a completion 5 | # request is triggered. 6 | struct CompletionContext 7 | include JSON::Serializable 8 | 9 | # How the completion was triggered. 10 | @[JSON::Field(key: "triggerKind", converter: Enum::ValueConverter(LSP::Data::CompletionTriggerKind))] 11 | property trigger_kind : CompletionTriggerKind 12 | 13 | # The trigger character (a single character) that has trigger code 14 | # complete. Is undefined if 15 | # `triggerKind !== CompletionTriggerKind.TriggerCharacter` 16 | @[JSON::Field(key: "triggerCharacter")] 17 | property trigger_character : String? 18 | 19 | def initialize( 20 | @trigger_kind = CompletionTriggerKind::Invoked, 21 | @trigger_character = nil 22 | ) 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/lsp/src/lsp/data/completion_item_kind.cr: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | module LSP::Data 4 | enum CompletionItemKind 5 | Text = 1 6 | Method = 2 7 | Function = 3 8 | Constructor = 4 9 | Field = 5 10 | Variable = 6 11 | Class = 7 12 | Interface = 8 13 | Module = 9 14 | Property = 10 15 | Unit = 11 16 | Value = 12 17 | Enum = 13 18 | Keyword = 14 19 | Snippet = 15 20 | Color = 16 21 | File = 17 22 | Reference = 18 23 | Folder = 19 24 | EnumMember = 20 25 | Constant = 21 26 | Struct = 22 27 | Event = 23 28 | Operator = 24 29 | TypeParameter = 25 30 | 31 | def self.default 32 | [ 33 | Text, Method, Function, Constructor, Field, Variable, 34 | Class, Interface, Module, Property, Unit, Value, Enum, Keyword, 35 | Snippet, Color, File, Reference, 36 | ] 37 | end 38 | 39 | def self.new(*args) 40 | ::Enum::ValueConverter(CompletionItemKind).from_json(*args) 41 | end 42 | 43 | def to_json(*args) 44 | ::Enum::ValueConverter(CompletionItemKind).to_json(self, *args) 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/lsp/src/lsp/data/completion_item_kind_set.cr: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | module LSP::Data 4 | struct CompletionItemKindSet 5 | include JSON::Serializable 6 | 7 | # The completion item kind values the client supports. When this 8 | # property exists the client also guarantees that it will 9 | # handle values outside its set gracefully and falls back 10 | # to a default value when unknown. 11 | # 12 | # If this property is not present the client only supports 13 | # the completion items kinds from `Text` to `Reference` as defined in 14 | # the initial version of the protocol. 15 | @[JSON::Field(key: "valueSet")] 16 | property value_set : Array(CompletionItemKind) = CompletionItemKind.default 17 | 18 | def initialize 19 | @value_set = CompletionItemKind.default 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/lsp/src/lsp/data/completion_list.cr: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | module LSP::Data 4 | # Represents a collection of [completion items](#CompletionItem) to be 5 | # presented in the editor. 6 | struct CompletionList 7 | include JSON::Serializable 8 | 9 | # This list it not complete. Further typing should result in recomputing 10 | # this list. 11 | @[JSON::Field(key: "isIncomplete")] 12 | property is_incomplete : Bool 13 | 14 | # The completion items. 15 | property items : Array(CompletionItem) 16 | 17 | def initialize( 18 | @is_incomplete = false, 19 | @items = [] of CompletionItem 20 | ) 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/lsp/src/lsp/data/completion_trigger_kind.cr: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | module LSP::Data 4 | # How a completion was triggered. 5 | enum CompletionTriggerKind 6 | # Completion was triggered by typing an identifier (24x7 code 7 | # complete), manual invocation (e.g Ctrl+Space) or via API. 8 | Invoked = 1 9 | 10 | # Completion was triggered by a trigger character specified by 11 | # the `triggerCharacters` properties of the `CompletionRegistrationOptions`. 12 | TriggerCharacter = 2 13 | 14 | # Completion was re-triggered as the current completion list is incomplete. 15 | TriggerForIncompleteCompletions = 3 16 | 17 | def self.new(*args) 18 | ::Enum::ValueConverter(CompletionTriggerKind).from_json(*args) 19 | end 20 | 21 | def to_json(*args) 22 | ::Enum::ValueConverter(CompletionTriggerKind).to_json(self, *args) 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/lsp/src/lsp/data/document_filter.cr: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | module LSP::Data 4 | # A document filter denotes a document through properties like language, 5 | # scheme or pattern. 6 | # 7 | # An example is a filter that applies to TypeScript files on disk. Another 8 | # example is a filter the applies to JSON files with name package.json: 9 | # { language: 'typescript', scheme: 'file' } 10 | # { language: 'json', pattern: '**/package.json' } 11 | struct DocumentFilter 12 | include JSON::Serializable 13 | 14 | # A language id, like `typescript`. 15 | property language : String? 16 | 17 | # A Uri [scheme](#Uri.scheme), like `file` or `untitled`. 18 | property scheme : String? 19 | 20 | # A glob pattern, like `*.{ts,js}`. 21 | property pattern : String? 22 | 23 | def initialize(@language = nil, @scheme = nil, @pattern = nil) 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/lsp/src/lsp/data/dynamic_registration.cr: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | module LSP::Data 4 | struct DynamicRegistration 5 | include JSON::Serializable 6 | 7 | # The capability includes dynamic registration. 8 | @[JSON::Field(key: "dynamicRegistration")] 9 | property dynamic_registration : Bool = false 10 | 11 | def initialize 12 | @dynamic_registration = false 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/lsp/src/lsp/data/insert_text_format.cr: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | module LSP::Data 4 | # Defines whether the insert text in a completion item should be interpreted 5 | # as plain text or a snippet. 6 | enum InsertTextFormat 7 | # The primary text to be inserted is treated as a plain string. 8 | PlainText = 1 9 | 10 | # The primary text to be inserted is treated as a snippet. 11 | # 12 | # A snippet can define tab stops and placeholders with `$1`, `$2` 13 | # and `${3:foo}`. `$0` defines the final tab stop, it defaults to 14 | # the end of the snippet. Placeholders with equal identifiers are linked, 15 | # that is typing in one will update others too. 16 | Snippet = 2 17 | 18 | def self.new(*args) 19 | ::Enum::ValueConverter(InsertTextFormat).from_json(*args) 20 | end 21 | 22 | def to_json(*args) 23 | ::Enum::ValueConverter(InsertTextFormat).to_json(self, *args) 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/lsp/src/lsp/data/location.cr: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | module LSP::Data 4 | struct Location 5 | include JSON::Serializable 6 | 7 | @[JSON::Field(converter: LSP::JSONUtil::URIString)] 8 | property uri : URI 9 | 10 | property range : Range 11 | 12 | def initialize( 13 | @uri = URI.new, 14 | @range = Range.new 15 | ) 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/lsp/src/lsp/data/markup_content.cr: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | module LSP::Data 4 | # A `MarkupContent` literal represents a string value which content is interpreted base on its 5 | # kind flag. Currently the protocol supports `plaintext` and `markdown` as markup kinds. 6 | # 7 | # If the kind is `markdown` then the value can contain fenced code blocks like in GitHub issues. 8 | # See https://help.github.com/articles/creating-and-highlighting-code-blocks/#syntax-highlighting 9 | # 10 | # Here is an example how such a string can be constructed using JavaScript / TypeScript: 11 | # ```ts 12 | # let markdown: MarkdownContent = { 13 | # kind: MarkupKind.Markdown, 14 | # value: [ 15 | # '# Header', 16 | # 'Some text', 17 | # '```typescript', 18 | # 'someCode();', 19 | # '```' 20 | # ].join('\n') 21 | # }; 22 | # ``` 23 | # 24 | # *Please Note* that clients might sanitize the return markdown. A client could decide to 25 | # remove HTML from the markdown to avoid script execution. 26 | struct MarkupContent 27 | include JSON::Serializable 28 | 29 | # The type of the Markup. 30 | property kind : String 31 | 32 | # The content itself. 33 | property value : String 34 | 35 | def initialize(@kind = "plaintext", @value = "") 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/lsp/src/lsp/data/message_action_item.cr: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | module LSP::Data 4 | struct MessageActionItem 5 | include JSON::Serializable 6 | 7 | # A short title like 'Retry', 'Open Log' etc. 8 | property title : String 9 | 10 | def initialize(@title) 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/lsp/src/lsp/data/message_type.cr: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | module LSP::Data 4 | enum MessageType 5 | Error = 1 6 | Warning = 2 7 | Info = 3 8 | Log = 4 9 | 10 | def self.new(*args) 11 | ::Enum::ValueConverter(MessageType).from_json(*args) 12 | end 13 | 14 | def to_json(*args) 15 | ::Enum::ValueConverter(MessageType).to_json(self, *args) 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/lsp/src/lsp/data/parameter_information.cr: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | module LSP::Data 4 | struct ParameterInformation 5 | include JSON::Serializable 6 | 7 | # The label of this parameter. Will be shown in the UI. 8 | property label : String 9 | 10 | # The human-readable doc-comment of this parameter. 11 | # Will be shown in the UI but can be omitted. 12 | property documentation : String | MarkupContent | Nil 13 | 14 | def initialize( 15 | @label = "", 16 | @documentation = nil 17 | ) 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/lsp/src/lsp/data/position.cr: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | module LSP::Data 4 | struct Position 5 | include JSON::Serializable 6 | 7 | # Line position in a document (zero-based). 8 | property line : Int64 9 | 10 | # Character offset on a line in a document (zero-based). Assuming that 11 | # the line is represented as a string, the `character` value represents 12 | # the gap between the `character` and `character + 1`. 13 | # 14 | # If the character value is greater than the line length it defaults 15 | # back to the line length. 16 | property character : Int64 17 | 18 | def initialize( 19 | @line = 0_i64, 20 | @character = 0_i64 21 | ) 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/lsp/src/lsp/data/range.cr: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | module LSP::Data 4 | struct Range 5 | include JSON::Serializable 6 | 7 | property start : Position 8 | 9 | @[JSON::Field(key: "end")] 10 | property finish : Position 11 | 12 | def initialize( 13 | @start = Position.new, 14 | @finish = Position.new 15 | ) 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/lsp/src/lsp/data/response_error.cr: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | module LSP::Data 4 | struct ResponseError(D) 5 | include JSON::Serializable 6 | 7 | property code : Code 8 | property message : String 9 | property data : D? 10 | 11 | enum Code 12 | # Defined by JSON RPC. 13 | ParseError = -32700 14 | InvalidRequest = -32600 15 | MethodNotFound = -32601 16 | InvalidParams = -32602 17 | InternalError = -32603 18 | ServerErrorStart = -32099 19 | ServerErrorEnd = -32000 20 | ServerNotInitialized = -32002 21 | UnknownErrorCode = -32001 22 | 23 | # Defined by the protocol. 24 | RequestCancelled = -32800 25 | 26 | def self.new(*args) 27 | ::Enum::ValueConverter(Code).from_json(*args) 28 | end 29 | 30 | def to_json(*args) 31 | ::Enum::ValueConverter(Code).to_json(self, *args) 32 | end 33 | end 34 | 35 | def initialize( 36 | @data = nil, 37 | @message = "(no error message specified)", 38 | @code = Code::InternalError 39 | ) 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/lsp/src/lsp/data/signature_information.cr: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | module LSP::Data 4 | struct SignatureInformation 5 | include JSON::Serializable 6 | 7 | # The label of this signature. Will be shown in the UI. 8 | property label : String 9 | 10 | # The human-readable doc-comment of this signature. 11 | # Will be shown in the UI but can be omitted. 12 | property documentation : String | MarkupContent | Nil 13 | 14 | # The parameters of this signature. 15 | property parameters : Array(ParameterInformation) = [] of ParameterInformation 16 | 17 | def initialize( 18 | @label = "", 19 | @documentation = nil, 20 | @parameters = [] of ParameterInformation 21 | ) 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/lsp/src/lsp/data/symbol_kind.cr: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | module LSP::Data 4 | enum SymbolKind 5 | File = 1 6 | Module = 2 7 | Namespace = 3 8 | Package = 4 9 | Class = 5 10 | Method = 6 11 | Property = 7 12 | Field = 8 13 | Constructor = 9 14 | Enum = 10 15 | Interface = 11 16 | Function = 12 17 | Variable = 13 18 | Constant = 14 19 | String = 15 20 | Number = 16 21 | Boolean = 17 22 | Array = 18 23 | Object = 19 24 | Key = 20 25 | Null = 21 26 | EnumMember = 22 27 | Struct = 23 28 | Event = 24 29 | Operator = 25 30 | TypeParameter = 26 31 | 32 | def self.default 33 | [ 34 | File, Module, Namespace, Package, Class, Method, Property, Field, 35 | Constructor, Enum, Interface, Function, Variable, Constant, 36 | String, Number, Boolean, Array, 37 | ] 38 | end 39 | 40 | def self.new(*args) 41 | ::Enum::ValueConverter(SymbolKind).from_json(*args) 42 | end 43 | 44 | def to_json(*args) 45 | ::Enum::ValueConverter(SymbolKind).to_json(self, *args) 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/lsp/src/lsp/data/symbol_kind_set.cr: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | module LSP::Data 4 | struct SymbolKindSet 5 | include JSON::Serializable 6 | 7 | # The symbol kind values the client supports. When this 8 | # property exists the client also guarantees that it will 9 | # handle values outside its set gracefully and falls back 10 | # to a default value when unknown. 11 | # 12 | # If this property is not present the client only supports 13 | # the symbol kinds from `File` to `Array` as defined in 14 | # the initial version of the protocol. 15 | @[JSON::Field(key: "valueSet")] 16 | property value_set : Array(SymbolKind) = SymbolKind.default 17 | 18 | def initialize 19 | @value_set = SymbolKind.default 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/lsp/src/lsp/data/text_document_content_change_event.cr: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | module LSP::Data 4 | struct TextDocumentContentChangeEvent 5 | include JSON::Serializable 6 | 7 | # The range of the document that changed. 8 | property range : Range? 9 | 10 | # The length of the range that got replaced. 11 | @[JSON::Field(key: "rangeLength")] 12 | property range_length : Int64? 13 | 14 | # The new text of the range/document. 15 | property text : String 16 | 17 | def initialize( 18 | @range = nil, 19 | @range_length = nil, 20 | @text = "" 21 | ) 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/lsp/src/lsp/data/text_document_identifier.cr: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | module LSP::Data 4 | struct TextDocumentIdentifier 5 | include JSON::Serializable 6 | 7 | # The text document's URI. 8 | @[JSON::Field(converter: LSP::JSONUtil::URIString)] 9 | property uri : URI 10 | 11 | def initialize(@uri = URI.new) 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/lsp/src/lsp/data/text_document_item.cr: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | module LSP::Data 4 | struct TextDocumentItem 5 | include JSON::Serializable 6 | 7 | # The text document's URI. 8 | @[JSON::Field(converter: LSP::JSONUtil::URIString)] 9 | property uri : URI 10 | 11 | # The text document's language identifier. 12 | @[JSON::Field(key: "languageId")] 13 | property language_id : String 14 | 15 | # The version number of this document (it will increase after each 16 | # change, including undo/redo). 17 | property version : Int64 18 | 19 | # The content of the opened text document. 20 | property text : String 21 | 22 | def initialize( 23 | @uri = URI.new, 24 | @language_id = "", 25 | @version = 0, 26 | @text = "" 27 | ) 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/lsp/src/lsp/data/text_document_registration_options.cr: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | module LSP::Data 4 | struct TextDocumentRegistrationOptions 5 | include JSON::Serializable 6 | 7 | # A document selector to identify the scope of the registration. If set 8 | # to null the document selector provided on the client side will be used. 9 | @[JSON::Field(key: "documentSelector", emit_null: true)] 10 | property document_selector : Array(DocumentFilter)? 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/lsp/src/lsp/data/text_document_save_reason.cr: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | module LSP::Data 4 | enum TextDocumentSaveReason 5 | # Manually triggered, e.g. by the user pressing save, by starting debugging, 6 | # or by an API call. 7 | Manual = 1 8 | 9 | # Automatic after a delay. 10 | AfterDelay = 2 11 | 12 | # When the editor lost focus. 13 | FocusOut = 3 14 | 15 | def self.new(*args) 16 | ::Enum::ValueConverter(TextDocumentSaveReason).from_json(*args) 17 | end 18 | 19 | def to_json(*args) 20 | ::Enum::ValueConverter(TextDocumentSaveReason).to_json(self, *args) 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/lsp/src/lsp/data/text_document_sync_kind.cr: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | module LSP::Data 4 | # Defines how the host (editor) should sync document changes to the language server. 5 | enum TextDocumentSyncKind 6 | # Documents should not be synced at all. 7 | None = 0 8 | 9 | # Documents are synced by always sending the full content 10 | # of the document. 11 | Full = 1 12 | 13 | # Documents are synced by sending the full content on open. 14 | # After that only incremental updates to the document are 15 | # send. 16 | Incremental = 2 17 | 18 | def self.new(*args) 19 | ::Enum::ValueConverter(TextDocumentSyncKind).from_json(*args) 20 | end 21 | 22 | def to_json(*args) 23 | ::Enum::ValueConverter(TextDocumentSyncKind).to_json(self, *args) 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/lsp/src/lsp/data/text_document_versioned_identifier.cr: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | module LSP::Data 4 | struct TextDocumentVersionedIdentifier 5 | include JSON::Serializable 6 | 7 | # The text document's URI. 8 | @[JSON::Field(converter: LSP::JSONUtil::URIString)] 9 | property uri : URI 10 | 11 | # The version number of this document (it will increase after each 12 | # change, including undo/redo). 13 | property version : Int64 14 | 15 | def initialize(@uri = URI.new, @version = 0) 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/lsp/src/lsp/data/text_edit.cr: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | module LSP::Data 4 | # A textual edit applicable to a text document. 5 | struct TextEdit 6 | include JSON::Serializable 7 | 8 | # The range of the text document to be manipulated. 9 | # To insert netext into a document create a range where start === end. 10 | property range : Range 11 | 12 | # The string to be inserted. 13 | # For delete operations use an empty string. 14 | @[JSON::Field(key: "newText")] 15 | property new_text : String 16 | 17 | def initialize( 18 | @range = Range.new, 19 | @new_text = "" 20 | ) 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/lsp/src/lsp/data/workspace_folder.cr: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | module LSP::Data 4 | struct WorkspaceFolder 5 | include JSON::Serializable 6 | 7 | # The associated URI for this workspace folder. 8 | @[JSON::Field(converter: LSP::JSONUtil::URIString)] 9 | property uri : URI 10 | 11 | # The name of the workspace folder. Defaults to the 12 | # uri's basename. 13 | property name : String 14 | 15 | def initialize 16 | @uri = URI.new 17 | @name = "" 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/pegmatite/.gitignore: -------------------------------------------------------------------------------- 1 | /docs/ 2 | /lib/ 3 | /bin/ 4 | /.shards/ 5 | *.dwarf 6 | -------------------------------------------------------------------------------- /lib/pegmatite/README.md: -------------------------------------------------------------------------------- 1 | # crystal-pegmatite 2 | 3 | [![Linux CI](https://github.com/jemc/crystal-pegmatite/actions/workflows/ci.yml/badge.svg)](https://github.com/jemc/crystal-pegmatite/actions/workflows/ci.yml) 4 | 5 | A high-performance Parsing Expression Grammar (PEG) library for the Crystal language. 6 | 7 | See the [`spec/fixtures`](spec/fixtures) directory for example grammar definitions, and see the [`spec`](spec) directory for example invocations. 8 | -------------------------------------------------------------------------------- /lib/pegmatite/lib: -------------------------------------------------------------------------------- 1 | .. -------------------------------------------------------------------------------- /lib/pegmatite/shard.lock: -------------------------------------------------------------------------------- 1 | version: 2.0 2 | shards: 3 | ameba: 4 | git: https://github.com/crystal-ameba/ameba.git 5 | version: 0.14.3 6 | 7 | -------------------------------------------------------------------------------- /lib/pegmatite/shard.yml: -------------------------------------------------------------------------------- 1 | name: pegmatite 2 | version: 0.2.3 3 | 4 | authors: 5 | - Joe Eli McIlvain 6 | 7 | crystal: '>= 0.35.1' 8 | 9 | development_dependencies: 10 | ameba: 11 | github: crystal-ameba/ameba 12 | 13 | license: MPLv2 14 | -------------------------------------------------------------------------------- /lib/pegmatite/spec/dynamics_spec.cr: -------------------------------------------------------------------------------- 1 | require "./spec_helper" 2 | 3 | describe "Pegmatite dynamic matchers" do 4 | it "can be used to parse heredoc-like structures" do 5 | source = <<-SRC 6 | one = 1 7 | two = <<-TWO 8 | I am a heredoc 9 | TWO 10 | three = 3 11 | 12 | SRC 13 | 14 | tokens = Pegmatite.tokenize(Fixtures::HeredocGrammar, source) 15 | 16 | tokens.should eq [ 17 | {:attribute, 0, 8}, 18 | {:identifier, 0, 3}, # one 19 | {:number, 6, 7}, # 1 20 | {:attribute, 8, 42}, 21 | {:identifier, 8, 11}, # two 22 | {:heredoc, 14, 41}, 23 | {:identifier, 17, 20}, # TWO 24 | {:string, 21, 38}, # " I am a heredoc\n" 25 | {:identifier, 38, 41}, # TWO 26 | {:attribute, 42, 52}, 27 | {:identifier, 42, 47}, # three 28 | {:number, 50, 51}, # 3 29 | ] 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/pegmatite/spec/spec_helper.cr: -------------------------------------------------------------------------------- 1 | require "spec" 2 | require "../src/pegmatite" 3 | require "./fixtures/*" 4 | -------------------------------------------------------------------------------- /lib/pegmatite/src/pegmatite/pattern/dynamic_match.cr: -------------------------------------------------------------------------------- 1 | module Pegmatite 2 | # Pattern::DynamicMatch is used to match against a stored dynamic match value 3 | # and consume the string that matches the stored dynamic match. 4 | # 5 | # Parsing will fail if the bytes in the stream don't exactly match the dynamic 6 | # match. Otherwise, the pattern succeeds, consuming the matched bytes. 7 | class Pattern::DynamicMatch < Pattern 8 | def initialize(@label : Symbol) 9 | end 10 | 11 | def inspect(io) 12 | io << "dynamic_match(\"" 13 | @label.inspect(io) 14 | io << "\")" 15 | end 16 | 17 | def dsl_name 18 | "dynamic_match" 19 | end 20 | 21 | def description 22 | @label.inspect 23 | end 24 | 25 | def _match(source, offset, state) : MatchResult 26 | last_delim = state.dynamic_matches.reverse_each.find { |delim| 27 | delim[0] == @label 28 | } 29 | 30 | if !last_delim 31 | return {0, self} 32 | end 33 | 34 | delim_val = last_delim[1] 35 | delim_size = delim_val.bytesize.as(Int32) 36 | 37 | # Like Literal, we use some ugly patterns here for optimization 38 | return {0, self} if source.bytesize < (offset + delim_size) 39 | i = 0 40 | while i < delim_size 41 | return {0, self} \ 42 | if delim_val.to_unsafe[i] != source.to_unsafe[offset + i] 43 | i += 1 44 | end 45 | 46 | {delim_val.bytesize, nil} 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/pegmatite/src/pegmatite/pattern/dynamic_pop.cr: -------------------------------------------------------------------------------- 1 | module Pegmatite 2 | # Pattern::DynamicPop is used pop a dynamic match off the stack after matching, 3 | # such as to mark the end of a dynamic delimiter. 4 | # 5 | # If the child pattern produces tokens, those tokens will be passed as-is. 6 | # 7 | # Returns the result of the child pattern's parsing. 8 | class Pattern::DynamicPop < Pattern 9 | def initialize(@child : Pattern, @label : Symbol) 10 | end 11 | 12 | def inspect(io) 13 | io << "dynamic_pop(\"" 14 | @label.inspect(io) 15 | io << "\")" 16 | end 17 | 18 | def dsl_name 19 | "dynamic_pop" 20 | end 21 | 22 | def description 23 | @label.inspect 24 | end 25 | 26 | def _match(source, offset, state) : MatchResult 27 | length, result = @child.match(source, offset, state) 28 | return {length, result} if !result.is_a?(MatchOK) 29 | 30 | last_delim = state.dynamic_matches.last 31 | 32 | val = source[offset...(offset + length)] 33 | 34 | if last_delim[0] != @label || last_delim[1] != val 35 | state.observe_fail(offset + length, @child) 36 | return {length, result} 37 | end 38 | 39 | state.dynamic_matches.pop 40 | 41 | {length, result} 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/pegmatite/src/pegmatite/pattern/dynamic_push.cr: -------------------------------------------------------------------------------- 1 | module Pegmatite 2 | # Pattern::DynamicPush is used to dynamically push a dynamic match onto the stack, 3 | # usually for the purposes of dynamically constraining the scope of a pattern. 4 | # 5 | # If the child pattern produces tokens, those tokens will be passed as-is. 6 | # 7 | # Returns the result of the child pattern's parsing. 8 | class Pattern::DynamicPush < Pattern 9 | def initialize(@child : Pattern, @label : Symbol) 10 | end 11 | 12 | def inspect(io) 13 | io << "dynamic_push(\"" 14 | @label.inspect(io) 15 | io << "\")" 16 | end 17 | 18 | def dsl_name 19 | "dynamic_push" 20 | end 21 | 22 | def description 23 | @label.inspect 24 | end 25 | 26 | def _match(source, offset, state) : MatchResult 27 | length, result = @child.match(source, offset, state) 28 | return {length, result} if !result.is_a?(MatchOK) 29 | 30 | val = source[offset...(offset + length)] 31 | 32 | state.dynamic_matches.push({@label, val}) 33 | 34 | {length, result} 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/pegmatite/src/pegmatite/pattern/eof.cr: -------------------------------------------------------------------------------- 1 | module Pegmatite 2 | # Pattern::EOF specifies that the end of the source must follow 3 | # after the child pattern has been parsed successfully. 4 | # 5 | # If the child pattern doesn't match, its result is returned. 6 | # Fails if the cursor hasn't reached the end of the source. 7 | # Otherwise, the result of the child pattern is returned. 8 | class Pattern::EOF < Pattern 9 | def initialize(@child : Pattern) 10 | end 11 | 12 | def inspect(io) 13 | @child.inspect(io) 14 | io << ".then_eof" 15 | end 16 | 17 | def dsl_name 18 | "then_eof" 19 | end 20 | 21 | def description 22 | "#{@child.description} followed by end-of-file" 23 | end 24 | 25 | def _match(source, offset, state) : MatchResult 26 | length, result = @child.match(source, offset, state) 27 | return {length, result} if !result.is_a?(MatchOK) 28 | 29 | # Fail if the end of the source hasn't been reached yet. 30 | if (offset + length) != source.bytesize 31 | state.observe_fail(offset + length + 1, @child) 32 | return {0, self} 33 | end 34 | 35 | {length, result} 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/pegmatite/src/pegmatite/pattern/forward.cr: -------------------------------------------------------------------------------- 1 | module Pegmatite 2 | # Pattern::Forward is used to create a forward declaration, 3 | # such as when defining a pattern that will be used recursively with another. 4 | # 5 | # There is no child pattern present at declaration, so one must be added 6 | # later by calling define exactly once before the pattern is used to parse. 7 | # 8 | # Returns the result of the child pattern's parsing. 9 | class Pattern::Forward < Pattern 10 | @child : Pattern? 11 | 12 | def inspect(io) 13 | io << "forward" 14 | end 15 | 16 | def dsl_name 17 | "forward" 18 | end 19 | 20 | def initialize 21 | @child = nil 22 | end 23 | 24 | def define(child) 25 | raise "already defined" unless @child.nil? 26 | @child = child 27 | end 28 | 29 | def description 30 | @child.as(Pattern).description 31 | end 32 | 33 | def _match(source, offset, state) : MatchResult 34 | @child.as(Pattern).match(source, offset, state) 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/pegmatite/src/pegmatite/pattern/literal.cr: -------------------------------------------------------------------------------- 1 | module Pegmatite 2 | # Pattern::Literal is used to consume a specific string. 3 | # 4 | # Parsing will fail if the bytes in the stream don't exactly match the string. 5 | # Otherwise, the pattern succeeds, consuming the matched bytes. 6 | class Pattern::Literal < Pattern 7 | def initialize(@string : String) 8 | @size = @string.bytesize.as(Int32) 9 | end 10 | 11 | def inspect(io) 12 | io << "str(\"" 13 | @string.inspect(io) 14 | io << "\")" 15 | end 16 | 17 | def dsl_name 18 | "str" 19 | end 20 | 21 | def description 22 | @string.inspect 23 | end 24 | 25 | def _match(source, offset, state) : MatchResult 26 | # We use some ugly patterns here for optimization - this is a hot path! 27 | return {0, self} if source.bytesize < (offset + @string.bytesize) 28 | i = 0 29 | while i < @size 30 | return {0, self} \ 31 | if @string.to_unsafe[i] != source.to_unsafe[offset + i] 32 | i += 1 33 | end 34 | 35 | {@string.bytesize, nil} 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/pegmatite/src/pegmatite/pattern/not.cr: -------------------------------------------------------------------------------- 1 | module Pegmatite 2 | # Pattern::Not is used for negative-lookahead. 3 | # 4 | # Parsing will fail if the child pattern parsing succeeds. 5 | # Otherwise, the pattern succeeds, consuming zero bytes. 6 | # 7 | # Composing two Pattern::Not instances inside one another is a valid strategy 8 | # for positive lookahead. (TODO: test for this example) 9 | class Pattern::Not < Pattern 10 | def initialize(@child : Pattern) 11 | end 12 | 13 | def inspect(io) 14 | io << "~" 15 | @child.inspect(io) 16 | end 17 | 18 | def dsl_name 19 | "~" 20 | end 21 | 22 | def description 23 | "excluding #{@child.description}" 24 | end 25 | 26 | def _match(source, offset, state) : MatchResult 27 | length, result = @child.match(source, offset, state) 28 | case result 29 | when MatchOK 30 | {length, self} 31 | else 32 | {0, nil} 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/pegmatite/src/pegmatite/pattern/optional.cr: -------------------------------------------------------------------------------- 1 | module Pegmatite 2 | # Pattern::Optional specifies that a pattern that can either match or not. 3 | # 4 | # If the child pattern doesn't match, zero bytes are consumed. 5 | # Otherwise, the result of the child pattern is returned directly. 6 | # This pattern will never fail. 7 | class Pattern::Optional < Pattern 8 | def initialize(@child : Pattern) 9 | end 10 | 11 | def inspect(io) 12 | @child.inspect(io) 13 | io << ".maybe" 14 | end 15 | 16 | def dsl_name 17 | "maybe" 18 | end 19 | 20 | def description 21 | "optional #{@child.description}" 22 | end 23 | 24 | def _match(source, offset, state) : MatchResult 25 | length, result = @child.match(source, offset, state) 26 | 27 | if result.is_a?(MatchOK) 28 | {length, result} 29 | else 30 | state.observe_fail(offset + length, @child) 31 | {0, nil} 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/pegmatite/src/pegmatite/pattern/unicode_char.cr: -------------------------------------------------------------------------------- 1 | module Pegmatite 2 | # Pattern::UnicodeChar is used to consume a single specific character. 3 | # 4 | # Parsing will fail if a valid UTF-32 codepoint couldn't be parsed, 5 | # or if the parsed codepoint didn't match the expected one. 6 | # Otherwise, the pattern succeeds, consuming the matched bytes. 7 | class Pattern::UnicodeChar < Pattern 8 | def initialize(@expected : UInt32) 9 | raise "0xFFFD isn't a valid expected character" if @expected == 0xFFFD_u32 10 | end 11 | 12 | def inspect(io) 13 | io << "char(" 14 | @expected.chr.inspect(io) 15 | io << ")" 16 | end 17 | 18 | def dsl_name 19 | "char" 20 | end 21 | 22 | def description 23 | @expected.chr.inspect 24 | end 25 | 26 | def _match(source, offset, state) : MatchResult 27 | c, length, is_valid = Pattern::UnicodeAny.utf8_at(source, offset) 28 | 29 | # Fail if a valid UTF-32 character couldn't be parsed. 30 | return {0, self} if !is_valid 31 | 32 | # Fail if the character wasn't the expected value. 33 | return {0, self} if c != @expected 34 | 35 | # Otherwise, pass. 36 | {length, nil} 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/pegmatite/src/pegmatite/pattern/unicode_range.cr: -------------------------------------------------------------------------------- 1 | module Pegmatite 2 | # Pattern::UnicodeRange is used to consume a single character from within 3 | # a specified contiguous range of acceptable codepoints. 4 | # 5 | # Parsing will fail if a valid UTF-32 codepoint couldn't be parsed, 6 | # or if the parsed codepoint didn't fall in the specified range. 7 | # Otherwise, the pattern succeeds, consuming the matched bytes. 8 | class Pattern::UnicodeRange < Pattern 9 | def initialize(@min : UInt32, @max : UInt32) 10 | end 11 | 12 | def inspect(io) 13 | io << "range(" 14 | @min.chr.inspect(io) 15 | io << ", " 16 | @max.chr.inspect(io) 17 | io << ")" 18 | end 19 | 20 | def dsl_name 21 | "range" 22 | end 23 | 24 | def description 25 | "#{@min.chr.inspect}..#{@max.chr.inspect}" 26 | end 27 | 28 | def _match(source, offset, state) : MatchResult 29 | c, length, is_valid = Pattern::UnicodeAny.utf8_at(source, offset) 30 | 31 | # Fail if a valid UTF-32 character couldn't be parsed. 32 | return {0, self} if !is_valid 33 | 34 | # Fail if the character wasn't in the expected range. 35 | return {0, self} if (c < @min) || (c > @max) 36 | 37 | # Otherwise, pass. 38 | {length, nil} 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/pegmatite/src/pegmatite/token.cr: -------------------------------------------------------------------------------- 1 | module Pegmatite 2 | # A Token is a triple containing a name, a start offset, and end offset, 3 | # representing a named pattern that was matched within the overall pattern. 4 | alias Token = {Symbol, Int32, Int32} 5 | end 6 | -------------------------------------------------------------------------------- /self-hosted/manifest.savi: -------------------------------------------------------------------------------- 1 | :manifest bin "savi-lang-parse" 2 | :sources "src/savi-lang-parse/*.savi" 3 | :sources "src/SaviProto/*.savi" 4 | 5 | :dependency CapnProto v0 6 | :from "github:jemc-savi/CapnProto" 7 | :depends on ByteStream 8 | 9 | :dependency Map v0 10 | :from "github:savi-lang/Map" 11 | 12 | :dependency PEG v0 13 | :from "github:savi-lang/PEG" 14 | 15 | :dependency Time v0 16 | :from "github:savi-lang/Time" 17 | 18 | :dependency StdIn v0 19 | :from "github:savi-lang/StdIn" 20 | :depends on ByteStream 21 | :depends on IO 22 | :depends on OSError 23 | 24 | :dependency IO v0 25 | :from "github:savi-lang/IO" 26 | :depends on ByteStream 27 | :depends on OSError 28 | 29 | :transitive dependency ByteStream v0 30 | :from "github:savi-lang/ByteStream" 31 | 32 | :transitive dependency OSError v0 33 | :from "github:savi-lang/OSError" 34 | -------------------------------------------------------------------------------- /self-hosted/spec/savi-lang-parse.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # Go to the right working directory and set up shell options 4 | cd -- "$(dirname -- "$0")" 5 | set -e 6 | 7 | find savi-lang-parse -name '*.savi' | xargs -I '{}' \ 8 | sh -c 'cat {} | ../bin/savi-lang-parse > {}.ast.yaml' 9 | 10 | git diff --exit-code savi-lang-parse 11 | -------------------------------------------------------------------------------- /self-hosted/spec/savi-lang-parse/01-parse-hello-world.savi: -------------------------------------------------------------------------------- 1 | :actor Main 2 | :new (env Env) 3 | env.out.print("Hello, World!") 4 | -------------------------------------------------------------------------------- /self-hosted/spec/savi-lang-parse/02-parse-example-class.savi: -------------------------------------------------------------------------------- 1 | :class Example 2 | :let name String: "World" 3 | 4 | :: Return a friendly greeting string for this instance. 5 | :: This always starts with a good, old-fashioned "Hello". 6 | :fun greeting String 7 | "Hello, " + @name + "!" 8 | 9 | :fun degreesF(c F64) F64 10 | c * 9 / 5 + 32.0 11 | -------------------------------------------------------------------------------- /self-hosted/spec/savi-lang-parse/03-parse-values.savi: -------------------------------------------------------------------------------- 1 | :module _Values 2 | :fun example 3 | 0 4 | -0 5 | 36 6 | -36 7 | 1_234_567_890 8 | 0xfab0_9FAB 9 | 0b1000_1000 10 | 36.6 11 | -36.6 12 | 36e0 13 | 36E3 14 | 36.6666e0 15 | 36.6666E3 16 | 36.6666e+3 17 | 36.6666E-3 18 | "foo" 19 | b"bar" 20 | "\x01\x02\x03" 21 | "\u2764" 22 | "\U0001fa01" 23 | 'f' 24 | '\0' 25 | '\x01' 26 | '\u2764' 27 | '\U0001fa01' 28 | "compose string a \( 29 | 99 30 | ) \(100) final" 31 | "compose string b \( 32 | 99 33 | ) \(100)" 34 | b"\(99)\(100)" 35 | "\ 36 | Hello, \ 37 | World\ 38 | !\ 39 | " 40 | b"\ 41 | \x00\x11\x22\x33\x44\x55\x66\x77\ 42 | \x88\x99\xaa\xbb\xcc\xdd\xee\xff\ 43 | " 44 | -------------------------------------------------------------------------------- /self-hosted/spec/savi-lang-parse/04-parse-operators.savi: -------------------------------------------------------------------------------- 1 | :module _Operators 2 | :fun example_ordered // in order of precedence, from "weakest" to "strongest" 3 | y = x <<= x x 4 | && x || x 5 | === x == x !== x != x =~ x 6 | >= x <= x < x > x 7 | <|> x <~> x <<~ x ~>> x << x >> x <~ x ~> x 8 | + x - x 9 | * x / x.y 10 | 11 | :fun example_mixed 12 | a != b && c > d / x + e / y || i.j > k << l 13 | 14 | :fun example_prefix 15 | ~x 16 | -------------------------------------------------------------------------------- /self-hosted/spec/savi-lang-parse/05-parse-nested-bracket-strings.savi: -------------------------------------------------------------------------------- 1 | :actor Main 2 | :new 3 | <<>> 4 | <<>>BAZ>>> 5 | <<< 6 | FOO 7 | BAR 8 | BAZ 9 | >>> 10 | <<< 11 | FOO 12 | <<< 13 | BAR 14 | >>> 15 | BAZ 16 | >>> 17 | -------------------------------------------------------------------------------- /self-hosted/spec/savi-lang-parse/06-parse-groups.savi: -------------------------------------------------------------------------------- 1 | :module _Groups 2 | :fun example 3 | a b c d e 4 | a b c \ 5 | d e 6 | (a, b, c, d, e) :: annotated to prevent `savi format` from removing parens 7 | ( 8 | a, b 9 | c, d 10 | e 11 | ) :: annotated to prevent `savi format` from removing parens 12 | (a | b | c | d | e) 13 | ( 14 | | a | b 15 | | c | d 16 | | e 17 | ) 18 | (a, b | c, d | | e) 19 | (a, b, c, d, e | ) 20 | (a, b, c, d, e | | ) 21 | () :: annotated to prevent `savi format` from removing parens 22 | -------------------------------------------------------------------------------- /self-hosted/spec/savi-lang-parse/07-parse-annotations.savi: -------------------------------------------------------------------------------- 1 | :: 1st line of type-level annotation 2 | :: 2nd line of type-level annotation 3 | :module _Annotations 4 | :: 1st line of function-level annotation 5 | :: 2nd line of function-level annotation 6 | :fun example 7 | :: 1st block in function 8 | (x = "foo") :: assignment-level annotation (combines with above) 9 | (x = "foo") :: another assignment-level annotation 10 | (x = "foo") :: yet another assignment-level annotation 11 | 12 | :: 2nd block in function 13 | x = "foo" :: value-level annotation 14 | 15 | :: 4th block in function 16 | ( 17 | :: 1st sub-block 18 | (x = "foo") :: 1st nested assignment-level annotation (combines with above) 19 | 20 | :: 2nd sub-block 21 | (x = "foo") :: 2nd nested assignment-level annotation (combines with above) 22 | ) :: trailing 4th-block annotation (combines with above) 23 | -------------------------------------------------------------------------------- /self-hosted/spec/savi-lang-parse/08-parse-yield-blocks.savi: -------------------------------------------------------------------------------- 1 | :module _YieldBlocks 2 | :fun example 3 | foo.bar.each -> (x, y | 4 | x 5 | ) 6 | -------------------------------------------------------------------------------- /self-hosted/src/SaviProto/SaviProto.Source.capnp: -------------------------------------------------------------------------------- 1 | @0xf053415649535243; # "\xf0" + "SAVISRC" 2 | 3 | using Savi = import "/CapnProto.Savi.Meta.capnp"; 4 | $Savi.namespace("SaviProto"); 5 | 6 | struct Source { 7 | absoluteFilePath @0 :Text; 8 | contentForNonFile @1 :Text; 9 | contentHash64 @2 :UInt64; 10 | package @3 :Source.Package; 11 | 12 | struct Position { 13 | source @0 :Source; 14 | offset @1 :UInt32; 15 | size @2 :UInt32; 16 | row @3 :UInt32; 17 | column @4 :UInt32; 18 | } 19 | 20 | struct Package { 21 | absoluteManifestDirectoryPath @0 :Text; 22 | name @1 :Text; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /self-hosted/src/savi-lang-parse/_Error.savi: -------------------------------------------------------------------------------- 1 | :enum _Error 2 | :member IntegerTooBig 1 3 | :member FloatingPointInvalid 2 4 | 5 | :member BugNoTokens 100 6 | :member BugInitialTokenIsNotDocument 101 7 | :member BugFixedSizeListIsTooSmall 102 8 | :member BugASTListNeedsLargerInitSize 103 9 | :member BugUnexpectedGrammarToken 104 10 | :member BugInvalidGroupStyleByte 105 11 | :member BugInvalidString 106 12 | :member BugInvalidPrefixedString 107 13 | :member BugInvalidCharacter 108 14 | 15 | :member ToDoTokenKind 200 16 | 17 | :struct _Error.Info 18 | :let at PEG.Token(_Token) 19 | :let code _Error 20 | :new (@at, @code) 21 | 22 | :struct _Error.List 23 | :let _list Array(_Error.Info) 24 | :new (@_list = []) 25 | 26 | :fun ref at(at, code): @_list << _Error.Info.new(at, code) 27 | 28 | :fun has_any: @_list.is_not_empty 29 | :fun each: @_list.each -> (e | yield e) 30 | -------------------------------------------------------------------------------- /self-hosted/src/savi-lang-parse/_FFI.savi: -------------------------------------------------------------------------------- 1 | 2 | :module _FFI 3 | // TODO: Avoid FFI and use a pure Savi `strtod` implementation. 4 | :ffi strtod( 5 | start_pointer CPointer(U8) 6 | end_pointer CPointer(CPointer(U8)) 7 | ) F64 8 | -------------------------------------------------------------------------------- /self-hosted/src/savi-lang-parse/_Token.savi: -------------------------------------------------------------------------------- 1 | :enum _Token 2 | :member Annotation 1 3 | :member Identifier 2 4 | :member BinaryInteger 3 5 | :member HexadecimalInteger 4 6 | :member DecimalInteger 5 7 | :member FloatingPoint 6 8 | :member Character 7 9 | :member String 8 10 | :member PrefixedString 9 11 | :member BracketString 10 12 | :member Operator 11 13 | :member Compound 12 14 | :member Prefix 13 15 | :member Group 14 16 | :member GroupPartition 15 17 | :member GroupWhitespace 16 18 | :member Relate 17 19 | :member RelateAssign 18 20 | :member Declare 19 21 | :member Document 20 22 | -------------------------------------------------------------------------------- /self-hosted/src/savi-lang-parse/_TokenPrinter.savi: -------------------------------------------------------------------------------- 1 | :class _TokenPrinter 2 | :is PEG.Parser.Builder(_Token, String) 3 | 4 | :fun ref build(tokens Array(PEG.Token(_Token))'val) String 5 | out = String.new 6 | tokens.each -> (token | 7 | out << "\(token.start)-\(token.end): \(token.kind)\n" 8 | ) 9 | out.take_buffer 10 | -------------------------------------------------------------------------------- /shard.lock: -------------------------------------------------------------------------------- 1 | version: 2.0 2 | shards: 3 | capnproto: 4 | git: https://github.com/jemc/crystal-capnproto.git 5 | version: 1.0.0+git.commit.61c3cf19167eb71398a1f36da2032c4560ebcdc9 6 | 7 | clang: 8 | git: https://github.com/crystal-lang/clang.cr.git 9 | version: 0.3.0 10 | 11 | clim: 12 | git: https://github.com/at-grandpa/clim.git 13 | version: 0.13.0 14 | 15 | lsp: 16 | git: https://github.com/jemc/crystal-lsp.git 17 | version: 0.1.0+git.commit.f7af03eed5b63974c04e92c0854b7c4a5b92c7f9 18 | 19 | pegmatite: 20 | git: https://github.com/jemc/crystal-pegmatite.git 21 | version: 0.2.3+git.commit.e7470d6eb1135cfd8a5353302edc5cad96f2c562 22 | 23 | -------------------------------------------------------------------------------- /shard.yml: -------------------------------------------------------------------------------- 1 | name: savi 2 | version: 0.1.0 3 | 4 | authors: 5 | - Joe Eli McIlvain 6 | 7 | targets: 8 | savi: 9 | main: main.cr 10 | 11 | dependencies: 12 | pegmatite: 13 | github: jemc/crystal-pegmatite 14 | lsp: 15 | github: jemc/crystal-lsp 16 | capnproto: 17 | github: jemc/crystal-capnproto 18 | clang: 19 | github: crystal-lang/clang.cr 20 | clim: 21 | github: at-grandpa/clim 22 | version: 0.13.0 23 | 24 | crystal: 1.10.1 25 | 26 | license: MPLv2 27 | -------------------------------------------------------------------------------- /spec/all.cr: -------------------------------------------------------------------------------- 1 | require "./**" 2 | -------------------------------------------------------------------------------- /spec/compiler/binary_verona_spec.cr: -------------------------------------------------------------------------------- 1 | describe Savi::Compiler::BinaryVerona do 2 | pending "creates a simple binary leveraging the Verona runtime" do 3 | source_dir = File.join(__DIR__, "../../examples/verona") 4 | 5 | ctx = Savi.compiler.compile(source_dir, :binary_verona) 6 | ctx.errors.should be_empty 7 | 8 | no_test_failures = 9 | Savi::Compiler::BinaryVerona.run_last_compiled_program == 0 10 | 11 | no_test_failures.should eq true 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/compiler/ffigen.savi.spec.md: -------------------------------------------------------------------------------- 1 | --- 2 | pass: load 3 | --- 4 | 5 | It generates C bindings for a basic function. 6 | 7 | ```c ffigen 8 | unsigned sleep(unsigned seconds); 9 | ``` 10 | ```savi 11 | :module _FFI 12 | :ffi sleep( 13 | seconds U32 14 | ) U32 15 | ``` 16 | 17 | --- 18 | 19 | It picks up block-style comments as documentation. 20 | 21 | ```c ffigen 22 | /** 23 | * Sleep for the given number of seconds. 24 | * 25 | * Returns the number of seconds remaining in the sleep, if the sleep was 26 | * interrupted by a signal. Otherwise, returns zero to indicate completion. 27 | */ 28 | unsigned sleep(unsigned seconds); 29 | ``` 30 | ```savi 31 | :module _FFI 32 | :: Sleep for the given number of seconds. 33 | :: 34 | :: Returns the number of seconds remaining in the sleep, if the sleep was 35 | :: interrupted by a signal. Otherwise, returns zero to indicate completion. 36 | :ffi sleep( 37 | seconds U32 38 | ) U32 39 | ``` 40 | 41 | --- 42 | 43 | It handles functions with no arguments. 44 | 45 | ```c ffigen 46 | int rand(void); 47 | ``` 48 | ```savi 49 | :module _FFI 50 | :ffi rand I32 51 | ``` 52 | 53 | --- 54 | 55 | It handles functions with no return value. 56 | 57 | ```c ffigen 58 | void srand(unsigned int seed); 59 | ``` 60 | ```savi 61 | :module _FFI 62 | :ffi srand( 63 | seed U32 64 | ) 65 | ``` 66 | -------------------------------------------------------------------------------- /spec/compiler/format.dots.savi.spec.md: -------------------------------------------------------------------------------- 1 | --- 2 | pass: format 3 | --- 4 | 5 | Calls on the self (`@`) should not have an explicit dot. 6 | 7 | ```savi 8 | @foo 9 | @.foo 10 | @ .foo 11 | @. foo 12 | @ . foo 13 | @ 14 | .foo 15 | @foo.bar 16 | self = @ 17 | self.foo 18 | self.foo.bar 19 | ``` 20 | ```savi format.NoExplicitSelfDot 21 | @foo 22 | @foo 23 | @foo 24 | @foo 25 | @foo 26 | @foo 27 | @foo.bar 28 | self = @ 29 | self.foo 30 | self.foo.bar 31 | ``` 32 | -------------------------------------------------------------------------------- /spec/compiler/format.numbers.savi.spec.md: -------------------------------------------------------------------------------- 1 | --- 2 | pass: format 3 | --- 4 | 5 | Number separators should not be lost. 6 | 7 | ```savi 8 | 1234 9 | 1000_000 10 | 123.3_4 11 | ``` 12 | ```savi format.Indentation 13 | 1234 14 | 1000_000 15 | 123.3_4 16 | ``` 17 | 18 | -------------------------------------------------------------------------------- /spec/compiler/namespace_spec.cr: -------------------------------------------------------------------------------- 1 | describe Savi::Compiler::Namespace do 2 | it "returns the same output state when compiled again with same sources" do 3 | source = Savi::Source.new_example <<-SOURCE 4 | :actor Main 5 | :new (env) 6 | env.out.print("Hello, World") 7 | SOURCE 8 | 9 | ctx1 = Savi.compiler.test_compile([source], :namespace) 10 | ctx2 = Savi.compiler.test_compile([source], :namespace) 11 | 12 | ctx1.namespace[source.package].should eq ctx2.namespace[source.package] 13 | end 14 | 15 | # TODO: Figure out how to test these in our test suite - they need a package. 16 | pending "complains when a bulk-imported type conflicts with another" 17 | pending "won't have conflicts with a private type in an imported package" 18 | pending "complains when trying to explicitly import a private type" 19 | end 20 | -------------------------------------------------------------------------------- /spec/compiler/populate_spec.cr: -------------------------------------------------------------------------------- 1 | describe Savi::Compiler::Populate do 2 | it "complains when a source type couldn't be resolved" do 3 | source = Savi::Source.new_example <<-SOURCE 4 | :actor Main 5 | :is Bogus 6 | SOURCE 7 | 8 | expected = <<-MSG 9 | This type couldn't be resolved: 10 | from (example):2: 11 | :is Bogus 12 | ^~~~~ 13 | MSG 14 | 15 | Savi.compiler.test_compile([source], :populate) 16 | .errors.map(&.message).join("\n").should eq expected 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/compiler/privacy.savi.spec.md: -------------------------------------------------------------------------------- 1 | --- 2 | pass: privacy 3 | --- 4 | 5 | It complains when calling a private method on a core Savi package type: 6 | 7 | ```savi 8 | Env._create 9 | ``` 10 | ```error 11 | This function call breaks privacy boundaries: 12 | Env._create 13 | ^~~~~~~ 14 | 15 | - this is a private function from another package: 16 | :new val _create( 17 | ^~~~~~~ 18 | ``` 19 | 20 | --- 21 | 22 | TODO: It won't allow an interface in the local package to circumvent 23 | 24 | --- 25 | 26 | It won't crash on private calls within a type-conditional layer: 27 | 28 | ```savi 29 | :class Generic(A) 30 | :var _value A 31 | :new (@_value) 32 | :fun numeric_bit_width 33 | if (A <: Numeric.Convertible) (@._value.bit_width) 34 | ``` 35 | -------------------------------------------------------------------------------- /spec/compiler/t_type_check.completeness.savi.spec.md: -------------------------------------------------------------------------------- 1 | --- 2 | pass: t_type_check 3 | --- 4 | 5 | TODO: It complains when access to the self is shared while still incomplete: 6 | 7 | ```savi 8 | @x = 1 9 | AccessWhileIncomplete.data(@) 10 | @y = 2 11 | @z = 3 12 | 13 | :var x U64 14 | :var y U64 15 | :var z U64 16 | 17 | :module AccessWhileIncomplete 18 | :fun data(any Any'box) 19 | any 20 | ``` 21 | 22 | --- 23 | 24 | It allows opaque sharing of the self while still incomplete and non-opaque sharing of the self after becoming complete: 25 | 26 | ```savi 27 | @x = 1 28 | TouchWhileIncomplete.data(@) 29 | @y = 2 30 | @z = 3 31 | AccessAfterComplete.data(@) 32 | 33 | :var x U64 34 | :var y U64 35 | :var z U64 36 | 37 | :module AccessAfterComplete 38 | :fun data(any Any'box) 39 | any 40 | 41 | :module TouchWhileIncomplete 42 | :fun data(any Any'tag) 43 | any 44 | ``` 45 | -------------------------------------------------------------------------------- /spec/compiler/t_type_check.identifiers.savi.spec.md: -------------------------------------------------------------------------------- 1 | --- 2 | pass: t_type_check 3 | --- 4 | 5 | It complains when the type identifier couldn't be resolved: 6 | 7 | ```savi 8 | x BogusType = 42 9 | ``` 10 | ```error 11 | This type couldn't be resolved: 12 | x BogusType = 42 13 | ^~~~~~~~~ 14 | ``` 15 | 16 | --- 17 | 18 | It complains when the return type identifier couldn't be resolved: 19 | 20 | ```savi 21 | :fun x BogusType: 42 22 | ``` 23 | ```error 24 | This type couldn't be resolved: 25 | :fun x BogusType: 42 26 | ^~~~~~~~~ 27 | ``` 28 | -------------------------------------------------------------------------------- /spec/compiler/t_type_check.validation.savi.spec.md: -------------------------------------------------------------------------------- 1 | --- 2 | pass: t_type_check 3 | --- 4 | 5 | TODO: It complains if some params of an elevated constructor are not sendable: 6 | 7 | ```savi 8 | // :new val bad_constructor(a String'ref, b String'val, c String'box) 9 | ``` 10 | 11 | --- 12 | 13 | TODO: It complains if some params of an asynchronous function are not sendable: 14 | 15 | ```savi 16 | // :actor BadActor 17 | // :be bad_behavior(a String'ref, b String'val, c String'box) 18 | ``` 19 | 20 | --- 21 | 22 | TODO: It complains when a constant isn't of one of the supported types: 23 | 24 | ```savi 25 | :const i8 I8: 1 26 | :const u64 U64: 2 27 | :const f64 F32: 3.3 28 | :const string String: "Hello, World!" 29 | :const bytes Bytes: b"Hello, World!" 30 | :const array_i8 Array(I8)'val: [1] 31 | :const array_u64 Array(U64)'val: [2] 32 | :const array_f32 Array(F32)'val: [3.3] 33 | :const array_string Array(String)'val: ["Hello", "World"] 34 | :const array_bytes Array(Bytes)'val: [b"Hello", b"World"] 35 | // :const array_ref_string Array(String)'ref: ["Hello", "World"] // NOT VAL 36 | ``` 37 | -------------------------------------------------------------------------------- /spec/compiler/type_check.completeness.savi.spec.md: -------------------------------------------------------------------------------- 1 | --- 2 | pass: type_check 3 | --- 4 | 5 | It complains when access to the self is shared while still incomplete: 6 | 7 | ```savi 8 | @x = 1 9 | AccessWhileIncomplete.data(@) 10 | @y = 2 11 | @z = 3 12 | 13 | :var x U64 14 | :var y U64 15 | :var z U64 16 | 17 | :module AccessWhileIncomplete 18 | :fun data(any Any'box) 19 | any 20 | ``` 21 | ```error 22 | This usage of `@` shares field access to the object from a constructor before all fields are initialized: 23 | AccessWhileIncomplete.data(@) 24 | ^ 25 | 26 | - if this constraint were specified as `tag` or lower it would not grant field access: 27 | :fun data(any Any'box) 28 | ^~~~~~~ 29 | 30 | - this field didn't get initialized: 31 | :var y U64 32 | ^ 33 | 34 | - this field didn't get initialized: 35 | :var z U64 36 | ^ 37 | ``` 38 | 39 | --- 40 | 41 | It allows opaque sharing of the self while still incomplete and non-opaque sharing of the self after becoming complete: 42 | 43 | ```savi 44 | @x = 1 45 | TouchWhileIncomplete.data(@) 46 | @y = 2 47 | @z = 3 48 | AccessAfterComplete.data(@) 49 | 50 | :var x U64 51 | :var y U64 52 | :var z U64 53 | 54 | :module AccessAfterComplete 55 | :fun data(any Any'box) 56 | any 57 | 58 | :module TouchWhileIncomplete 59 | :fun data(any Any'tag) 60 | any 61 | ``` 62 | -------------------------------------------------------------------------------- /spec/compiler/type_check.identifiers.savi.spec.md: -------------------------------------------------------------------------------- 1 | --- 2 | pass: type_check 3 | --- 4 | 5 | It complains when the type identifier couldn't be resolved: 6 | 7 | ```savi 8 | x BogusType = 42 9 | ``` 10 | ```error 11 | This type couldn't be resolved: 12 | x BogusType = 42 13 | ^~~~~~~~~ 14 | ``` 15 | 16 | --- 17 | 18 | It complains when the return type identifier couldn't be resolved: 19 | 20 | ```savi 21 | :fun x BogusType: 42 22 | ``` 23 | ```error 24 | This type couldn't be resolved: 25 | :fun x BogusType: 42 26 | ^~~~~~~~~ 27 | ``` 28 | -------------------------------------------------------------------------------- /spec/core/Bool.Spec.savi: -------------------------------------------------------------------------------- 1 | :class Savi.Bool.Spec 2 | :is Spec 3 | :const describes: "Bool" 4 | 5 | :it "has some trivial convenience methods for readability's sake" 6 | assert: True.is_true == True 7 | assert: False.is_true == False 8 | assert: True.is_false == False 9 | assert: False.is_false == True 10 | assert: True.not == False 11 | assert: False.not == True 12 | 13 | :it "can be converted from a U64" 14 | assert: Bool.from_u64!(U64[0]) == False 15 | assert: Bool.from_u64!(U64[1]) == True 16 | assert error: Bool.from_u64!(U64[2]) 17 | -------------------------------------------------------------------------------- /spec/core/Bytes.Format.Spec.savi: -------------------------------------------------------------------------------- 1 | :class Savi.Bytes.Format.Spec 2 | :is Spec 3 | :const describes: "Bytes.Format" 4 | 5 | :it "can format as a hex dump" 6 | value = Bytes.new 7 | U8[0x89].times -> (byte | value.push(byte)) 8 | 9 | assert: "\(value.format.hex_dump)" == String.join([ 10 | "00000000: 0001 0203 0405 0607 0809 0a0b 0c0d 0e0f ................" 11 | "00000010: 1011 1213 1415 1617 1819 1a1b 1c1d 1e1f ................" 12 | "00000020: 2021 2223 2425 2627 2829 2a2b 2c2d 2e2f .!\"#$%&'()*+,-./" 13 | "00000030: 3031 3233 3435 3637 3839 3a3b 3c3d 3e3f 0123456789:;<=>?" 14 | "00000040: 4041 4243 4445 4647 4849 4a4b 4c4d 4e4f @ABCDEFGHIJKLMNO" 15 | "00000050: 5051 5253 5455 5657 5859 5a5b 5c5d 5e5f PQRSTUVWXYZ[\\]^_" 16 | "00000060: 6061 6263 6465 6667 6869 6a6b 6c6d 6e6f `abcdefghijklmno" 17 | "00000070: 7071 7273 7475 7677 7879 7a7b 7c7d 7e7f pqrstuvwxyz{|}~." 18 | "00000080: 8081 8283 8485 8687 88 ......... " 19 | "" 20 | ], "\n") 21 | -------------------------------------------------------------------------------- /spec/core/CPointer.Spec.savi: -------------------------------------------------------------------------------- 1 | :class Savi.CPointer.Spec 2 | :is Spec 3 | :const describes: "CPointer" 4 | 5 | :it "returns the address of the pointer as an unsigned integer" 6 | assert: CPointer(U8).null.address == 0 7 | assert: (static_address_of_function Bytes.join).address != 0 8 | 9 | :it "tests if it is a null pointer or not" 10 | assert: CPointer(U8).null.is_null 11 | assert: CPointer(U8).null.is_not_null.is_false 12 | assert: (static_address_of_function Bytes.join).is_null.is_false 13 | assert: (static_address_of_function Bytes.join).is_not_null 14 | -------------------------------------------------------------------------------- /spec/core/FloatingPoint.Arithmetic.Spec.savi: -------------------------------------------------------------------------------- 1 | :class Savi.FloatingPoint.Arithmetic.Spec 2 | :is Spec 3 | :const describes: "FloatingPoint.Arithmetic" 4 | 5 | :it "implements logarithms and exponents for floating points" 6 | assert: F64[1].log == 0 7 | assert: F32[1].log == 0 8 | assert: F64[2.718281828459045].log == 1 9 | assert: F32[2.718282].log == 1 10 | assert: F64[1000000000].log10 == 9 11 | assert: F32[1000000000].log10 == 9 12 | assert: F64[0.0000000001].log10 == -10 13 | assert: F32[0.0000000001].log10 == -10 14 | assert: F64[1024].log2 == 10 15 | assert: F32[1024].log2 == 10 16 | assert: F64[0.0625].log2 == -4 17 | assert: F32[0.0625].log2 == -4 18 | assert: F64[2].pow(-4) == 0.0625 19 | assert: F32[2].pow(-4) == 0.0625 20 | assert: F64[2].pow(10) == 1024 21 | assert: F32[2].pow(10) == 1024 22 | assert: F64[10].pow(-10) == 0.0000000001 23 | assert: F32[10].pow(-10) == 0.0000000001 24 | assert: F64[10].pow(9) == 1000000000 25 | assert: F32[10].pow(9) == 1000000000 26 | -------------------------------------------------------------------------------- /spec/core/InhibitOptimization.Spec.savi: -------------------------------------------------------------------------------- 1 | :class Savi.InhibitOptimization.Spec 2 | :is Spec 3 | :const describes: "InhibitOptimization" 4 | 5 | :it "observes a result" 6 | // The effects of calling this function on optimizations are not really 7 | // testable, but we can at least test that the function can be called 8 | // (that code generation of the intrinsic is successful). 9 | InhibitOptimization.ObserveResult(U64)[99] 10 | 11 | :it "observes side effects" 12 | // The effects of calling this function on optimizations are not really 13 | // testable, but we can at least test that the function can be called 14 | // (that code generation of the intrinsic is successful). 15 | InhibitOptimization.observe_side_effects 16 | -------------------------------------------------------------------------------- /spec/core/Integer.Countable.Spec.savi: -------------------------------------------------------------------------------- 1 | :class Savi.Integer.Countable.Spec 2 | :is Spec 3 | :const describes: "Integer.Countable" 4 | 5 | :it "can count up to the given number, starting with 0, excluding itself" 6 | count = 0 7 | count_sum = 0 8 | count_return = 5.times -> (i | 9 | count = count + 1 10 | count_sum = count_sum + i 11 | ) 12 | 13 | assert: count_return == 5 14 | assert: count == 5 15 | assert: count_sum == 10 16 | -------------------------------------------------------------------------------- /spec/core/Integer.WideArithmetic.Spec.savi: -------------------------------------------------------------------------------- 1 | :class Savi.Integer.WideArithmetic.Spec 2 | :is Spec 3 | :const describes: "Integer.WideArithmetic" 4 | 5 | :it "implements special multiplication without overflow by returning a pair" 6 | product = U8[99].wide_multiply(200) 7 | assert: product.hi == 0x4d 8 | assert: product.lo == 0x58 9 | assert: product.hi.u16.bit_shl(8) + product.lo.u16 == U16[99] * 200 10 | -------------------------------------------------------------------------------- /spec/core/None.Spec.savi: -------------------------------------------------------------------------------- 1 | :class Savi.None.Spec 2 | :is Spec 3 | :const describes: "None" 4 | 5 | :it "emits nothing into a string" 6 | assert: "\(None)" == "" 7 | 8 | :it "inspects as its explicit name" 9 | assert: Inspect[None] == "None" 10 | -------------------------------------------------------------------------------- /spec/core/Numeric.Arithmetic.Spec.savi: -------------------------------------------------------------------------------- 1 | :class Savi.Numeric.Arithmetic.Spec 2 | :is Spec 3 | :const describes: "Numeric.Arithmetic" 4 | 5 | :it "applies arithmetic operations" 6 | assert: U32[6] + 30 == 36 7 | assert: I32[-6] + 30 == 24 8 | assert: F32[3] + 0.6 == 3.6 9 | assert: U32[30] - 6 == 24 10 | assert: I32[30] - -6 == 36 11 | assert: F32[3] - 0.6 == 2.4 12 | assert: U32[12] * 3 == 36 13 | assert: I32[12] * 3 == 36 14 | assert: F32[12] * 0.1 == 1.2 15 | assert: U32[36] / 10 == 3 16 | assert: I32[36] / 10 == 3 17 | assert: F32[36] / 10 == 3.6 18 | assert: U32[36] % 10 == 6 19 | assert: I32[36] % 10 == 6 20 | assert: F32[36] % 10 == 6 21 | assert: U32[36] / 0 == 0 22 | assert: I32[36] / 0 == 0 23 | assert: U32[36] % 0 == 0 24 | assert: I32[36] % 0 == 0 25 | assert: I8[-128] / -1 == 0 26 | assert: I8[-128] / -1 == 0 27 | -------------------------------------------------------------------------------- /spec/core/Pair.Spec.savi: -------------------------------------------------------------------------------- 1 | :class Savi.Pair.Spec 2 | :is Spec 3 | :const describes: "Pair" 4 | 5 | :it "has two elements" 6 | pair = Pair(String, U64).new("example", 99) 7 | assert: pair.first == "example" 8 | assert: pair.second == 99 9 | assert: pair.last == 99 10 | 11 | :it "calls its elements a key and a value" 12 | pair = Pair(String).new("color", "red") 13 | assert: pair.key == "color" 14 | assert: pair.value == "red" 15 | 16 | :it "calls its elements a head and a tail" 17 | pair = Pair(String, Pair(String, String)).new( 18 | "one" 19 | Pair(String, String).new("two", "three") 20 | ) 21 | assert: pair.head == "one" 22 | assert: pair.tail.head == "two" 23 | assert: pair.tail.tail == "three" 24 | 25 | :it "calls its elements a high and a low" 26 | pair = Pair(U32).new(0xFEDCBA98, 0x7654321) 27 | assert: pair.high == 0xFEDCBA98 28 | assert: pair.low == 0x7654321 29 | assert: pair.hi == 0xFEDCBA98 30 | assert: pair.lo == 0x7654321 31 | -------------------------------------------------------------------------------- /spec/core/U64.BCD.Spec.savi: -------------------------------------------------------------------------------- 1 | :class Savi.U64.BCD.Spec 2 | :is Spec 3 | :const describes: "U64.BCD" 4 | 5 | :it "can be converted back into an U64" 6 | assert: U64.BCD.new(0).u64 == U64[0] 7 | assert: U64.BCD.new(12345).u64 == U64[12345] 8 | assert: U64.BCD.new(U64.min_value).u64 == U64.min_value 9 | assert: U64.BCD.new(U64.max_value).u64 == U64.max_value 10 | 11 | :it "can test for zero" 12 | assert: U64.BCD.new(0).is_zero 13 | assert: U64.BCD.new(1).is_zero.is_false 14 | 15 | :it "can retrieve number of digits" 16 | assert: U64.BCD.new(0).ndigits == 1 17 | assert: U64.BCD.new(9).ndigits == 1 18 | assert: U64.BCD.new(10).ndigits == 2 19 | assert: U64.BCD.new(12345).ndigits == 5 20 | assert: U64.BCD.new(U64.max_value).ndigits == 20 21 | 22 | :it "can access digits by position" 23 | bcd = U64.BCD.new(123) 24 | assert: bcd.digit!(0) == 3 25 | assert: bcd.digit!(1) == 2 26 | assert: bcd.digit!(2) == 1 27 | assert error: bcd.digit!(3) 28 | 29 | :it "displays as decimal string representation" 30 | assert: "\(U64.BCD.new(0))" == "0" 31 | assert: "\(U64.BCD.new(123))" == "123" 32 | assert: "\(U64.BCD.new(U64.max_value))" == "18446744073709551615" 33 | -------------------------------------------------------------------------------- /spec/core/manifest.savi: -------------------------------------------------------------------------------- 1 | :manifest bin "spec" 2 | :sources "*.savi" 3 | :sources "../../core/_Ryu*.savi" // (load some private files for unit testing) 4 | 5 | :dependency Spec v0 6 | :from "github:savi-lang/Spec" 7 | :depends on Map 8 | :depends on Time 9 | :depends on Timer 10 | 11 | :transitive dependency Map v0 12 | :from "github:savi-lang/Map" 13 | 14 | :transitive dependency Time v0 15 | :from "github:savi-lang/Time" 16 | 17 | :transitive dependency Timer v0 18 | :from "github:savi-lang/Timer" 19 | :depends on Time 20 | -------------------------------------------------------------------------------- /spec/integration/error-manifests-copies-recursive/manifest.savi: -------------------------------------------------------------------------------- 1 | :manifest lib Thing1 2 | :sources "*.savi" 3 | :copies Thing3 // -> 3 -> 2 -> 1 -> ... 4 | 5 | :manifest lib Thing2 6 | :sources "*.savi" 7 | :copies Thing1 // -> 1 -> 3 -> 2 -> ... 8 | 9 | :manifest lib Thing3 10 | :sources "*.savi" 11 | :copies Thing2 // -> 2 -> 1 -> 3 -> ... 12 | 13 | :manifest lib Thing4 14 | :sources "*.savi" 15 | :copies Thing5 // -> 5 -> 6 (no recursion) 16 | 17 | :manifest lib Thing5 18 | :sources "*.savi" 19 | :copies Thing6 // -> 6 (no recursion) 20 | 21 | :manifest lib Thing6 22 | :sources "*.savi" 23 | 24 | :manifest "spec" 25 | :sources "*.savi" 26 | -------------------------------------------------------------------------------- /spec/integration/error-manifests-copies-wrong-name/manifest.savi: -------------------------------------------------------------------------------- 1 | :manifest lib Foo 2 | :sources "*.savi" 3 | 4 | :manifest lib Bar 5 | :sources "*.savi" 6 | :copies Food // mispelled `Foo` 7 | 8 | :manifest lib Baz 9 | :sources "*.savi" 10 | :copies TotallyBogusName // no similar name exists 11 | 12 | :manifest lib Speccy 13 | :sources "*.savi" 14 | :copies Spec // can't copy from foreign manifests - even standard library 15 | 16 | :manifest "main" 17 | :copies Foo 18 | :copies Bar 19 | :copies Baz 20 | :copies Speccy 21 | -------------------------------------------------------------------------------- /spec/integration/error-manifests-copies-wrong-name/savi.errors.txt: -------------------------------------------------------------------------------- 1 | 2 | Compilation Errors: 3 | 4 | --- 5 | 6 | There's no manifest named `Food` in this directory: 7 | from ./manifest.savi:6: 8 | :copies Food // mispelled `Foo` 9 | ^~~~ 10 | 11 | - maybe you meant `Foo`: 12 | from ./manifest.savi:1: 13 | :manifest lib Foo 14 | ^~~ 15 | 16 | --- 17 | 18 | There's no manifest named `TotallyBogusName` in this directory: 19 | from ./manifest.savi:10: 20 | :copies TotallyBogusName // no similar name exists 21 | ^~~~~~~~~~~~~~~~ 22 | 23 | --- 24 | 25 | There's no manifest named `Spec` in this directory: 26 | from ./manifest.savi:14: 27 | :copies Spec // can't copy from foreign manifests - even standard library 28 | ^~~~ 29 | -------------------------------------------------------------------------------- /spec/integration/error-manifests-many-but-no-main/another.manifest.savi: -------------------------------------------------------------------------------- 1 | :manifest lib "package-e" 2 | :sources "*.savi" 3 | 4 | :manifest bin "package-f" 5 | :sources "*.savi" 6 | -------------------------------------------------------------------------------- /spec/integration/error-manifests-many-but-no-main/manifest.savi: -------------------------------------------------------------------------------- 1 | :manifest lib "package-a" 2 | :sources "*.savi" 3 | 4 | :manifest bin "package-b" 5 | :sources "*.savi" 6 | 7 | :manifest lib "package-c" 8 | :sources "*.savi" 9 | 10 | :manifest bin "package-d" 11 | :sources "*.savi" 12 | -------------------------------------------------------------------------------- /spec/integration/error-manifests-many-but-no-main/savi.errors.txt: -------------------------------------------------------------------------------- 1 | 2 | Compilation Error: 3 | 4 | --- 5 | 6 | There is more than one manifest and it isn't clear which to use; please specify one explicitly by name 7 | 8 | - this is an available manifest: 9 | from ./another.manifest.savi:1: 10 | :manifest lib "package-e" 11 | ^~~~~~~~~~~ 12 | 13 | - this is an available manifest: 14 | from ./another.manifest.savi:4: 15 | :manifest bin "package-f" 16 | ^~~~~~~~~~~ 17 | 18 | - this is an available manifest: 19 | from ./manifest.savi:1: 20 | :manifest lib "package-a" 21 | ^~~~~~~~~~~ 22 | 23 | - this is an available manifest: 24 | from ./manifest.savi:4: 25 | :manifest bin "package-b" 26 | ^~~~~~~~~~~ 27 | 28 | - this is an available manifest: 29 | from ./manifest.savi:7: 30 | :manifest lib "package-c" 31 | ^~~~~~~~~~~ 32 | 33 | - this is an available manifest: 34 | from ./manifest.savi:10: 35 | :manifest bin "package-d" 36 | ^~~~~~~~~~~ 37 | -------------------------------------------------------------------------------- /spec/integration/error-manifests-many-main/another.manifest.savi: -------------------------------------------------------------------------------- 1 | :manifest "package-g" 2 | :sources "*.savi" 3 | 4 | :manifest lib "package-h" 5 | :sources "*.savi" 6 | 7 | :manifest bin "package-i" 8 | :sources "*.savi" 9 | -------------------------------------------------------------------------------- /spec/integration/error-manifests-many-main/manifest.savi: -------------------------------------------------------------------------------- 1 | :manifest "package-a" 2 | :sources "*.savi" 3 | 4 | :manifest lib "package-b" 5 | :sources "*.savi" 6 | 7 | :manifest bin "package-c" 8 | :sources "*.savi" 9 | 10 | :manifest "package-d" 11 | :sources "*.savi" 12 | 13 | :manifest lib "package-e" 14 | :sources "*.savi" 15 | 16 | :manifest bin "package-f" 17 | :sources "*.savi" 18 | -------------------------------------------------------------------------------- /spec/integration/error-manifests-many-main/savi.errors.txt: -------------------------------------------------------------------------------- 1 | 2 | Compilation Error: 3 | 4 | --- 5 | 6 | There can't be more than one main manifest in this directory; please mark some of these as `:manifest lib` or `:manifest bin` 7 | 8 | - this is a main manifest: 9 | from ./another.manifest.savi:1: 10 | :manifest "package-g" 11 | ^~~~~~~~~~~ 12 | 13 | - this is a main manifest: 14 | from ./manifest.savi:1: 15 | :manifest "package-a" 16 | ^~~~~~~~~~~ 17 | 18 | - this is a main manifest: 19 | from ./manifest.savi:10: 20 | :manifest "package-d" 21 | ^~~~~~~~~~~ 22 | -------------------------------------------------------------------------------- /spec/integration/error-manifests-no-sources/manifest.savi: -------------------------------------------------------------------------------- 1 | :manifest lib ExampleNoSources 2 | 3 | :manifest bin "example-no-sources" 4 | 5 | :manifest lib ExampleWithSources 6 | :sources "*.savi" 7 | 8 | :manifest bin "example-with-sources" 9 | :sources "*.savi" 10 | -------------------------------------------------------------------------------- /spec/integration/error-manifests-no-sources/savi.errors.txt: -------------------------------------------------------------------------------- 1 | 2 | Compilation Error: 3 | 4 | --- 5 | 6 | There is more than one manifest and it isn't clear which to use; please specify one explicitly by name 7 | 8 | - this is an available manifest: 9 | from ./manifest.savi:1: 10 | :manifest lib ExampleNoSources 11 | ^~~~~~~~~~~~~~~~ 12 | 13 | - this is an available manifest: 14 | from ./manifest.savi:3: 15 | :manifest bin "example-no-sources" 16 | ^~~~~~~~~~~~~~~~~~~~ 17 | 18 | - this is an available manifest: 19 | from ./manifest.savi:5: 20 | :manifest lib ExampleWithSources 21 | ^~~~~~~~~~~~~~~~~~ 22 | 23 | - this is an available manifest: 24 | from ./manifest.savi:8: 25 | :manifest bin "example-with-sources" 26 | ^~~~~~~~~~~~~~~~~~~~~~ 27 | -------------------------------------------------------------------------------- /spec/integration/error-manifests-non-unique-names/manifest.savi: -------------------------------------------------------------------------------- 1 | :manifest lib Thing1 2 | :sources "*.savi" 3 | 4 | :manifest lib Thing2 5 | :sources "*.savi" 6 | 7 | :manifest lib Thing3 8 | :sources "*.savi" 9 | 10 | // DEJA VU 11 | 12 | :manifest lib Thing2 13 | :sources "*.savi" 14 | 15 | :manifest lib Thing3 16 | :sources "*.savi" 17 | 18 | // DEJA VU ALL OVER AGAIN 19 | 20 | :manifest lib Thing3 21 | :sources "*.savi" 22 | -------------------------------------------------------------------------------- /spec/integration/error-manifests-non-unique-names/savi.errors.txt: -------------------------------------------------------------------------------- 1 | 2 | Compilation Errors: 3 | 4 | --- 5 | 6 | This manifest needs a unique name: 7 | from ./manifest.savi:12: 8 | :manifest lib Thing2 9 | ^~~~~~ 10 | 11 | - a conflicting one is here: 12 | from ./manifest.savi:4: 13 | :manifest lib Thing2 14 | ^~~~~~ 15 | 16 | --- 17 | 18 | This manifest needs a unique name: 19 | from ./manifest.savi:20: 20 | :manifest lib Thing3 21 | ^~~~~~ 22 | 23 | - a conflicting one is here: 24 | from ./manifest.savi:7: 25 | :manifest lib Thing3 26 | ^~~~~~ 27 | 28 | - a conflicting one is here: 29 | from ./manifest.savi:15: 30 | :manifest lib Thing3 31 | ^~~~~~ 32 | -------------------------------------------------------------------------------- /spec/integration/error-manifests-none/manifest.savi: -------------------------------------------------------------------------------- 1 | // This file is named as a manifest file, but it doesn't contain a manifest. 2 | // It only contains a class, which will be ignored during manifest probe. 3 | :class NotAManifest 4 | -------------------------------------------------------------------------------- /spec/integration/error-manifests-none/not-a-manifest.savi: -------------------------------------------------------------------------------- 1 | // This is a real manifest, but the filename isn't matching the pattern 2 | // "*.manifest.savi" or "manifest.savi", so it won't be found as a manifest. 3 | :manifest "not-in-a-manifest-dot-savi-file" 4 | :sources "*.savi" 5 | -------------------------------------------------------------------------------- /spec/integration/error-manifests-none/savi.errors.txt: -------------------------------------------------------------------------------- 1 | 2 | Compilation Error: 3 | 4 | --- 5 | 6 | No manifests found in the 'manifest.savi' files in this directory 7 | -------------------------------------------------------------------------------- /spec/integration/error-manifests-reserved-names/manifest.savi: -------------------------------------------------------------------------------- 1 | :manifest lib Savi // NOT OKAY 2 | :sources "*.savi" 3 | 4 | :manifest lib Savi.Thing // NOT OKAY 5 | :sources "*.savi" 6 | 7 | :manifest lib Saviour // okay - no dot after `Savi` 8 | :sources "*.savi" 9 | 10 | :manifest lib Thing.Savi // okay - `Savi` is not first 11 | :sources "*.savi" 12 | 13 | :manifest "savi" // okay - `savi` is lowercase 14 | :sources "*.savi" 15 | -------------------------------------------------------------------------------- /spec/integration/error-manifests-reserved-names/savi.errors.txt: -------------------------------------------------------------------------------- 1 | 2 | Compilation Errors: 3 | 4 | --- 5 | 6 | This name is reserved for core Savi packages: 7 | from ./manifest.savi:1: 8 | :manifest lib Savi // NOT OKAY 9 | ^~~~ 10 | 11 | --- 12 | 13 | This name is reserved for core Savi packages: 14 | from ./manifest.savi:4: 15 | :manifest lib Savi.Thing // NOT OKAY 16 | ^~~~~~~~~~ 17 | -------------------------------------------------------------------------------- /spec/integration/error-namespace-conflicts-and-sources-exclusions/manifest.savi: -------------------------------------------------------------------------------- 1 | :manifest "example" 2 | :sources "src/*.savi" 3 | :excluding "src/exclude-*.savi" 4 | :sources "src2/*.savi" 5 | :excluding "src2/exclude-a.savi" 6 | :excluding "src2/exclude-b.savi" 7 | :excluding "src/include-*.savi" // (this will have no effect, because it is 8 | // // nested in a :sources declaration 9 | // // which includes `src2` but not the `src` 10 | // // directory, and it can only limit 11 | // // the inclusion from that set - not others) 12 | 13 | // For convenience of reasoning, here's a summary of the types in each file: 14 | // 15 | // src/ 16 | // include-a exclude-a include-b exclude-b exclude-extra 17 | // Alice Andre Bob Bob Alice 18 | // Alex Alex Bernice Bill Andre 19 | // 20 | // src2/ 21 | // include-a exclude-a include-b exclude-b exclude-extra 22 | // Andre Alice Bob Bobby Bob 23 | // Alex Alex Bill Bill Bobby 24 | // 25 | // Given the exclusions in the manifest resolving correctly, we expect 26 | // only the following conflicts, and no more: 27 | // - Alex (x2) 28 | // - Bob (x3) (note that src2/exclude-extra.savi is not actually excluded) 29 | -------------------------------------------------------------------------------- /spec/integration/error-namespace-conflicts-and-sources-exclusions/savi.errors.txt: -------------------------------------------------------------------------------- 1 | 2 | Compilation Errors: 3 | 4 | --- 5 | 6 | This type conflicts with another declared type in the same package: 7 | from ./src2/exclude-extra.savi:1: 8 | :module Bob 9 | ^~~ 10 | 11 | - the other type with the same name is here: 12 | from ./src/include-b.savi:1: 13 | :module Bob 14 | ^~~ 15 | 16 | --- 17 | 18 | This type conflicts with another declared type in the same package: 19 | from ./src2/include-a.savi:2: 20 | :module Alex 21 | ^~~~ 22 | 23 | - the other type with the same name is here: 24 | from ./src/include-a.savi:2: 25 | :module Alex 26 | ^~~~ 27 | 28 | --- 29 | 30 | This type conflicts with another declared type in the same package: 31 | from ./src2/include-b.savi:1: 32 | :module Bob 33 | ^~~ 34 | 35 | - the other type with the same name is here: 36 | from ./src/include-b.savi:1: 37 | :module Bob 38 | ^~~ 39 | -------------------------------------------------------------------------------- /spec/integration/error-namespace-conflicts-and-sources-exclusions/src/exclude-a.savi: -------------------------------------------------------------------------------- 1 | :module Andre 2 | :module Alex 3 | -------------------------------------------------------------------------------- /spec/integration/error-namespace-conflicts-and-sources-exclusions/src/exclude-b.savi: -------------------------------------------------------------------------------- 1 | :module Bob 2 | :module Bill 3 | -------------------------------------------------------------------------------- /spec/integration/error-namespace-conflicts-and-sources-exclusions/src/exclude-extra.savi: -------------------------------------------------------------------------------- 1 | :module Alice 2 | :module Andre 3 | -------------------------------------------------------------------------------- /spec/integration/error-namespace-conflicts-and-sources-exclusions/src/include-a.savi: -------------------------------------------------------------------------------- 1 | :module Alice 2 | :module Alex 3 | -------------------------------------------------------------------------------- /spec/integration/error-namespace-conflicts-and-sources-exclusions/src/include-b.savi: -------------------------------------------------------------------------------- 1 | :module Bob 2 | :module Bernice 3 | -------------------------------------------------------------------------------- /spec/integration/error-namespace-conflicts-and-sources-exclusions/src2/exclude-a.savi: -------------------------------------------------------------------------------- 1 | :module Alice 2 | :module Alex 3 | -------------------------------------------------------------------------------- /spec/integration/error-namespace-conflicts-and-sources-exclusions/src2/exclude-b.savi: -------------------------------------------------------------------------------- 1 | :module Bobby 2 | :module Bill 3 | -------------------------------------------------------------------------------- /spec/integration/error-namespace-conflicts-and-sources-exclusions/src2/exclude-extra.savi: -------------------------------------------------------------------------------- 1 | :module Bob 2 | :module Bobby 3 | -------------------------------------------------------------------------------- /spec/integration/error-namespace-conflicts-and-sources-exclusions/src2/include-a.savi: -------------------------------------------------------------------------------- 1 | :module Andre 2 | :module Alex 3 | -------------------------------------------------------------------------------- /spec/integration/error-namespace-conflicts-and-sources-exclusions/src2/include-b.savi: -------------------------------------------------------------------------------- 1 | :module Bob 2 | :module Bill 3 | -------------------------------------------------------------------------------- /spec/integration/fix-manifests-missing-transitive-deps/savi.fix.after.dir/manifest.savi: -------------------------------------------------------------------------------- 1 | :manifest "example" 2 | :sources "main.savi" 3 | 4 | :dependency TCP v0 5 | :from "github:savi-lang/TCP" 6 | :depends on ByteStream 7 | :depends on IO 8 | :depends on OSError 9 | :depends on IPAddress 10 | 11 | :transitive dependency ByteStream v0 12 | :from "github:savi-lang/ByteStream" 13 | 14 | :transitive dependency IO v0 15 | :from "github:savi-lang/IO" 16 | :depends on ByteStream 17 | :depends on OSError 18 | 19 | :transitive dependency OSError v0 20 | :from "github:savi-lang/OSError" 21 | 22 | :transitive dependency IPAddress v0 23 | :from "github:savi-lang/IPAddress" 24 | -------------------------------------------------------------------------------- /spec/integration/fix-manifests-missing-transitive-deps/savi.fix.before.dir/main.savi: -------------------------------------------------------------------------------- 1 | :actor Main 2 | :new (env) 3 | TCP // just ensure the type name is reachable 4 | -------------------------------------------------------------------------------- /spec/integration/fix-manifests-missing-transitive-deps/savi.fix.before.dir/manifest.savi: -------------------------------------------------------------------------------- 1 | :manifest "example" 2 | :sources "main.savi" 3 | 4 | :dependency TCP v0 5 | :from "github:savi-lang/TCP" 6 | -------------------------------------------------------------------------------- /spec/integration/run-all.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -e 4 | 5 | # Set up path to the Savi compiler to use, based on the path provided via arg. 6 | SAVI=$(CDPATH= cd -- "$(dirname -- "${1:-build/savi-debug}")" && pwd)/$(basename "${1:-build/savi-debug}") 7 | 8 | # Change directory to the directory where this script is located. 9 | cd -- "$(dirname -- "$0")" 10 | 11 | 12 | # Start running integation tests. 13 | echo "Running integration tests..." 14 | echo 15 | for subdir in $(find . -maxdepth 1 -mindepth 1 -type d | cut -b 3- | sort --ignore-case); do 16 | ./run-one.sh $subdir $SAVI || did_fail="X" 17 | done 18 | 19 | # If any test failed, report overall failure here. 20 | if [ -n "$did_fail" ]; then 21 | echo "INTEGRATION TESTS FAILED" 22 | exit 1 23 | fi 24 | -------------------------------------------------------------------------------- /spec/integration/run-ffi-globals/manifest.savi: -------------------------------------------------------------------------------- 1 | :manifest "example" 2 | :sources "src/*.savi" 3 | -------------------------------------------------------------------------------- /spec/integration/run-ffi-globals/savi.run.output.txt: -------------------------------------------------------------------------------- 1 | initially... 2 | foo == 1 == 1 3 | bar == 2 4 | foo cpointer and foo_2 cpointer have the same address? True 5 | foo cpointer and bar cpointer have the same address? False 6 | --- 7 | foo = 42 returns 42 8 | foo == 42 == 42 9 | bar == 2 10 | --- 11 | bar = 99 returns 99 12 | foo == 42 == 42 13 | bar == 99 14 | --- 15 | setting foo via cpointer to 32 16 | foo == 32 == 32 17 | bar == 99 18 | -------------------------------------------------------------------------------- /spec/integration/run-ffi-globals/savi.run.test.sh: -------------------------------------------------------------------------------- 1 | $SAVI 2 | bin/example 3 | -------------------------------------------------------------------------------- /spec/integration/run-ffi-globals/vendor/mylib_globals.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | uint64_t foo = 1; 4 | uint64_t bar = 2; 5 | -------------------------------------------------------------------------------- /spec/integration/run-ffi-link-c-files/manifest.savi: -------------------------------------------------------------------------------- 1 | :manifest "example" 2 | :sources "src/*.savi" 3 | -------------------------------------------------------------------------------- /spec/integration/run-ffi-link-c-files/savi.run.output.txt: -------------------------------------------------------------------------------- 1 | 2 + 2 == 4! 2 | 4 - 2 == 2! 3 | -------------------------------------------------------------------------------- /spec/integration/run-ffi-link-c-files/savi.run.test.sh: -------------------------------------------------------------------------------- 1 | $SAVI 2 | bin/example 3 | -------------------------------------------------------------------------------- /spec/integration/run-ffi-link-c-files/src/Main.savi: -------------------------------------------------------------------------------- 1 | 2 | :ffi_link_c_files ( 3 | "../vendor/mylib_add.c" 4 | "../vendor/mylib_sub.c" 5 | ) 6 | 7 | :module _FFI 8 | :ffi mylib_add(a I32, b I32) I32 9 | :ffi mylib_sub(a I32, b I32) I32 10 | 11 | :actor Main 12 | :new (env) 13 | env.out.print("2 + 2 == \(_FFI.mylib_add(2, 2))!") 14 | env.out.print("4 - 2 == \(_FFI.mylib_sub(4, 2))!") 15 | -------------------------------------------------------------------------------- /spec/integration/run-ffi-link-c-files/vendor/mylib_add.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | // We included some headers mainly to test that the compiler can find them. 5 | // We may or may not actually use features of them here. 6 | 7 | int mylib_add(int a, int b) 8 | { 9 | return a + b; 10 | } 11 | -------------------------------------------------------------------------------- /spec/integration/run-ffi-link-c-files/vendor/mylib_sub.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | // We included some headers mainly to test that the compiler can find them. 5 | // We may or may not actually use features of them here. 6 | 7 | int mylib_sub(int a, int b) 8 | { 9 | return a - b; 10 | } 11 | -------------------------------------------------------------------------------- /spec/integration/run-ffi-link-cpp-files/manifest.savi: -------------------------------------------------------------------------------- 1 | :manifest "example" 2 | :sources "src/*.savi" 3 | -------------------------------------------------------------------------------- /spec/integration/run-ffi-link-cpp-files/savi.run.output.txt: -------------------------------------------------------------------------------- 1 | 7 returns 0 2 | 77 returns 77 3 | -------------------------------------------------------------------------------- /spec/integration/run-ffi-link-cpp-files/savi.run.test.sh: -------------------------------------------------------------------------------- 1 | $SAVI 2 | bin/example 3 | -------------------------------------------------------------------------------- /spec/integration/run-ffi-link-cpp-files/src/Main.savi: -------------------------------------------------------------------------------- 1 | 2 | :ffi_link_cpp_files ( 3 | "../vendor/mylib_wraps_cpp.cpp" 4 | ) 5 | 6 | :module _FFI 7 | :ffi mylib_wraps_cpp(value I32) I32 8 | 9 | :actor Main 10 | :new (env) 11 | env.out.print("7 returns \(_FFI.mylib_wraps_cpp(7))") 12 | env.out.print("77 returns \(_FFI.mylib_wraps_cpp(77))") 13 | -------------------------------------------------------------------------------- /spec/integration/run-ffi-link-cpp-files/vendor/mylib_wraps_cpp.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | static void throw_if_low(int value) { 4 | if (value < 10) 5 | throw std::runtime_error("whoops"); 6 | } 7 | 8 | extern "C" { 9 | 10 | int mylib_wraps_cpp(int value) 11 | { 12 | try { 13 | throw_if_low(value); 14 | return value; 15 | } catch (std::runtime_error err) { 16 | return 0; 17 | } 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /spec/integration/run-overlapping-manifest-sources/manifest.savi: -------------------------------------------------------------------------------- 1 | :manifest "example" 2 | :sources "src/Main*.savi" // matches both Main.savi and Main.Example.savi 3 | :sources "src/*Example.savi" // matches both Message.Example.savi and Main.Example.savi 4 | -------------------------------------------------------------------------------- /spec/integration/run-overlapping-manifest-sources/savi.run.output.txt: -------------------------------------------------------------------------------- 1 | Example output 2 | -------------------------------------------------------------------------------- /spec/integration/run-overlapping-manifest-sources/savi.run.test.sh: -------------------------------------------------------------------------------- 1 | $SAVI 2 | bin/example 3 | -------------------------------------------------------------------------------- /spec/integration/run-overlapping-manifest-sources/src/Main.Example.savi: -------------------------------------------------------------------------------- 1 | :actor Main.Example 2 | :new (env Env) 3 | env.out.print(Message.Example.string) 4 | -------------------------------------------------------------------------------- /spec/integration/run-overlapping-manifest-sources/src/Main.savi: -------------------------------------------------------------------------------- 1 | :actor Main 2 | :new (env) 3 | Main.Example.new(env) 4 | -------------------------------------------------------------------------------- /spec/integration/run-overlapping-manifest-sources/src/Message.Example.savi: -------------------------------------------------------------------------------- 1 | :module Message.Example 2 | :const string: "Example output" 3 | -------------------------------------------------------------------------------- /spec/integration/run-stdout/manifest.savi: -------------------------------------------------------------------------------- 1 | :manifest "example" 2 | :sources "src/*.savi" 3 | -------------------------------------------------------------------------------- /spec/integration/run-stdout/savi.run.output.txt: -------------------------------------------------------------------------------- 1 | Hello, World! 2 | -------------------------------------------------------------------------------- /spec/integration/run-stdout/savi.run.test.sh: -------------------------------------------------------------------------------- 1 | $SAVI 2 | bin/example 3 | -------------------------------------------------------------------------------- /spec/integration/run-stdout/src/Main.savi: -------------------------------------------------------------------------------- 1 | :actor Main 2 | :new (env) 3 | env.out.write("Hello, ") 4 | env.out.print("World!") 5 | -------------------------------------------------------------------------------- /spec/language/manifest.savi: -------------------------------------------------------------------------------- 1 | :manifest "spec-language" 2 | :sources "**/*.savi" 3 | -------------------------------------------------------------------------------- /spec/language/micro_test/micro_test.savi: -------------------------------------------------------------------------------- 1 | :module _FFI 2 | :ffi variadic libc_printf(format CPointer(U8)) I32 3 | :foreign_name printf // (we use this just to test `:foreign_name` here) 4 | 5 | :class val MicroTest 6 | :var env Env 7 | :new val (@env) 8 | :fun non _printf(f String, t String): _FFI.libc_printf(f.cstring, t.cstring) 9 | :fun non print_line_break: @_printf("%s", "\n") 10 | :fun "[]"(text String) MicroTestInstance 11 | MicroTestInstance.new(@env, text) 12 | 13 | :class MicroTestInstance 14 | :var env Env 15 | :var text String 16 | :new (@env, @text) 17 | :fun "pass="(pass Bool) 18 | if pass ( 19 | MicroTest._printf("%s", ".") 20 | | 21 | MicroTest._printf("\nfailure of '%s'!\n", @text) 22 | @env.exit_code = 1 // Mark the entire process as a failure. 23 | ) 24 | -------------------------------------------------------------------------------- /spec/language/semantics/displacing_assignment_spec.savi: -------------------------------------------------------------------------------- 1 | :module DisplacingAssignmentSpec 2 | :fun run(test MicroTest) 3 | displacable = "original" 4 | displaced = displacable <<= "new" 5 | test["<<= result"].pass = displaced == "original" 6 | test["<<= effect"].pass = displacable == "new" 7 | 8 | container_string = Container(String).new("original") 9 | test["<<= result 2"].pass = (container_string.value <<= "new") == "original" 10 | test["<<= effect 2"].pass = container_string.value == "new" 11 | -------------------------------------------------------------------------------- /spec/language/semantics/enum_spec.savi: -------------------------------------------------------------------------------- 1 | :enum EnumExample 2 | :member Integer48 48 3 | :member Hexadecimal49 0x31 4 | :member Char50 '2' 5 | 6 | :module EnumSpec 7 | :fun run(test MicroTest) 8 | array Array(String) = [] 9 | total U64 = 0 10 | 11 | test["can declare a member value as an integer"].pass = 12 | EnumExample.Integer48.u8 == 48 13 | 14 | test["can declare a member value as a hexadecimal integer"].pass = 15 | EnumExample.Hexadecimal49.u8 == 49 16 | 17 | test["can declare a member value as a char literal"].pass = 18 | EnumExample.Char50.u8 == 50 19 | 20 | test["can interpolate into a string with the member name"].pass = 21 | "\(EnumExample.Integer48)" == "EnumExample.Integer48" 22 | 23 | test["can iterate over each member of the enum"].pass = ( 24 | members Array(EnumExample) = [] 25 | EnumExample.each_enum_member -> (member | 26 | members << member 27 | ) 28 | members == [ 29 | EnumExample.Integer48 30 | EnumExample.Hexadecimal49 31 | EnumExample.Char50 32 | ] 33 | ) 34 | -------------------------------------------------------------------------------- /spec/language/semantics/number_spec.savi: -------------------------------------------------------------------------------- 1 | :module NumberSpec 2 | :fun run(test MicroTest) 3 | test["number separator hex"].pass = U64[0xFFFF_FFFF] == U64[0xFFFFFFFF] 4 | test["number separator bin"].pass = U64[0b0000_1000] == U64[0b00001000] 5 | test["number separator dec"].pass = U64[120_000_000] == U64[120000000] 6 | test["number separator float"].pass = F64[123_456.789_1] == F64[123456.7891] 7 | -------------------------------------------------------------------------------- /spec/language/semantics/reflection_spec.savi: -------------------------------------------------------------------------------- 1 | :module ReflectionSpec 2 | :fun run(test MicroTest) 3 | test["reflection_of_type.string String"].pass = 4 | (reflection_of_type "example").string == "String" 5 | test["reflection_of_type string String"].pass = 6 | "\(reflection_of_type "example")" == "String" 7 | test["reflection_of_type string String'ref"].pass = 8 | "\(reflection_of_type String.new)" == "String'ref" 9 | test["reflection_of_type string Array(U8)"].pass = 10 | "\(reflection_of_type Array(U8).new)" == "Array(U8)" 11 | 12 | test["reflection_of_runtime_type_name U64"].pass = 13 | (reflection_of_runtime_type_name U64[0]) == "U64" 14 | string_or_none (String'ref | None) = None 15 | test["reflection_of_runtime_type_name None"].pass = 16 | (reflection_of_runtime_type_name string_or_none) == "None" 17 | string_or_none = String.new 18 | test["reflection_of_runtime_type_name String"].pass = 19 | (reflection_of_runtime_type_name string_or_none) == "String" 20 | -------------------------------------------------------------------------------- /spec/language/semantics/return_spec.savi: -------------------------------------------------------------------------------- 1 | :module _EarlyReturn 2 | :fun non conditional(early Bool) U64 3 | if early ( 4 | return 33 5 | // Prove that codegen doesn't choke on unreachable code after return. 6 | totally = "unreachable" 7 | ) 8 | 11 9 | 10 | :module ReturnSpec 11 | :fun run(test MicroTest) 12 | test["return; with early return value"].pass = 13 | U64[33] == _EarlyReturn.conditional(True) 14 | 15 | test["return; without early return value"].pass = 16 | U64[11] == _EarlyReturn.conditional(False) 17 | -------------------------------------------------------------------------------- /spec/language/semantics/source_code_spec.savi: -------------------------------------------------------------------------------- 1 | :module SourceCodeSpec 2 | :fun run(test MicroTest) 3 | zero = U64[0] 4 | 5 | test["source_code_position_of_argument string"].pass = 6 | @source_code_position_of_argument_string(zero == 0) == "zero == 0" 7 | 8 | test["source_code_position_of_argument yield without yield param"].pass = 9 | @source_code_position_of_argument_yield -> (99) == "99" 10 | 11 | test["source_code_position_of_argument yield with yield param"].pass = 12 | @source_code_position_of_argument_yield -> (none | 99) == "99" 13 | 14 | :fun source_code_position_of_argument_string( 15 | arg Bool 16 | pos SourceCodePosition = source_code_position_of_argument arg 17 | ) 18 | pos.string 19 | 20 | :fun source_code_position_of_argument_yield( 21 | pos SourceCodePosition = source_code_position_of_argument yield 22 | ) 23 | :yields None for I32 24 | yield None 25 | pos.string 26 | -------------------------------------------------------------------------------- /spec/language/semantics/stack_address_of_variable_spec.savi: -------------------------------------------------------------------------------- 1 | :module StackAddressOfVariableSpec 2 | :fun run(test MicroTest) 3 | foo = 99 4 | bar = 99 5 | foo_addr = stack_address_of_variable foo 6 | bar_addr = stack_address_of_variable bar 7 | foo_addr_2 = stack_address_of_variable foo 8 | 9 | test["stack_address_of_variable foo not null"].pass = foo_addr.is_not_null 10 | 11 | test["stack_address_of_variable foo != bar"].pass = 12 | foo_addr.address != bar_addr.address 13 | 14 | test["stack_address_of_variable foo == foo"].pass = 15 | foo_addr.address == foo_addr_2.address 16 | -------------------------------------------------------------------------------- /spec/language/semantics/static_address_of_function_spec.savi: -------------------------------------------------------------------------------- 1 | :module StaticAddressOfFunctionSpec 2 | :fun foo: "Foo" 3 | :fun bar: "Bar" 4 | 5 | :fun run(test MicroTest) 6 | foo_addr = static_address_of_function @foo 7 | bar_addr = static_address_of_function @bar 8 | foo_addr_2 = static_address_of_function StaticAddressOfFunctionSpec.foo 9 | 10 | test["static_address_of_function foo not null"].pass = foo_addr.is_not_null 11 | 12 | test["static_address_of_function foo != bar"].pass = 13 | foo_addr.address != bar_addr.address 14 | 15 | test["static_address_of_function foo == foo"].pass = 16 | foo_addr.address == foo_addr_2.address 17 | -------------------------------------------------------------------------------- /spec/language/semantics/string_spec.savi: -------------------------------------------------------------------------------- 1 | :module StringSpec 2 | :fun run(test MicroTest) 3 | bar = "bar" 4 | 5 | test["compose string 1"].pass = "foo \(bar) baz" == "foo bar baz" 6 | test["compose string 2"].pass = "\(bar)\(bar)\(bar)" == "barbarbar" 7 | 8 | test["16-bit lowercase U escape"].pass = "\u0161" == "\xc5\xa1" 9 | test["32-bit capital U escape"].pass = "\U0001fa01" == "\xf0\x9f\xa8\x81" 10 | 11 | test["byte string literal size"].pass = 12 | b"\xde\xad\xbe\xef\xde\xad\xbe\xef".size == 8 13 | 14 | test["line break escape 1"].pass = "\ 15 | Hello, \ 16 | World\ 17 | !\ 18 | " == "Hello, World!" 19 | 20 | test["line break escape 2"].pass = b"\ 21 | \x00\x11\x22\x33\x44\x55\x66\x77\ 22 | \x88\x99\xaa\xbb\xcc\xdd\xee\xff\ 23 | ".size == 16 24 | 25 | test["heredoc-like string"].pass = <<< 26 | Hello 27 | World 28 | >>> == "Hello\nWorld" 29 | -------------------------------------------------------------------------------- /spec/language/semantics/struct_spec.savi: -------------------------------------------------------------------------------- 1 | :struct _StructWithFieldInitializer 2 | :let array Array(String): [] 3 | 4 | :struct _StructWithSingleStringField 5 | :let string String 6 | :new(@string) 7 | 8 | :module _FFI.Cast(A, B) 9 | :: An FFI-only utility function for bit-casting type A to B. 10 | :: 11 | :: This is only meant to be used for pointer types, and will 12 | :: fail badly if either A or B is not an ABI pointer type 13 | :: 14 | :: Obviously this utility function makes it easy to break 15 | :: memory safety, so it should be used with great care. 16 | :: 17 | :: Being private, It is only accessible from within the core library, 18 | :: though other libraries can set up similar mechanisms as well, 19 | :: provided that they are explicitly allowed by the root manifest to use FFI. 20 | :ffi pointer(input A) B 21 | :foreign_name savi_cast_pointer 22 | 23 | :module StructSpec 24 | :fun run(test MicroTest) 25 | s_w_f_i = _StructWithFieldInitializer.new 26 | s_w_f_i.array << "example" 27 | test["struct with field initializer"].pass = s_w_f_i.array == ["example"] 28 | 29 | s_w_s_s_f = _StructWithSingleStringField.new("example") 30 | test["struct FFI cast to its one field"].pass = 31 | _FFI.Cast(_StructWithSingleStringField, String).pointer(s_w_s_s_f) == "example" 32 | 33 | test["struct FFI cast from its one field"].pass = 34 | _FFI.Cast(String, _StructWithSingleStringField).pointer("example").string == "example" 35 | -------------------------------------------------------------------------------- /spec/language/semantics/test.savi: -------------------------------------------------------------------------------- 1 | :class Container(A) 2 | :var value A 3 | :new (@value) 4 | 5 | :struct ContainerStruct(A) 6 | :let value A 7 | :new (@value) 8 | 9 | :actor Main 10 | :new (env) 11 | test = MicroTest.new(env) 12 | test.print_line_break // TODO: move to MicroTest constructor and finalizer 13 | 14 | RegressionSpec.run(test) 15 | TrySpec.run(test) 16 | WhileSpec.run(test) 17 | ReturnSpec.run(test) 18 | YieldingCallSpec.run(test) 19 | StackAddressOfVariableSpec.run(test) 20 | StaticAddressOfFunctionSpec.run(test) 21 | ReflectionSpec.run(test) 22 | SourceCodeSpec.run(test) 23 | DisplacingAssignmentSpec.run(test) 24 | IdentitySpec.run(test) 25 | TraitNonSpec.run(test) 26 | EnumSpec.run(test) 27 | StringSpec.run(test) 28 | NumberSpec.run(test) 29 | StructSpec.run(test) 30 | 31 | test.print_line_break // TODO: move to MicroTest constructor and finalizer 32 | -------------------------------------------------------------------------------- /spec/language/semantics/trait_non.savi: -------------------------------------------------------------------------------- 1 | :trait non TraitNon 2 | :fun non example: "value" 3 | 4 | :module TraitNonDefault 5 | :is TraitNon 6 | 7 | :module TraitNonOverride 8 | :is TraitNon 9 | :fun non example: "other value" 10 | 11 | :module TraitNonSpec 12 | :fun run(test MicroTest) 13 | test["fun non call on trait singleton"].pass = 14 | TraitNon.example == "value" 15 | 16 | test["trait-inherited fun non call on module"].pass = 17 | TraitNonDefault.example == "value" 18 | 19 | test["overriden fun non call on module"].pass = 20 | TraitNonOverride.example == "other value" 21 | -------------------------------------------------------------------------------- /spec/spec_helper.cr: -------------------------------------------------------------------------------- 1 | require "spec" 2 | require "../src/savi" 3 | 4 | module Spec::Methods 5 | def fixture(*parts) 6 | path = File.join(__DIR__, "fixtures", *parts) 7 | content = File.read(path) 8 | 9 | Savi::Source.new( 10 | File.dirname(path), 11 | File.basename(path), 12 | content, 13 | Savi::Source::Package.new(File.dirname(path)), 14 | ) 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /src/savi.cr: -------------------------------------------------------------------------------- 1 | require "./savi/ext/**" 2 | require "./savi/**" 3 | 4 | module Savi 5 | VERSION = {{ env("SAVI_VERSION") || "unknown" }} 6 | LLVM_VERSION = {{ env("SAVI_LLVM_VERSION") || "unknown" }} 7 | 8 | def self.compiler 9 | Compiler::INSTANCE 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /src/savi/ast/gather.cr: -------------------------------------------------------------------------------- 1 | module Savi::AST::Gather 2 | # Return the list of all nodes under the given node that have annotations. 3 | def self.annotated_nodes(ctx, node : AST::Node) 4 | visitor = AnnotatedNodes.new 5 | node.accept(ctx, visitor) 6 | visitor.list 7 | end 8 | 9 | class AnnotatedNodes < AST::Visitor 10 | getter list = [] of AST::Node 11 | 12 | def visit(ctx : Compiler::Context, node : AST::Node) 13 | list << node if node.annotations 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /src/savi/compiler/load.cr: -------------------------------------------------------------------------------- 1 | ## 2 | # The purpose of the Load pass is to load more manifests packages into memory, 3 | # based on the dependency declarations found in the selected root manifest. 4 | # 5 | # This pass does not mutate the Program topology directly, though it instructs Context to compile more packages. 6 | # This pass does not mutate the AST. 7 | # This pass may raise a compilation error. 8 | # This pass keeps no state. 9 | # This pass produces no output state. 10 | # 11 | class Savi::Compiler::Load 12 | def initialize 13 | end 14 | 15 | def run(ctx) 16 | return if ctx.options.skip_manifest 17 | 18 | loaded = Set(Packaging::Manifest).new 19 | ctx.manifests.manifests_by_name.each_value { |manifest| 20 | next if loaded.includes?(manifest) 21 | loaded.add(manifest) 22 | 23 | ctx.compile_package(manifest) 24 | } 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /src/savi/compiler/run.cr: -------------------------------------------------------------------------------- 1 | require "llvm" 2 | 3 | ## 4 | # The purpose of the Run pass is to run the program built by the Binary pass. 5 | # 6 | # This pass does not mutate the Program topology. 7 | # This pass does not mutate the AST. 8 | # This pass does not raise any compilation errors. 9 | # This pass keeps temporary state (on the stack) at the program level. 10 | # This pass produces output state at the program level (the exit code). 11 | # !! This pass has the side-effect of executing the program. 12 | # 13 | class Savi::Compiler::Run 14 | getter! exitcode : Int32 15 | 16 | def run(ctx) 17 | target = ctx.code_gen.target_info 18 | bin_path = Binary.path_for(ctx) 19 | bin_path += ".exe" if target.windows? 20 | 21 | res = Process.run("/usr/bin/env", [bin_path], output: STDOUT, error: STDERR) 22 | 23 | if res.exit_reason == Process::ExitReason::Normal 24 | @exitcode = res.exit_code 25 | else 26 | STDERR.puts "Process exited with reason: #{res.exit_reason}" 27 | @exitcode = 1 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /src/savi/compiler/target.cr: -------------------------------------------------------------------------------- 1 | require "compiler/crystal/codegen/target" 2 | 3 | class Savi::Compiler::Target < Crystal::Codegen::Target 4 | def any_arm? 5 | architecture == "arm" || architecture == "aarch64" 6 | end 7 | 8 | def any_x86? 9 | architecture == "i386" || architecture == "x86_64" 10 | end 11 | 12 | def arm64? 13 | architecture == "aarch64" 14 | end 15 | 16 | def x86_64? 17 | architecture == "x86_64" 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /src/savi/ext/clang.cr: -------------------------------------------------------------------------------- 1 | require "clang" 2 | require "./clang/*" 3 | -------------------------------------------------------------------------------- /src/savi/ext/clang/cursor.cr: -------------------------------------------------------------------------------- 1 | struct Clang::Cursor 2 | # The upstream code doesn't deal with null pointers correctly, 3 | # so we have copied the code here and included an explicit null check. 4 | def brief_comment_text 5 | if (ptr = LibC.clang_Cursor_getBriefCommentText(self)) && !ptr.data.null? 6 | Clang.string(ptr) 7 | end 8 | end 9 | def raw_comment_text 10 | if (ptr = LibC.clang_Cursor_getRawCommentText(self)) && !ptr.data.null? 11 | Clang.string(ptr) 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /src/savi/ext/lib_gc.cr: -------------------------------------------------------------------------------- 1 | # Set the "warning proc" of the native Boehm GC library to a no-op, 2 | # so that it won't print warnings it would otherwise print, looking like: 3 | # 4 | # GC Warning: Repeated allocation of very large block (appr. size 528384): 5 | # May lead to memory leak and poor performance 6 | # 7 | # For more info, 8 | # - see https://forum.crystal-lang.org/t/gc-warning-repeated-allocation-of-very-large-block/928/11?u=jemc 9 | # - see https://github.com/crystal-lang/crystal/issues/2104#issuecomment-180471871 10 | 11 | LibGC.set_warn_proc ->(msg, word) {} 12 | -------------------------------------------------------------------------------- /src/savi/ext/llvm.cr: -------------------------------------------------------------------------------- 1 | require "llvm" 2 | require "./llvm/*" 3 | 4 | module LLVM 5 | def self.configured_default_target_triple 6 | {{ env("LLVM_DEFAULT_TARGET") }} || default_target_triple 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /src/savi/ext/llvm/basic_block.cr: -------------------------------------------------------------------------------- 1 | struct LLVM::BasicBlock 2 | def get_terminator 3 | ref = LibLLVM.get_basic_block_terminator(to_unsafe) 4 | Value.new(ref) if ref 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /src/savi/ext/llvm/basic_block_collection.cr: -------------------------------------------------------------------------------- 1 | require "./basic_block" 2 | 3 | struct LLVM::BasicBlockCollection 4 | def empty? 5 | !LibLLVM.get_first_basic_block(@function) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /src/savi/ext/llvm/context.cr: -------------------------------------------------------------------------------- 1 | class LLVM::Context 2 | def intptr(target_data : TargetData) : Type 3 | Type.new LibLLVM.intptr_type_in_context(self, target_data) 4 | end 5 | 6 | def struct_create_named(name : String) : Type 7 | Type.new LibLLVM.struct_create_named(self, name) 8 | end 9 | 10 | def const_inbounds_gep(type : Type, value : Value, indices : Array(Value)) 11 | Value.new LibLLVM.const_inbounds_gep_2(type, value, indices.to_unsafe.as(LibLLVM::ValueRef*), indices.size) 12 | end 13 | 14 | def const_bit_cast(value : Value, to_type : Type) 15 | Value.new LibLLVM.const_bit_cast(value, to_type) 16 | end 17 | 18 | {% for name in %w(shl and lshr) %} 19 | def const_{{name.id}}(lhs, rhs) 20 | # check_value(lhs) 21 | # check_value(rhs) 22 | 23 | Value.new LibLLVM.const_{{name.id}}(lhs, rhs) 24 | end 25 | {% end %} 26 | 27 | # (derived from existing parse_ir method) 28 | def parse_bitcode(buf : MemoryBuffer) 29 | ret = LibLLVM.parse_bitcode_in_context(self, buf, out mod, out msg) 30 | if ret != 0 && msg 31 | raise LLVM.string_and_dispose(msg) 32 | end 33 | Module.new(mod, self) 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /src/savi/ext/llvm/for_savi/main.cc: -------------------------------------------------------------------------------- 1 | // For convenience of compilation, we just use the C++ preprocessor to 2 | // combine the following C++ files here into one file, as if they were headers. 3 | // 4 | // This implies we won't be able to have static naming collisions in them, 5 | // but that's an acceptable limitation for us for now. 6 | // 7 | // Each C++ file defines a C function of the same name, which is meant to 8 | // expose some LLVM-related functionality (which cannot be accomplished with 9 | // the LLVM C API alone) in a C function that is FFI-callable from the compiler. 10 | // 11 | // That is, parts of the LLVM API are only exposed in C++ but not the C wrapper, 12 | // so if we want to use them, we need to wrap them ourselves here. 13 | 14 | #include "./LLVMLinkForSavi.cc" 15 | #include "./LLVMOptimizeForSavi.cc" 16 | #include "./LLVMCompileClangForSavi.cc" 17 | #include "./LLVMDefaultClangFlagsForSavi.cc" 18 | #include "./LLVMRemapDIDirectoryForSavi.cc" 19 | -------------------------------------------------------------------------------- /src/savi/ext/llvm/function_collection.cr: -------------------------------------------------------------------------------- 1 | struct LLVM::FunctionCollection 2 | def add(name, fun_type : LLVM::Type) 3 | # check_types_context(name, arg_types, ret_type) 4 | 5 | func = LibLLVM.add_function(@mod, name, fun_type) 6 | Function.new(func) 7 | end 8 | 9 | def add(name, fun_type : LLVM::Type) 10 | func = add(name, fun_type) 11 | yield func 12 | func 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /src/savi/ext/llvm/module.cr: -------------------------------------------------------------------------------- 1 | class LLVM::Module 2 | def add_named_metadata_operand(name : String, value : Value) : Nil 3 | LibLLVM.add_named_metadata_operand(self, name, value) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /src/savi/ext/llvm/target_data.cr: -------------------------------------------------------------------------------- 1 | struct LLVM::TargetData 2 | def big_endian? 3 | LibLLVM.byte_order(self) == LibLLVM::ByteOrdering::BigEndian 4 | end 5 | 6 | def little_endian? 7 | LibLLVM.byte_order(self) == LibLLVM::ByteOrdering::LittleEndian 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /src/savi/ext/llvm/type.cr: -------------------------------------------------------------------------------- 1 | struct LLVM::Type 2 | def const_struct(values : Array(Value)) 3 | Value.new LibLLVM.const_named_struct(self, 4 | (values.to_unsafe.as(LibLLVM::ValueRef*)), values.size) 5 | end 6 | 7 | def struct_set_body(element_types : Array(LLVM::Type), packed = false) 8 | raise "Not a Struct" unless kind == Kind::Struct 9 | LibLLVM.struct_set_body(to_unsafe, (element_types.to_unsafe.as(LibLLVM::TypeRef*)), element_types.size, packed ? 1 : 0) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /src/savi/ext/llvm/value_methods.cr: -------------------------------------------------------------------------------- 1 | module LLVM::ValueMethods 2 | def unnamed_addr=(unnamed_addr) 3 | LibLLVM.set_unnamed_addr(self, unnamed_addr ? 1 : 0) 4 | end 5 | 6 | def unnamed_addr? 7 | LibLLVM.is_unnamed_addr(self) != 0 8 | end 9 | 10 | def externally_initialized=(externally_initialized) 11 | LibLLVM.set_externally_initialized(self, externally_initialized ? 1 : 0) 12 | end 13 | 14 | def externally_initialized? 15 | LibLLVM.is_externally_initialized(self) != 0 16 | end 17 | 18 | def dll_storage_class : LLVM::DLLStorageClass 19 | LibLLVM.get_dll_storage_class(self) 20 | end 21 | 22 | def dll_storage_class=(cls : LLVM::DLLStorageClass) 23 | LibLLVM.set_dll_storage_class(self, cls) 24 | cls 25 | end 26 | 27 | def allocated_type 28 | Type.new LibLLVM.get_allocated_value_type(self) 29 | end 30 | 31 | def to_value 32 | Value.new to_unsafe 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /src/savi/ext/string.cr: -------------------------------------------------------------------------------- 1 | class String 2 | # Crystal has index and rindex and byte_index, but no byte_rindex! 3 | # So we add it here, at least for the simple version of it that only takes 4 | # a single byte as its search term - we leave out the trickier string search. 5 | def byte_rindex(byte : Int, offset = bytesize) 6 | (offset - 1).downto(0) do |i| 7 | if to_unsafe[i] == byte 8 | return i 9 | end 10 | end 11 | nil 12 | end 13 | 14 | # We also provide convenience wrappers for using a Char as the search byte. 15 | def byte_index(char : Char, offset = 0) 16 | byte_index(char.ord, offset) 17 | end 18 | def byte_rindex(char : Char, offset = bytesize) 19 | byte_rindex(char.ord, offset) 20 | end 21 | 22 | # Convenience method to split on the given delimiter once, raising an error 23 | # if the delimiter wasn't found, and otherwise returning as a tuple. 24 | def split2!(separator : Char) : {String, String} 25 | array = split(separator, 2, remove_empty: true) 26 | raise ArgumentError.new \ 27 | "expected #{self.inspect} to be splittable by #{separator.inspect}" \ 28 | unless array.size == 2 29 | {array[0], array[1]} 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /src/savi/ext/struct_ref.cr: -------------------------------------------------------------------------------- 1 | # This class is used as a workaround for cases where we want to have a struct 2 | # field that otherwise would break the rule that a struct cannot contain itself. 3 | # 4 | # Basically, this lets us sort of pretend of have referential transparency 5 | # by wrapping a struct in a class that forwards all methods to the struct. 6 | class StructRef(T) 7 | property value : T 8 | 9 | forward_missing_to @value 10 | 11 | def initialize(@value) 12 | end 13 | 14 | def ==(other : StructRef(T)) 15 | value == other.value 16 | end 17 | def ==(other_value : T) 18 | value == other_value 19 | end 20 | 21 | def hash(hasher) 22 | value.hash(hasher) 23 | end 24 | 25 | def to_s(io) 26 | value.to_s(io) 27 | end 28 | 29 | def inspect(io) 30 | value.inspect(io) 31 | end 32 | 33 | def pretty_print(pp) 34 | value.pretty_print(pp) 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /src/savi/ext/time.cr: -------------------------------------------------------------------------------- 1 | require "time" 2 | 3 | struct Time 4 | # A handy convenience method for measuring the time duration of a block. 5 | # It works like Time.measure but it returns the result value of the block 6 | # and prints the timing information, including an optional label and indent. 7 | def self.show(label : String, indent : Int32 = 0, &block : Nil -> U) : U forall U 8 | result : U? = nil 9 | time = Time.measure { result = block.call(nil) } 10 | puts "#{" " * indent}#{time} - #{label}" 11 | result.as(U) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /src/savi/packaging/dependency.cr: -------------------------------------------------------------------------------- 1 | struct Savi::Packaging::Dependency 2 | getter ast : AST::Declare 3 | getter name : AST::Identifier 4 | getter version : AST::Identifier? 5 | 6 | getter location_nodes = [] of AST::LiteralString 7 | getter revision_nodes = [] of AST::Identifier 8 | getter depends_on_nodes = [] of AST::Identifier 9 | 10 | def initialize(@ast, @name, @version, @transitive = false) 11 | end 12 | 13 | def transitive? 14 | @transitive 15 | end 16 | 17 | def accepts_version?(version : String) 18 | expected = @version.try(&.value) 19 | 20 | # If no version was specified, then every version is acceptable. 21 | return true unless expected 22 | 23 | version == expected || (version.starts_with?("#{expected}.")) 24 | end 25 | 26 | def location 27 | location_nodes.first?.try(&.value) || "" 28 | end 29 | 30 | def location_scheme : String 31 | location = location() 32 | return "" unless location.includes?(":") 33 | 34 | location.split(":", 2).first 35 | end 36 | 37 | def location_without_scheme : String 38 | location = location() 39 | return location unless location.includes?(":") 40 | 41 | location.split(":", 2).last 42 | end 43 | 44 | def is_location_relative_path? 45 | location_scheme == "relative" 46 | end 47 | 48 | def append_pos 49 | ast.span_pos(ast.pos.source).next_line_start_as_pos 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /src/savi/packaging/manifest.cr: -------------------------------------------------------------------------------- 1 | struct Savi::Packaging::Manifest 2 | getter ast : AST::Declare 3 | getter name : AST::Identifier 4 | getter kind : AST::Identifier 5 | getter copies_names = [] of AST::Identifier 6 | getter provides_names = [] of AST::Identifier 7 | getter sources_paths = [] of {AST::LiteralString, Array(AST::LiteralString)} 8 | getter dependencies = [] of Dependency 9 | 10 | def initialize(@ast, @name, @kind) 11 | end 12 | 13 | def bin_path 14 | File.join(name.pos.source.dirname, "bin", name.value) 15 | end 16 | 17 | def deps_path 18 | File.join(name.pos.source.dirname, "deps") 19 | end 20 | 21 | def is_main? 22 | @kind.value == "main" 23 | end 24 | 25 | def is_lib? 26 | @kind.value == "lib" 27 | end 28 | 29 | def is_bin? 30 | @kind.value == "bin" 31 | end 32 | 33 | def is_whole_program? 34 | !is_lib? 35 | end 36 | 37 | def append_pos 38 | ast.span_pos(ast.pos.source).next_line_start_as_pos 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /src/savi/parser.cr: -------------------------------------------------------------------------------- 1 | require "pegmatite" 2 | 3 | module Savi::Parser 4 | @@cache = {} of String => {Source, AST::Document} 5 | def self.parse(source : Source) 6 | if (cache_result = @@cache[source.path]?; cache_result) 7 | cached_source, cached_ast = cache_result 8 | return cached_ast if cached_source == source 9 | end 10 | 11 | grammar = 12 | case source.language 13 | when :savi then Grammar 14 | else raise NotImplementedError.new("#{source.language} language parsing") 15 | end 16 | 17 | Builder.build(Pegmatite.tokenize(grammar, source.content), source) 18 | 19 | .tap do |result| 20 | @@cache[source.path] = {source, result} 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /tooling/coc-nvim/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /lib 3 | -------------------------------------------------------------------------------- /tooling/coc-nvim/.npmignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | src 3 | tsconfig.json 4 | tslint.json 5 | *.map 6 | webpack.config.js -------------------------------------------------------------------------------- /tooling/coc-nvim/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to the "savi" extension will be documented in this file. 4 | 5 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. 6 | 7 | ## [Unreleased] 8 | 9 | - Initial release -------------------------------------------------------------------------------- /tooling/coc-nvim/README.md: -------------------------------------------------------------------------------- 1 | # Savi support for coc.nvim 2 | 3 | [![npm version](http://img.shields.io/npm/v/coc-savi.svg?style=flat)](https://npmjs.org/package/coc-savi "View this project on npm") 4 | 5 | This extension adds various Intellisense features for the Savi Programming Language to the coc.nvim. 6 | 7 | ## Prerequisites 8 | 9 | This extension doesn't provide savi language support to vim yet, so you need to install [savi-vim](https://github.com/teggotic/vim-savi) 10 | 11 | This extension uses the `savi` binary to run the Savi language server in the background, so you'll need to have a working installation of Savi (see main README), with the intended `savi` binary in your `PATH`. 12 | -------------------------------------------------------------------------------- /tooling/coc-nvim/src/@types/tmp/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'tmp' { 2 | type Options = { 3 | keep: boolean | null; 4 | tries: number | null; 5 | template: string | null; 6 | name: string | null; 7 | dir: string | null; 8 | prefix: string | null; 9 | postfix: string | null; 10 | tmpdir: string | null; 11 | unsafeCleanup: boolean | null; 12 | detachDescriptor: boolean | null; 13 | discardDescriptor: boolean | null; 14 | } 15 | type simpleCallback = () => any 16 | type cleanupCallback = (next?: simpleCallback) => any 17 | type fileCallback = (err: Error | null, name: string, fd: number, fn: cleanupCallback) => any 18 | type DirSyncObject = { 19 | name: string; 20 | removeCallback: fileCallback; 21 | } 22 | export function dirSync(options?: Options): DirSyncObject 23 | } -------------------------------------------------------------------------------- /tooling/coc-nvim/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "moduleResolution": "node", 5 | "target": "es6", 6 | "outDir": "lib", 7 | "lib": ["es6"], 8 | "sourceMap": true, 9 | "rootDir": "src", 10 | "strict": false, 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true, 13 | "noImplicitReturns": true, 14 | "noFallthroughCasesInSwitch": true 15 | }, 16 | "exclude": [ 17 | "node_modules", 18 | ".vscode-test" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /tooling/coc-nvim/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | entry: './src/index.ts', 5 | target: 'node', 6 | mode: 'none', 7 | resolve: { 8 | extensions: ['.js', '.ts'] 9 | }, 10 | externals: { 11 | 'coc.nvim': 'commonjs coc.nvim' 12 | }, 13 | module: { 14 | rules: [{ 15 | test: /\.ts$/, 16 | exclude: /node_modules/, 17 | use: [{ 18 | loader: 'ts-loader', 19 | options: { 20 | compilerOptions: { 21 | "sourceMap": true, 22 | } 23 | } 24 | }] 25 | }] 26 | }, 27 | output: { 28 | path: path.join(__dirname, 'lib'), 29 | filename: 'index.js', 30 | libraryTarget: "commonjs", 31 | }, 32 | plugins: [ 33 | ], 34 | node: { 35 | __dirname: false, 36 | __filename: false 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tooling/emacs/savi-mode/README.md: -------------------------------------------------------------------------------- 1 | # `savi-mode` for Emacs 2 | 3 | This major mode provides the following features for Savi: 4 | - Syntax highlighting 5 | - `savi server` support via eglot 6 | - Hovers 7 | - Diagnostics 8 | - Formatting 9 | - etc. 10 | - Indentation similar to other editors 11 | - Hopefully many more in the future... 12 | 13 | ### Setup 14 | 15 | This is not yet packaged on MELPA, but it could be in the future if there's enough interest. 16 | 17 | For a quick setup, add this to your config file: 18 | 19 | ```lisp 20 | (load-file "~/location/of/this/directory/savi-mode.el") 21 | (savi-configure-server "~/path/to/savi") 22 | ``` 23 | This provides the default configuration and sets up hooks to start the language server. Make sure you have eglot installed if your Emacs version is old enough. 24 | 25 | If you would like more control, read "savi-mode.el". It should be pretty easy to make it do what you want it to. 26 | -------------------------------------------------------------------------------- /tooling/pygments/README.md: -------------------------------------------------------------------------------- 1 | # Savi Lexer for Pygments 2 | 3 | This subdirectory contains source files which are mirrored in the [Pygments repository](https://github.com/pygments/pygments). 4 | 5 | That is, the [Pygments repository](https://github.com/pygments/pygments) forms the vehicle for releasing changes to the public, so any changes made in this subdirectory (apart from this README.md) should be propagated forward to the corresponding files in that repository. 6 | 7 | Changes should also be reflected in the other pygments-like lexers that we have tooling for here in the Savi repository, including: 8 | - `tooling/vscode/syntaxes` 9 | - `tooling/rouge` 10 | 11 | You'll notice that those other lexers are nearly identical in structure to this one (apart from being written using another language/framework), and we'd like to keep it that way, so please ensure changes are propagated accordingly. 12 | -------------------------------------------------------------------------------- /tooling/pygments/tests/examplefiles/savi/example.savi: -------------------------------------------------------------------------------- 1 | :actor Main 2 | :new (env) 3 | Spec.Process.run(env, [ 4 | Spec.Run(AdditionSpec).new(env) 5 | ]) 6 | 7 | :class AdditionSpec 8 | :is Spec 9 | :const describes: "Addition" 10 | 11 | :: Return the number 2 (written in hex, just for fun). 12 | :const _two U64'val: 0x02 13 | 14 | :it "adds two twos" 15 | assert: @_two + '\x02' == 4 16 | 17 | :: Raise an error if the argument is positive. 18 | :fun non add_overflow!(a U64'val, b U64'val): a +! b 19 | 20 | :it "can error on overflow" 21 | integers Array(U64)'val = [99, 100, 101] 22 | 23 | // Check addition overflow for various pairs of addends. 24 | assert error: add_overflow!(U64.max_value, 1) 25 | assert no_error: add_overflow!(U64.max_value, 0) 26 | assert no_error: add_overflow!(integers[0]!, 1) 27 | 28 | // Print a bit of extra information using string interpolation. 29 | @env.out.print("The first integer is \(integers[0]!)") 30 | -------------------------------------------------------------------------------- /tooling/rouge/README.md: -------------------------------------------------------------------------------- 1 | # Savi Lexer for Rouge 2 | 3 | This subdirectory contains source files which are mirrored in the [Rouge repository](https://github.com/rouge-ruby/rouge). 4 | 5 | That is, the [Rouge repository](https://github.com/rouge-ruby/rouge) forms the vehicle for releasing changes to the public, so any changes made in this subdirectory (apart from this README.md) should be propagated forward to the corresponding files in that repository. 6 | 7 | Changes should also be reflected in the other pygments-like lexers that we have tooling for here in the Savi repository, including: 8 | - `tooling/vscode/syntaxes` 9 | - `tooling/pygments` 10 | 11 | You'll notice that those other lexers are nearly identical in structure to this one (apart from being written using another language/framework), and we'd like to keep it that way, so please ensure changes are propagated accordingly. 12 | -------------------------------------------------------------------------------- /tooling/rouge/demos/savi: -------------------------------------------------------------------------------- 1 | :actor Main 2 | :new (env) 3 | Spec.Process.run(env, [ 4 | Spec.Run(AdditionSpec).new(env) 5 | ]) 6 | 7 | :class AdditionSpec 8 | :is Spec 9 | :const describes: "Addition" 10 | 11 | :: Return the number 2 (written in hex, just for fun). 12 | :const _two U64'val: 0x02 13 | 14 | :it "adds two twos" 15 | assert: @_two + '\x02' == 4 16 | 17 | :: Raise an error if the argument is positive. 18 | :fun non add_overflow!(a U64'val, b U64'val): a +! b 19 | 20 | :it "can error on overflow" 21 | integers Array(U64)'val = [99, 100, 101] 22 | 23 | // Check addition overflow for various pairs of addends. 24 | assert error: add_overflow!(U64.max_value, 1) 25 | assert no_error: add_overflow!(U64.max_value, 0) 26 | assert no_error: add_overflow!(integers[0]!, 1) 27 | 28 | // Print a bit of extra information using string interpolation. 29 | @env.out.print("The first integer is \(integers[0]!)") 30 | -------------------------------------------------------------------------------- /tooling/rouge/spec/lexers/savi_spec.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- # 2 | # frozen_string_literal: true 3 | 4 | describe Rouge::Lexers::Savi do 5 | let(:subject) { Rouge::Lexers::Savi.new } 6 | 7 | describe 'guessing' do 8 | include Support::Guessing 9 | 10 | it 'guesses by filename' do 11 | assert_guess :filename => 'foo.savi' 12 | end 13 | 14 | it 'guesses by mimetype' do 15 | assert_guess :mimetype => 'text/x-savi' 16 | assert_guess :mimetype => 'application/x-savi' 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /tooling/rouge/spec/visual/samples/savi: -------------------------------------------------------------------------------- 1 | :actor Main 2 | :new (env) 3 | Spec.Process.run(env, [ 4 | Spec.Run(AdditionSpec).new(env) 5 | ]) 6 | 7 | :class AdditionSpec 8 | :is Spec 9 | :const describes: "Addition" 10 | 11 | :: Return the number 2 (written in hex, just for fun). 12 | :const _two U64'val: 0x02 13 | 14 | :it "adds two twos" 15 | assert: @_two + '\x02' == 4 16 | 17 | :: Raise an error if the argument is positive. 18 | :fun non add_overflow!(a U64'val, b U64'val): a +! b 19 | 20 | :it "can error on overflow" 21 | integers Array(U64)'val = [99, 100, 101] 22 | 23 | // Check addition overflow for various pairs of addends. 24 | assert error: add_overflow!(U64.max_value, 1) 25 | assert no_error: add_overflow!(U64.max_value, 0) 26 | assert no_error: add_overflow!(integers[0]!, 1) 27 | 28 | // Print a bit of extra information using string interpolation. 29 | @env.out.print("The first integer is \(integers[0]!)") 30 | -------------------------------------------------------------------------------- /tooling/vscode/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /out 3 | -------------------------------------------------------------------------------- /tooling/vscode/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that launches the extension inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "runtimeExecutable": "${execPath}", 13 | "args": [ 14 | "--extensionDevelopmentPath=${workspaceFolder}" 15 | ] 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /tooling/vscode/.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | .gitignore 4 | vsc-extension-quickstart.md 5 | -------------------------------------------------------------------------------- /tooling/vscode/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to the "savi" extension will be documented in this file. 4 | 5 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. 6 | 7 | ## [Unreleased] 8 | 9 | - Initial release -------------------------------------------------------------------------------- /tooling/vscode/README.md: -------------------------------------------------------------------------------- 1 | # Savi support for VS Code 2 | 3 | [![](https://vsmarketplacebadge.apphb.com/version/savi-lang.savi.svg)](https://marketplace.visualstudio.com/items?itemName=savi-lang.savi) 4 | 5 | This extension adds to VS Code syntax highlighting and various Intellisense features for the Savi Programming Language. 6 | 7 | ## Prerequisites 8 | 9 | This extension uses the `savi` binary to run the Savi language server in the background, so you'll need to have a working installation of Savi (see main README), with the intended `savi` binary in your `PATH`. 10 | -------------------------------------------------------------------------------- /tooling/vscode/language-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | // symbol used for single line comment. Remove this entry if your language does not support line comments 4 | "lineComment": "//" 5 | }, 6 | // symbols used as brackets 7 | "brackets": [ 8 | ["{", "}"], 9 | ["[", "]"], 10 | ["(", ")"] 11 | ], 12 | // symbols that are auto closed when typing 13 | "autoClosingPairs": [ 14 | ["{", "}"], 15 | ["[", "]"], 16 | ["(", ")"], 17 | ["\"", "\""] 18 | ], 19 | // symbols that that can be used to surround a selection 20 | "surroundingPairs": [ 21 | ["{", "}"], 22 | ["[", "]"], 23 | ["(", ")"], 24 | ["\"", "\""], 25 | ["'", "'"] 26 | ] 27 | } -------------------------------------------------------------------------------- /tooling/vscode/src/@types/tmp/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'tmp' { 2 | type Options = { 3 | keep: boolean | null; 4 | tries: number | null; 5 | template: string | null; 6 | name: string | null; 7 | dir: string | null; 8 | prefix: string | null; 9 | postfix: string | null; 10 | tmpdir: string | null; 11 | unsafeCleanup: boolean | null; 12 | detachDescriptor: boolean | null; 13 | discardDescriptor: boolean | null; 14 | } 15 | type simpleCallback = () => any 16 | type cleanupCallback = (next?: simpleCallback) => any 17 | type fileCallback = (err: Error | null, name: string, fd: number, fn: cleanupCallback) => any 18 | type DirSyncObject = { 19 | name: string; 20 | removeCallback: fileCallback; 21 | } 22 | export function dirSync(options?: Options): DirSyncObject 23 | } -------------------------------------------------------------------------------- /tooling/vscode/syntaxes/codeblock.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileTypes": [], 3 | "injectionSelector": "L:text.html.markdown", 4 | "patterns": [ 5 | { 6 | "include": "#savi-code-block" 7 | } 8 | ], 9 | "repository": { 10 | "savi-code-block": { 11 | "begin": "(^|\\G)(\\s*)(\\`{3,}|~{3,})\\s*(?i:(savi)(\\s+[^`~]*)?$)", 12 | "name": "markup.fenced_code.block.markdown", 13 | "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", 14 | "beginCaptures": { 15 | "3": { 16 | "name": "punctuation.definition.markdown" 17 | }, 18 | "5": { 19 | "name": "fenced_code.block.language" 20 | }, 21 | "6": { 22 | "name": "fenced_code.block.language.attributes" 23 | } 24 | }, 25 | "endCaptures": { 26 | "3": { 27 | "name": "punctuation.definition.markdown" 28 | } 29 | }, 30 | "patterns": [ 31 | { 32 | "begin": "(^|\\G)(\\s*)(.*)", 33 | "while": "(^|\\G)(?!\\s*([`~]{3,})\\s*$)", 34 | "contentName": "meta.embedded.block.savi", 35 | "patterns": [ 36 | { 37 | "include": "source.savi" 38 | } 39 | ] 40 | } 41 | ] 42 | } 43 | }, 44 | "scopeName": "markdown.savi.codeblock" 45 | } 46 | -------------------------------------------------------------------------------- /tooling/vscode/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "moduleResolution": "node", 5 | "target": "es6", 6 | "outDir": "out", 7 | "lib": ["es6"], 8 | "sourceMap": true, 9 | "rootDir": "src", 10 | "strict": true, 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true, 13 | "noImplicitReturns": true, 14 | "noFallthroughCasesInSwitch": true 15 | }, 16 | "exclude": [ 17 | "node_modules", 18 | ".vscode-test" 19 | ] 20 | } 21 | --------------------------------------------------------------------------------