├── .github └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── builtin ├── array.sk ├── bool.sk ├── class.sk ├── dict.sk ├── enumerable.sk ├── error.sk ├── file.sk ├── float.sk ├── fn.sk ├── index.sk ├── int.sk ├── math.sk ├── maybe.sk ├── metaclass.sk ├── mutable_string.sk ├── never.sk ├── object.sk ├── pair.sk ├── random.sk ├── readable.sk ├── result.sk ├── shiika_internal.sk ├── string.sk ├── time.sk ├── triple.sk └── void.sk ├── doc ├── .gitignore ├── guide │ ├── .gitignore │ ├── book.toml │ └── src │ │ ├── SUMMARY.md │ │ ├── basic_types.md │ │ ├── classes.md │ │ ├── develop-in-docker-container.md │ │ ├── enums.md │ │ ├── expressions.md │ │ ├── install.md │ │ ├── ruby.md │ │ └── setup_windows.md ├── new_runtime.md ├── shg │ ├── book.toml │ └── src │ │ ├── README.md │ │ ├── SUMMARY.md │ │ ├── ast.md │ │ ├── code_gen.md │ │ ├── corelib.md │ │ ├── debug.md │ │ ├── design_notes │ │ └── enum.md │ │ ├── hir.md │ │ ├── parser.md │ │ ├── perf.md │ │ └── tests.md └── spec │ ├── .gitignore │ ├── book.toml │ └── src │ ├── SUMMARY.md │ ├── basic_concepts.md │ ├── classes.md │ ├── enums.md │ ├── expressions.md │ └── types.md ├── docker-compose.yml ├── examples ├── fib.expected_out.txt ├── fib.sk ├── hello.expected_out.txt ├── hello.sk ├── lifegame.expected_out.txt ├── lifegame.sk ├── mandel.expected_out.pbm ├── mandel.sk ├── ray.expected_out.ppm └── ray.sk ├── lib ├── shiika_ast │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── lib.rs │ │ ├── location.rs │ │ ├── token.rs │ │ └── visitor.rs ├── shiika_core │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── lib.rs │ │ ├── names.rs │ │ ├── names │ │ ├── class_name.rs │ │ ├── const_name.rs │ │ ├── method_name.rs │ │ ├── module_name.rs │ │ ├── namespace.rs │ │ └── type_name.rs │ │ ├── ty.rs │ │ └── ty │ │ ├── erasure.rs │ │ ├── lit_ty.rs │ │ ├── term_ty.rs │ │ ├── typaram.rs │ │ └── typaram_ref.rs ├── shiika_ffi │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── async_.rs │ │ ├── async_ │ │ └── chiika_env.rs │ │ ├── core_class.rs │ │ ├── core_class │ │ ├── bool.rs │ │ ├── int.rs │ │ └── object.rs │ │ └── lib.rs ├── shiika_ffi_macro │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── lib.rs │ │ ├── shiika_const_ref.rs │ │ └── shiika_method_ref.rs ├── shiika_parser │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── ast_builder.rs │ │ ├── base.rs │ │ ├── definition_parser.rs │ │ ├── error.rs │ │ ├── expression_parser.rs │ │ ├── lexer.rs │ │ ├── lib.rs │ │ └── source_file.rs ├── skc_ast2hir │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── accessors.rs │ │ ├── class_dict.rs │ │ ├── class_dict │ │ ├── build_wtable.rs │ │ ├── found_method.rs │ │ ├── indexing.rs │ │ ├── query.rs │ │ └── type_index.rs │ │ ├── convert_exprs.rs │ │ ├── convert_exprs │ │ ├── block.rs │ │ ├── lvar.rs │ │ ├── method_call.rs │ │ └── params.rs │ │ ├── ctx_stack.rs │ │ ├── error.rs │ │ ├── hir_maker.rs │ │ ├── hir_maker_context.rs │ │ ├── lib.rs │ │ ├── method_dict.rs │ │ ├── pattern_match.rs │ │ ├── rustlib_methods.rs │ │ ├── type_inference.rs │ │ ├── type_inference │ │ └── tmp_ty.rs │ │ ├── type_system.rs │ │ └── type_system │ │ ├── subtyping.rs │ │ └── type_checking.rs ├── skc_async_experiment │ ├── Cargo.toml │ ├── README.md │ ├── src │ │ ├── build.rs │ │ ├── build │ │ │ ├── cargo_builder.rs │ │ │ ├── compiler.rs │ │ │ ├── exe_builder.rs │ │ │ ├── lib_builder.rs │ │ │ ├── linker.rs │ │ │ └── loader.rs │ │ ├── cli.rs │ │ ├── cli │ │ │ └── command_line_options.rs │ │ ├── codegen.rs │ │ ├── codegen │ │ │ ├── codegen_context.rs │ │ │ ├── constants.rs │ │ │ ├── instance.rs │ │ │ ├── intrinsics.rs │ │ │ ├── llvm_struct.rs │ │ │ ├── mir_analysis.rs │ │ │ ├── mir_analysis │ │ │ │ └── list_constants.rs │ │ │ ├── value.rs │ │ │ └── vtables.rs │ │ ├── hir.rs │ │ ├── hir │ │ │ ├── expr.rs │ │ │ ├── ty.rs │ │ │ ├── typing.rs │ │ │ ├── untyped.rs │ │ │ └── visitor.rs │ │ ├── hir_building.rs │ │ ├── hir_building │ │ │ └── define_new.rs │ │ ├── hir_to_mir.rs │ │ ├── hir_to_mir │ │ │ ├── collect_allocs.rs │ │ │ └── constants.rs │ │ ├── lib.rs │ │ ├── mir.rs │ │ ├── mir │ │ │ ├── expr.rs │ │ │ ├── rewriter.rs │ │ │ ├── ty.rs │ │ │ ├── verifier.rs │ │ │ └── visitor.rs │ │ ├── mir_lowering.rs │ │ ├── mir_lowering │ │ │ ├── async_splitter.rs │ │ │ ├── asyncness_check.rs │ │ │ ├── pass_async_env.rs │ │ │ └── resolve_env_op.rs │ │ ├── names.rs │ │ ├── package.rs │ │ ├── prelude.rs │ │ ├── run.rs │ │ └── targets.rs │ └── tests │ │ ├── cps.rs │ │ ├── cps │ │ ├── async.sk │ │ ├── if_async_then.sk │ │ └── if_sync.sk │ │ └── snapshots │ │ ├── cps__cps_transformation@async.sk.snap │ │ ├── cps__cps_transformation@if_async_then.sk.snap │ │ └── cps__cps_transformation@if_sync.sk.snap ├── skc_codegen │ ├── Cargo.toml │ └── src │ │ ├── boxing.rs │ │ ├── code_gen_context.rs │ │ ├── gen_exprs.rs │ │ ├── lambda.rs │ │ ├── lib.rs │ │ ├── utils.rs │ │ ├── values.rs │ │ ├── vtable.rs │ │ └── wtable.rs ├── skc_corelib │ ├── Cargo.toml │ └── src │ │ ├── class.rs │ │ ├── fn_x.rs │ │ ├── lib.rs │ │ ├── rustlib_methods.rs │ │ ├── shiika_internal_memory.rs │ │ └── shiika_internal_ptr.rs ├── skc_error │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── lib.rs │ │ └── report_builder.rs ├── skc_hir │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ ├── pattern_match.rs │ │ ├── signature.rs │ │ ├── signatures.rs │ │ ├── sk_method.rs │ │ ├── sk_type.rs │ │ ├── sk_type │ │ ├── sk_class.rs │ │ ├── sk_module.rs │ │ ├── sk_type_base.rs │ │ └── wtable.rs │ │ ├── supertype.rs │ │ └── visitor.rs ├── skc_language_server │ ├── Cargo.toml │ ├── README.md │ ├── package-lock.json │ └── src │ │ ├── backend.rs │ │ ├── main.rs │ │ └── server.rs ├── skc_mir │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── lib.rs │ │ ├── library.rs │ │ ├── vtable.rs │ │ └── vtables.rs └── skc_rustlib │ ├── Cargo.toml │ ├── README.md │ ├── provided_methods.json5 │ └── src │ ├── allocator.rs │ ├── builtin.rs │ ├── builtin │ ├── array.rs │ ├── bool.rs │ ├── class.rs │ ├── class │ │ └── witness_table.rs │ ├── error.rs │ ├── file.rs │ ├── float.rs │ ├── fn_x.rs │ ├── int.rs │ ├── math.rs │ ├── object.rs │ ├── random.rs │ ├── result.rs │ ├── shiika_internal_memory.rs │ ├── shiika_internal_ptr.rs │ ├── shiika_internal_ptr_typed.rs │ ├── string.rs │ ├── time.rs │ ├── time │ │ ├── rs_zone.rs │ │ ├── sk_instant.rs │ │ ├── sk_plain.rs │ │ ├── sk_time.rs │ │ └── sk_zone.rs │ └── void.rs │ ├── lib.rs │ ├── sk_cls.rs │ └── sk_methods.rs ├── packages └── core │ ├── ext │ ├── Cargo.toml │ ├── exports.json5 │ └── src │ │ ├── core_class.rs │ │ ├── core_class │ │ ├── int.rs │ │ └── object.rs │ │ ├── lib.rs │ │ ├── runtime.rs │ │ └── runtime │ │ └── allocator.rs │ ├── index.sk │ └── package.json5 ├── release_test.sh ├── setup.sh ├── shiika_logo_small.png ├── src ├── bin │ └── exp_shiika.rs ├── cli.rs ├── error.rs ├── lib.rs ├── loader.rs ├── main.rs ├── runner.rs └── targets.rs └── tests ├── README.md ├── erroneous.rs ├── erroneous ├── class_definition │ ├── no_class_name.sk │ └── unknown_superclass.sk ├── method_call │ ├── extra_argument_error.sk │ └── invalid_type.sk └── parsing │ ├── semicolon_after_binary_op.sk │ ├── semicolon_within_parentheses1.sk │ ├── semicolon_within_parentheses2.sk │ ├── semicolon_within_parentheses3.sk │ └── semicolon_within_parentheses4.sk ├── integration_test.rs ├── new_runtime ├── arg_ref.expected_out ├── arg_ref.sk ├── async.expected_out ├── async.sk ├── async_if.expected_out ├── async_if.sk ├── async_lvar.expected_out ├── async_lvar.sk ├── async_valued_if.expected_out ├── async_valued_if.sk ├── countdown.expected_out ├── countdown.sk ├── countdown2.expected_out ├── countdown2.sk ├── countdown3.expected_out ├── countdown3.sk ├── early_return.expected_out ├── early_return.sk ├── if.expected_out ├── if.sk ├── if_return_return.expected_out ├── if_return_return.sk ├── sync.expected_out ├── sync.sk ├── valued_if.expected_out ├── valued_if.sk ├── while_arg.expected_out ├── while_arg.sk ├── while_lvar.expected_out ├── while_lvar.sk ├── while_return.expected_out ├── while_return.sk ├── while_sync.expected_out ├── while_sync.sk ├── while_while.expected_out └── while_while.sk ├── sk ├── array.sk ├── block.sk ├── class_hierarchy.sk ├── class_instance_variable.sk ├── conditional_expr.sk ├── constants.sk ├── enum.sk ├── enumerable.sk ├── file.sk ├── float.sk ├── generic_method.sk ├── generics.sk ├── inheritance.sk ├── instantiation.sk ├── int.sk ├── ivar.sk ├── lambda.sk ├── logical_operator.sk ├── loops_and_jumps.sk ├── match_expression.sk ├── method_call.sk ├── multi_line_function_call.sk ├── nested_class.sk ├── new_line_starts_with_dot.sk ├── parse_unary_plus.sk ├── readable.sk ├── relational_operator.sk ├── result.sk ├── string.sk ├── time.sk ├── type_system.sk ├── virtual_method.sk ├── void.sk └── xxx-eq.sk ├── snapshots ├── erroneous__erroneous@class_definition__no_class_name.sk.snap ├── erroneous__erroneous@class_definition__unknown_superclass.sk.snap ├── erroneous__erroneous@method_call__extra_argument_error.sk.snap ├── erroneous__erroneous@method_call__invalid_type.sk.snap ├── erroneous__erroneous@parsing__semicolon_after_binary_op.sk.snap ├── erroneous__erroneous@parsing__semicolon_within_parentheses1.sk.snap ├── erroneous__erroneous@parsing__semicolon_within_parentheses2.sk.snap ├── erroneous__erroneous@parsing__semicolon_within_parentheses3.sk.snap └── erroneous__erroneous@parsing__semicolon_within_parentheses4.sk.snap └── tmp └── .keep /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'main' 7 | pull_request: 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | # Run unit tests 14 | build: 15 | strategy: 16 | matrix: 17 | os: [ubuntu-22.04, macos-13] 18 | runs-on: ${{ matrix.os }} 19 | steps: 20 | - uses: actions/checkout@v4 21 | - uses: actions/cache@v4 22 | with: 23 | path: | 24 | ~/.cargo/registry 25 | ~/.cargo/git 26 | target 27 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 28 | 29 | - name: Set up the Linux env 30 | if: runner.os == 'Linux' 31 | run: | 32 | set -eux 33 | sudo apt install -y libgc-dev 34 | 35 | - name: Cache LLVM and Clang on Linux 36 | id: cache-llvm 37 | if: runner.os == 'Linux' 38 | uses: actions/cache@v4 39 | with: 40 | path: | 41 | C:/Program Files/LLVM 42 | ./llvm 43 | key: llvm-16.0 44 | - name: Install LLVM and Clang on Linux 45 | if: runner.os == 'Linux' 46 | uses: KyleMayes/install-llvm-action@v1 47 | with: 48 | version: "16.0" 49 | cached: ${{ steps.cache-llvm.outputs.cache-hit }} 50 | - name: Set up LLVM and Clang on macOS 51 | if: runner.os == 'macOS' 52 | run: | 53 | brew install llvm@16 54 | echo "/usr/local/opt/llvm@16/bin" >> $GITHUB_PATH 55 | 56 | - name: Output versions 57 | run: | 58 | set -eux 59 | rustc --version 60 | cargo --version 61 | clang --version 62 | 63 | - name: Build and test 64 | run: | 65 | set -eux 66 | mkdir ~/.shiika 67 | export SHIIKA_WORK=~/.shiika 68 | export SHIIKA_ROOT=. 69 | export RUST_BACKTRACE=1 70 | env -- cargo run --bin exp_shiika -- build packages/core 71 | env -- rake async_integration_test 72 | 73 | cd lib/skc_rustlib; cargo build; cd ../../ 74 | env -- cargo run -- build-corelib 75 | env -- cargo test 76 | env -- bash release_test.sh 77 | 78 | # Run cargo fmt --all -- --check 79 | format: 80 | name: Format 81 | runs-on: ubuntu-latest 82 | steps: 83 | - name: Checkout sources 84 | uses: actions/checkout@v4 85 | - name: Run cargo fmt 86 | run: | 87 | cargo fmt --all -- --check 88 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.ll 3 | *.out 4 | *.bc 5 | *.s 6 | *.dSYM 7 | /builtin/exports.json 8 | /coverage 9 | /examples/*.actual.* 10 | /lib/*/Cargo.lock 11 | /lib/*/target 12 | /lib/shiika/parser.rb 13 | /lib/shiika/parser.ry 14 | /rusty-tags.vi 15 | /target 16 | /tests/tmp.* 17 | /tests/tmp/hello.txt 18 | /tests/new_runtime/*.out 19 | /tests/new_runtime/*.actual_out 20 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition = "2021" 3 | name = "shiika" 4 | version = "0.9.1" 5 | authors = [ "Yutaka HARA " ] 6 | default-run = "shiika" 7 | 8 | [workspace] 9 | members = [ 10 | "lib/*", "packages/core/ext", 11 | ] 12 | 13 | [features] 14 | mac = [] 15 | 16 | [dependencies] 17 | shiika_parser = { path = "lib/shiika_parser/" } 18 | shiika_ast = { path = "lib/shiika_ast/" } 19 | shiika_core = { path = "lib/shiika_core/" } 20 | skc_corelib = { path = "lib/skc_corelib/" } 21 | skc_ast2hir = { path = "lib/skc_ast2hir/" } 22 | skc_mir = { path = "lib/skc_mir/" } 23 | skc_codegen = { path = "lib/skc_codegen/" } 24 | skc_async_experiment = { path = "lib/skc_async_experiment/" } 25 | 26 | anyhow = "1.0" 27 | inkwell = { git = "https://github.com/TheDan64/inkwell", features = ["llvm16-0"], rev = "4030f76" } 28 | clap = { version = "3.1.18", features = ["derive"] } 29 | env_logger = "0.8.2" 30 | log = "0.4.11" 31 | serde = { version = "1.0.125", features = ["derive"] } 32 | serde_json = "1.0" 33 | os_info = "3.7.0" 34 | concolor = { version = "0.1.1", features = ["api"] } 35 | 36 | chrono = "0.4" 37 | chrono-tz = "0.8" 38 | 39 | ariadne = "0.3.0" 40 | 41 | [dev-dependencies] 42 | insta = { version = "1.32.0", features = ["glob"] } 43 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Use an official Ubuntu as a parent image 2 | FROM ubuntu:22.04 3 | 4 | # Configure the environment 5 | ENV DEBIAN_FRONTEND=noninteractive 6 | ENV PATH="/root/.cargo/bin:${PATH}" 7 | ENV LLC=llc-16 8 | ENV CLANG=clang-16 9 | 10 | # Install necessary packages 11 | RUN apt-get update && apt-get install -y \ 12 | git \ 13 | curl \ 14 | build-essential \ 15 | cmake \ 16 | libgc-dev \ 17 | wget \ 18 | gnupg \ 19 | software-properties-common \ 20 | zlib1g-dev \ 21 | libzstd-dev \ 22 | && rm -rf /var/lib/apt/lists/* 23 | 24 | # Add the LLVM 16 repository and install LLVM 16 25 | RUN wget https://apt.llvm.org/llvm.sh && chmod +x llvm.sh && ./llvm.sh 16 26 | 27 | # Install Polly and other necessary LLVM packages 28 | RUN apt-get update && apt-get install -y \ 29 | libpolly-16-dev \ 30 | llvm-16-dev \ 31 | libllvm16 \ 32 | liblld-16-dev \ 33 | libclang-16-dev \ 34 | && rm -rf /var/lib/apt/lists/* 35 | 36 | # Install Rust 37 | RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y 38 | 39 | # Set the working directory 40 | WORKDIR /app 41 | 42 | # Set the default command 43 | CMD ["/bin/bash"] 44 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Yutaka Hara 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /builtin/bool.sk: -------------------------------------------------------------------------------- 1 | class Bool 2 | def ==(other: Bool) -> Bool 3 | (self and other) or (not self and not other) 4 | end 5 | 6 | def inspect -> String 7 | if self 8 | "true" 9 | else 10 | "false" 11 | end 12 | end 13 | 14 | def to_s -> String 15 | inspect 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /builtin/class.sk: -------------------------------------------------------------------------------- 1 | class Class 2 | def name -> String 3 | # Accessors does not defined automatically for classes defined skc_corelib 4 | @name 5 | end 6 | 7 | def inspect -> String 8 | "#" 9 | end 10 | 11 | def to_s -> String 12 | name 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /builtin/error.sk: -------------------------------------------------------------------------------- 1 | # Represents an error 2 | class Error 3 | def initialize(@msg: String); end 4 | 5 | # def backtrace 6 | 7 | def to_s -> String 8 | "#" 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /builtin/file.sk: -------------------------------------------------------------------------------- 1 | require "./readable.sk" 2 | 3 | class File : Readable 4 | # def self.read(path: String) -> Result 5 | 6 | # def self.write(path: String, content: String) -> Result 7 | 8 | def self.open(path: String, f: Fn1) -> Result 9 | let file = File._open(path).try! 10 | let v = f(file) 11 | #file.close 12 | Ok.new(v) 13 | end 14 | 15 | # TODO: will we need this? 16 | # def close 17 | 18 | def fill_buf -> Result 19 | _fill_buf 20 | end 21 | 22 | def consume(n_bytes: Int) 23 | _consume(n_bytes) 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /builtin/float.sk: -------------------------------------------------------------------------------- 1 | class Float 2 | def %(other: Int) -> Float 3 | self - other.to_f * (self / other.to_f).floor 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /builtin/fn.sk: -------------------------------------------------------------------------------- 1 | class Fn 2 | EXIT_NORMAL = 0 3 | EXIT_BREAK = 1 4 | 5 | def initialize( 6 | @func: Shiika::Internal::Ptr, 7 | @the_self: Object, 8 | @captures: Shiika::Internal::Ptr, 9 | ) 10 | let @exit_status = EXIT_NORMAL 11 | end 12 | end 13 | 14 | class Fn0 : Fn 15 | end 16 | 17 | class Fn1 : Fn 18 | end 19 | 20 | class Fn2 : Fn 21 | end 22 | 23 | class Fn3 : Fn 24 | end 25 | 26 | class Fn4 : Fn 27 | end 28 | 29 | class Fn5 : Fn 30 | end 31 | 32 | class Fn6 : Fn 33 | end 34 | 35 | class Fn7 : Fn 36 | end 37 | 38 | class Fn8 : Fn 39 | end 40 | 41 | class Fn9 : Fn 42 | end 43 | -------------------------------------------------------------------------------- /builtin/index.sk: -------------------------------------------------------------------------------- 1 | require "./object.sk" 2 | 3 | require "./array.sk" 4 | require "./bool.sk" 5 | require "./class.sk" 6 | require "./dict.sk" 7 | require "./enumerable.sk" 8 | require "./error.sk" 9 | require "./file.sk" 10 | require "./float.sk" 11 | require "./fn.sk" 12 | require "./int.sk" 13 | require "./math.sk" 14 | require "./maybe.sk" 15 | require "./metaclass.sk" 16 | require "./mutable_string.sk" 17 | require "./never.sk" 18 | require "./pair.sk" 19 | require "./random.sk" 20 | require "./result.sk" 21 | require "./shiika_internal.sk" 22 | require "./string.sk" 23 | require "./time.sk" 24 | require "./triple.sk" 25 | require "./void.sk" 26 | -------------------------------------------------------------------------------- /builtin/int.sk: -------------------------------------------------------------------------------- 1 | class Int 2 | # Returns the absolute value of `self`. 3 | def abs -> Int 4 | if self >= 0 5 | self 6 | else 7 | -self 8 | end 9 | end 10 | 11 | # Calls `f` by passing numbers from `self` to `n`. 12 | def downto(n: Int, f: Fn1) 13 | var i = self; while i >= n 14 | f(i) 15 | i -= 1 16 | end 17 | end 18 | 19 | # Returns true if `self` is even. 20 | def even? -> Bool 21 | self % 2 == 0 22 | end 23 | 24 | # Returns true if `self` is odd. 25 | def odd? -> Bool 26 | self % 2 != 0 27 | end 28 | 29 | # Return the hash value of `self`. 30 | def hash -> Int 31 | self 32 | end 33 | 34 | # Calls `f` for `self` times, passing numbers from zero to `self - 1`. 35 | def times(f: Fn1) 36 | var i = 0; while i < self 37 | f(i) 38 | i += 1 39 | end 40 | end 41 | 42 | # Returns `self`. 43 | def to_i -> Int 44 | self 45 | end 46 | 47 | # Returns string representation of `self`. 48 | def to_s -> String 49 | var minus = false 50 | var n = self 51 | if self < 0 52 | minus = true 53 | n = -self 54 | end 55 | 56 | let a = Array.new 57 | while true 58 | a.push((n % 10) + 48) # 48 = "0" 59 | n = (n / 10).to_i 60 | if n == 0 61 | break 62 | end 63 | end 64 | 65 | let ret = MutableString.new 66 | var i = 0 67 | if minus 68 | ret.append("-") 69 | i = 1 70 | end 71 | a.reverse_each do |b| 72 | ret.write_byte(i, b) 73 | i += 1 74 | end 75 | ret._unsafe_to_s 76 | end 77 | 78 | # Calls `f` by passing numbers from `self` to `n`. 79 | def upto(n: Int, f: Fn1) 80 | var i = self; while i <= n 81 | f(i) 82 | i += 1 83 | end 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /builtin/math.sk: -------------------------------------------------------------------------------- 1 | module Math 2 | end 3 | -------------------------------------------------------------------------------- /builtin/maybe.sk: -------------------------------------------------------------------------------- 1 | enum Maybe 2 | case Some(value: V) 3 | case None 4 | 5 | # Apply `f` to the value, if any. Returns `None` if not. 6 | def map(f: Fn1) -> Maybe 7 | match self 8 | when Some(v) 9 | Some.new(f(v)) 10 | else 11 | None 12 | end 13 | end 14 | 15 | # Returns the value. Panic with `msg` if none. 16 | def expect(msg: String) -> V 17 | match self 18 | when Some(v) 19 | v 20 | else 21 | panic msg 22 | end 23 | end 24 | 25 | def inspect -> String 26 | match self 27 | when Some(v) 28 | "#" 29 | else 30 | "#" 31 | end 32 | end 33 | 34 | def none? -> Bool 35 | match self 36 | when Some(_) then false 37 | else true 38 | end 39 | end 40 | 41 | def some? -> Bool 42 | match self 43 | when Some(_) then true 44 | else false 45 | end 46 | end 47 | end 48 | Some = Maybe::Some 49 | None = Maybe::None 50 | -------------------------------------------------------------------------------- /builtin/metaclass.sk: -------------------------------------------------------------------------------- 1 | # Class of metaclasses 2 | # see also: src/corelib/ 3 | class Metaclass : Class 4 | def name -> String 5 | if @name == "Metaclass" 6 | "Metaclass" 7 | else 8 | "Meta:" + @name 9 | end 10 | end 11 | 12 | def inspect -> String 13 | "#" 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /builtin/never.sk: -------------------------------------------------------------------------------- 1 | class Never 2 | end 3 | -------------------------------------------------------------------------------- /builtin/object.sk: -------------------------------------------------------------------------------- 1 | class Object 2 | def initialize 3 | end 4 | 5 | # TODO: These will be removed when `Comparable` is implemented 6 | def <(other: Object) -> Bool 7 | panic "[`<` is not implemented for class #{self.class}]" 8 | false 9 | end 10 | def >(other: Object) -> Bool 11 | other < self 12 | end 13 | def <=(other: Object) -> Bool 14 | self < other or self == other 15 | end 16 | def >=(other: Object) -> Bool 17 | self > other or self == other 18 | end 19 | 20 | # Returns the class which this object belongs to. 21 | # def class -> Class 22 | 23 | # Terminates the process. 24 | # Note: this may be moved to `Process.exit` or somewhere in the future 25 | # def exit -> Never 26 | 27 | # Reads a line form stdin. 28 | # def gets -> Result 29 | 30 | def hash -> Int 31 | 0 # TODO: Use the pointer address 32 | end 33 | 34 | def inspect -> String 35 | self.to_s 36 | end 37 | 38 | # Returns the unique id of this object. 39 | # def object_id -> Int 40 | 41 | def loop(f: Fn0) 42 | while true 43 | f() 44 | end 45 | end 46 | 47 | def p(obj: Object) 48 | puts obj.inspect 49 | end 50 | 51 | # Forcefully terminates the process with given error message. 52 | # panic(msg: String) -> Never 53 | 54 | # Prints message to stdout. 55 | # print(s: String) 56 | 57 | # Prints message and a newline to stdout. 58 | # puts(s: String) 59 | 60 | # Stop execution for a while. 61 | # sleep(sec: Float) 62 | 63 | def to_s -> String 64 | "#<#{self.class.name}:#{self.object_id}>" 65 | end 66 | 67 | # Force the compiler to treat this object is an instance of `cls`. 68 | # Usually you should not use this method unless to avoid compiler's bug, etc. 69 | def unsafe_cast(cls: Class) -> Object 70 | # The type checker has special handling rule for this method 71 | # (grep with "unsafe_cast".) 72 | self 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /builtin/pair.sk: -------------------------------------------------------------------------------- 1 | class Pair 2 | def initialize(@fst: A, @snd: B); end 3 | def inspect -> String 4 | "Pair(#{@fst.inspect}, #{@snd.inspect})" 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /builtin/random.sk: -------------------------------------------------------------------------------- 1 | class Random 2 | def self.initialize 3 | # Cannot do this yet (#455) 4 | # let @rand = Random._without_seed 5 | end 6 | 7 | # Create an instance of `Random` without explicit seed. 8 | # def self._without_seed 9 | 10 | def self.int(from: Int, to: Int) -> Int 11 | Random._without_seed.int(from, to) 12 | end 13 | 14 | def self.float -> Float 15 | Random._without_seed.float 16 | end 17 | 18 | def initialize(seed: Int) 19 | _initialize_rustlib(seed) 20 | end 21 | 22 | # Returns a random integer (end-exclusive). 23 | # def int(from: Int, to: Int) -> Int 24 | 25 | # Returns a random float between 0.0 and 1.0 (end-exclusive). 26 | # def float -> Float 27 | end 28 | -------------------------------------------------------------------------------- /builtin/readable.sk: -------------------------------------------------------------------------------- 1 | require "./result.sk" 2 | 3 | module Readable 4 | requirement fill_buf -> Result # TODO: Result 5 | requirement consume(n_bytes: Int) 6 | 7 | def read_line -> Result> 8 | let l = MutableString.new 9 | var done = false 10 | while true 11 | let s = fill_buf.try! 12 | if s.bytesize == 0 13 | done = true 14 | else 15 | s.each_byte do |b| 16 | if b == 10 # LF(\n) 17 | consume(1) 18 | done = true 19 | break 20 | end 21 | l.append_byte(b) 22 | consume(1) 23 | end 24 | end 25 | break if done 26 | end 27 | Ok.new( 28 | if l.empty? 29 | None 30 | else 31 | Some.new(l._unsafe_to_s) 32 | end 33 | ) 34 | end 35 | 36 | def lines -> Result> 37 | let a = Array.new 38 | while true 39 | match read_line 40 | when Ok(Some(s)) 41 | a.push(s) 42 | when Ok(None) 43 | break 44 | when Fail(e) 45 | return Fail.new(e) 46 | end 47 | end 48 | Ok.new(a) 49 | end 50 | 51 | def read -> Result 52 | let acc = MutableString.new 53 | while true 54 | let s = fill_buf.try! 55 | if s.bytesize == 0 56 | break 57 | else 58 | acc.append(s) 59 | consume(s.bytesize) 60 | end 61 | end 62 | Ok.new(acc._unsafe_to_s) 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /builtin/result.sk: -------------------------------------------------------------------------------- 1 | enum Result 2 | case Ok(value: V) 3 | case Fail(err: Error) 4 | 5 | def self.fail(msg: String) -> Fail 6 | Fail.new(Error.new(msg)) 7 | end 8 | 9 | def fail? -> Bool 10 | match self 11 | when Fail(_) then true 12 | else false 13 | end 14 | end 15 | 16 | def ok? -> Bool 17 | match self 18 | when Ok(_) then true 19 | else false 20 | end 21 | end 22 | 23 | # Special method which returns the `value` if this is `Ok` and otherwise escapes the 24 | # current method like `return self`. 25 | def try! # -> V or Never 26 | # Call to this method is replaced with a pattern match in 27 | # skc_ast2hir. 28 | end 29 | 30 | def unwrap -> V 31 | match self 32 | when Ok(v) then v 33 | when Fail(e) then panic(e.msg) 34 | end 35 | end 36 | 37 | def inspect -> String 38 | match self 39 | when Ok(v) then "#" 40 | when Fail(e) then "#" 41 | end 42 | end 43 | end 44 | Ok = Result::Ok 45 | Fail = Result::Fail 46 | -------------------------------------------------------------------------------- /builtin/shiika_internal.sk: -------------------------------------------------------------------------------- 1 | module Shiika 2 | module Internal 3 | class Memory 4 | # Run GC manually. 5 | # def self.force_gc 6 | 7 | # def self.memcpy(dst: Shiika::Internal::Ptr, src: Shiika::Internal::Ptr, n_bytes: Int) -> Void 8 | 9 | # def self.gc_malloc(n_bytes: Int) -> Shiika::Internal::Ptr 10 | 11 | # def self.gc_realloc(ptr: Shiika::Internal::Ptr, n_bytes: Int) -> Shiika::Internal::Ptr 12 | end 13 | 14 | class Ptr 15 | # Show memory dump beginning from the address of `obj` and continues for 16 | # `len`*16 bytes. For debugging memory-related bugs. 17 | # def self.p(obj: Object, len: Int) 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /builtin/time.sk: -------------------------------------------------------------------------------- 1 | class Time 2 | def self.local -> Time 3 | new(Instant.now, Zone::Local) 4 | end 5 | 6 | def self.utc -> Time 7 | new(Instant.now, Zone::Utc) 8 | end 9 | 10 | def initialize(@instant: Instant, @zone: Zone); end 11 | 12 | def inspect -> String 13 | let t = self.to_plain 14 | "#" 15 | end 16 | 17 | # Convert `self` to PlainDateTime. 18 | #def to_plain -> PlainDateTime 19 | 20 | class Date 21 | def initialize(@plain_date: PlainDate, @zone: Zone) 22 | end 23 | 24 | # TODO: Add Date.local, Date.utc, etc. 25 | end 26 | 27 | # Represents time duration with nanosecond precision. 28 | class Duration 29 | def initialize(@sec: Int, @nano_frac: Int) 30 | end 31 | 32 | # TODO: Add some apis which returns this 33 | end 34 | 35 | enum Zone 36 | case Utc 37 | case Local 38 | # TODO: Support explicit timezone 39 | #case Iana(inner: Object) 40 | end 41 | 42 | # Represents absolute time from the unix epoch. 43 | class Instant 44 | # Returns current time. 45 | #def self.now -> Instant 46 | 47 | def initialize(@nano_timestamp: Int) 48 | end 49 | 50 | def inspect -> String 51 | "#" 52 | end 53 | end 54 | 55 | class PlainDate 56 | def initialize(@year: Int, @month: Int, @day: Int) 57 | end 58 | end 59 | 60 | class PlainTime 61 | def initialize(@hour: Int, @minute: Int, @second: Int, @nano_frac: Int) 62 | end 63 | end 64 | 65 | class PlainDateTime 66 | def initialize(@plain_date: PlainDate, @plain_time: PlainTime) 67 | end 68 | 69 | def year -> Int; @plain_date.year; end 70 | def month -> Int; @plain_date.month; end 71 | def day -> Int; @plain_date.day; end 72 | def hour -> Int; @plain_time.hour; end 73 | def minute -> Int; @plain_time.minute; end 74 | def second -> Int; @plain_time.second; end 75 | def nano_frac -> Int; @plain_time.nano_frac; end 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /builtin/triple.sk: -------------------------------------------------------------------------------- 1 | class Triple 2 | def initialize(@fst: A, @snd: B, @thd: C); end 3 | end 4 | -------------------------------------------------------------------------------- /builtin/void.sk: -------------------------------------------------------------------------------- 1 | class Void 2 | def to_s -> String 3 | "Void" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /doc/.gitignore: -------------------------------------------------------------------------------- 1 | */html 2 | -------------------------------------------------------------------------------- /doc/guide/.gitignore: -------------------------------------------------------------------------------- 1 | book 2 | -------------------------------------------------------------------------------- /doc/guide/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["Yutaka HARA"] 3 | language = "en" 4 | multilingual = false 5 | src = "src" 6 | title = "Shiika Language Guide" 7 | 8 | [build] 9 | build-dir = "html" 10 | -------------------------------------------------------------------------------- /doc/guide/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Shiika Programming Guide 2 | 3 | - [Install](./install.md) 4 | - Overview 5 | - Getting Started 6 | - Basics 7 | - [Basic Types](./basic_types.md) 8 | - [Classes and Objects](./classes.md) 9 | - [Enums](./enums.md) 10 | - [Expressions](./expressions.md) 11 | - [Difference from Ruby](./ruby.md) 12 | -------------------------------------------------------------------------------- /doc/guide/src/basic_types.md: -------------------------------------------------------------------------------- 1 | # Basic classes 2 | 3 | ## Bool 4 | 5 | ``` 6 | true 7 | false 8 | ``` 9 | 10 | ## Int 11 | 12 | ``` 13 | 12 + 34 14 | ``` 15 | 16 | ## Float 17 | 18 | ``` 19 | 1.2 + 3.4 20 | ``` 21 | 22 | ## String 23 | 24 | ``` 25 | puts "Hello, world!" 26 | ``` 27 | 28 | ## Array 29 | 30 | ``` 31 | let a = [1, 2, 3] 32 | 33 | let b = Array.new 34 | b.push(0) 35 | ``` 36 | 37 | ## Dict 38 | 39 | ``` 40 | let d = Dict.new 41 | d["a"] = 1 42 | d["b"] = 2 43 | 44 | p d["a"] #=> 1 45 | ``` 46 | 47 | (There is no literals for dictionaries. Do you want?) 48 | 49 | ## Maybe 50 | 51 | ``` 52 | let a = Some.new(1) 53 | let b = None 54 | 55 | match a 56 | when Some(n) 57 | p n 58 | when None 59 | p "none." 60 | end 61 | ``` 62 | 63 | ## Result 64 | 65 | `Result` is defined as follows and used by classes like `File`. 66 | 67 | ``` 68 | enum Result 69 | case Ok(value: V) 70 | case Fail(err: Error) 71 | ... 72 | ``` 73 | 74 | ## Error 75 | 76 | `Error` is defined as follows. 77 | 78 | ``` 79 | class Error 80 | def initialize(@msg: String); end 81 | ... 82 | 83 | # TODO: def backtrace -> Array 84 | ``` 85 | 86 | ## And more 87 | 88 | Please refer to `./builtin/*.sk` for other built-in types. 89 | -------------------------------------------------------------------------------- /doc/guide/src/develop-in-docker-container.md: -------------------------------------------------------------------------------- 1 | # Develop in Docker Container 2 | 3 | You can develop Shiika in a Docker container without configuring your local environment. 4 | 5 | ## Install Docker Compose command 6 | 7 | At first, you need to install Docker Compose. 8 | The recommended way is to install Docker Desktop from the official site: https://docs.docker.com/compose/install/ 9 | 10 | Note: `docker-compose` command is included in Docker Desktop. 11 | 12 | ## build and run the container 13 | 14 | Then, you can follow the steps below: 15 | 16 | ``` 17 | docker compose up -d 18 | docker compose exec dev bash 19 | ``` 20 | 21 | After running the above commands, you will be in the container. 22 | 23 | `docker compose` command may be not found on your machine. Then you can use `docker-compose` command instead. 24 | ``` 25 | docker-compose up -d 26 | docker-compose exec dev bash 27 | ``` 28 | 29 | ## Run setup script 30 | 31 | After that, run the setup script: 32 | ``` 33 | chmod +x ./setup.sh 34 | ./setup.sh 35 | ``` 36 | 37 | This script installs the necessary tools and libraries. 38 | 39 | You've done the setup now. 40 | -------------------------------------------------------------------------------- /doc/guide/src/enums.md: -------------------------------------------------------------------------------- 1 | # Enums 2 | 3 | ## Enum definition 4 | 5 | Example: 6 | 7 | ``` 8 | enum Tree 9 | case Node(left: Tree, right: Tree) 10 | case Leaf(value: V) 11 | 12 | def dump 13 | match self 14 | when Node(l, r) 15 | print "(" 16 | l.dump 17 | print ", " 18 | r.dump 19 | print ")" 20 | when Leaf(v) 21 | print v.inspect 22 | end 23 | end 24 | end 25 | 26 | # Currently you need to write `` but this can be omitted in the future. 27 | tree = Tree::Node.new( 28 | Tree::Node.new( 29 | Tree::Leaf.new(1), 30 | Tree::Leaf.new(2) 31 | ), 32 | Tree::Leaf.new(3) 33 | ) 34 | tree.dump 35 | puts "" 36 | ``` 37 | -------------------------------------------------------------------------------- /doc/guide/src/ruby.md: -------------------------------------------------------------------------------- 1 | # Difference from Ruby 2 | 3 | TBA 4 | 5 | ## Syntax 6 | 7 | - No `&&` `||`, only `and` `or` (whose precedence are the same as Ruby's `&&` `||`) 8 | 9 | ## Built-in 10 | 11 | - No `Symbol`s 12 | - `Hash` is renamed to `Dict` 13 | -------------------------------------------------------------------------------- /doc/guide/src/setup_windows.md: -------------------------------------------------------------------------------- 1 | # Use Shiika on Windows 2 | 3 | The easiest way to try Shiika on Windows is to use WSL2. 4 | 5 | Using Shiika without WSL2 is not easy, but possible. This document describes how. 6 | 7 | ## Prerequisites 8 | 9 | - 64bit Windows 10 | - Rust 11 | - https://www.rust-lang.org/tools/install 12 | - Visual Studio >= 2019 (Tested with Professional but Community edition should suffice too) 13 | - CMake (to build LLVM and bdwgc-alloc) 14 | - Download from https://cmake.org/download/ and install 15 | - Python3 (to build LLVM) 16 | - https://www.python.org/downloads/windows/ 17 | 18 | ## Build LLVM 19 | 20 | First, you need to build LLVM because it seems that 21 | 22 | - the `llvm-sys` crate relies on `llvm-config.exe` (no?) and 23 | - the llvm release package does not contain llvm-config.exe. 24 | 25 | Steps to build your own llvm: 26 | 27 | - Install Python3 28 | - Get LLVM source 29 | - https://github.com/llvm/llvm-project/releases/tag/llvmorg-12.0.1 llvm-project-12.0.1.src.tar.xz 30 | - You may need 7zip to unpack .xz 31 | - Generate llvm.sln with cmake-gui 32 | - Open cmake-gui 33 | - Press `Browse Source...` and select `somewhere/llvmorg-12.0.1/llvm` 34 | - Press `Browse Build...` and select `somewhere/llvmorg-12.0.1/build` (or anywhere you like) 35 | - Press `Configure` 36 | - Put `host=x64` to `Optional toolset to use (argument to -T)` 37 | - Press `Finish` and wait 38 | - Set `clang;lld` to `LLVM_ENABLE_PROJECTS` (TODO: not needed?) 39 | - Press `Generate` and wait 40 | - Build LLVM with VS 41 | - Open build/llvm.sln with Visual Studio 42 | - Choose `ALL_BUILD` under `CMakePredefinedTargets` in solution explorer 43 | - Choose `Debug` `x64` and build (`llvm-config.exe` will not be created with `Release`) 44 | 45 | ## Build shiika.exe 46 | 47 | Make sure these are in the PATH. 48 | 49 | ``` 50 | > clang -v 51 | clang version 12.0.1 52 | ... 53 | > cmake --version 54 | cmake version 3.42.2 55 | ... 56 | > llvm-config --version 57 | 12.0.1 58 | ``` 59 | 60 | Then try `cargo build` and see if succeeds. 61 | 62 | ``` 63 | > git clone https://github.com/shiika-lang/shiika 64 | > cd shiika 65 | > set LLVM_SYS_120_PREFIX=c:\somewhere\llvm-project-12.0.1\build\Debug\bin 66 | > cargo build 67 | ``` -------------------------------------------------------------------------------- /doc/new_runtime.md: -------------------------------------------------------------------------------- 1 | # New runtime for concurrency 2 | 3 | Shiika will have concurrent feature with this new runtime using tokio. 4 | 5 | Tracking issue: https://github.com/shiika-lang/shiika/issues/545 6 | 7 | ## Files 8 | 9 | - src/bin/exp_shiika.rs 10 | - lib/skc_async_experiment/ 11 | - packages/core 12 | 13 | ## How to try 14 | 15 | 1. `cargo run --bin exp_shiika -- build packages/core` 16 | 1. `cargo run --bin exp_shiika -- a.sk"` 17 | 18 | ## Status 19 | 20 | Currently the syntax is the same as Shiika (as using lib/shiika_parser) 21 | but some features are not implemented yet. Please check the github issue above. 22 | -------------------------------------------------------------------------------- /doc/shg/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["Yutaka HARA"] 3 | language = "en" 4 | multilingual = false 5 | src = "src" 6 | title = "Shiika Hacking Guide" 7 | 8 | [build] 9 | build-dir = "html" 10 | -------------------------------------------------------------------------------- /doc/shg/src/README.md: -------------------------------------------------------------------------------- 1 | # Shiika Hacking Guide 2 | 3 | This docucment describes the internals of the Shiika compiler. 4 | -------------------------------------------------------------------------------- /doc/shg/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Shiika Hacking Guide 2 | 3 | * [Introduction](README.md) 4 | * Source code 5 | * [Parser](parser.md) 6 | * [AST](ast.md) 7 | * [HIR](hir.md) 8 | * [CodeGen](code_gen.md) 9 | * [Corelib](corelib.md) 10 | * [Tests](tests.md) 11 | * Design Notes 12 | * [Enum](design_notes/enum.md) 13 | * Other 14 | * [Performance](perf.md) 15 | * [Debugging](debug.md) 16 | -------------------------------------------------------------------------------- /doc/shg/src/ast.md: -------------------------------------------------------------------------------- 1 | # AST 2 | 3 | File: `lib/shiika_ast` 4 | 5 | AST (Abstract Syntax Tree) is generated by the parser. 6 | -------------------------------------------------------------------------------- /doc/shg/src/corelib.md: -------------------------------------------------------------------------------- 1 | # Corelib 2 | 3 | Directory: `lib/skc_corelib`, `lib/skc_rustlib`, `builtin` 4 | 5 | ## corelib 6 | 7 | `skc_corelib` defines the core classes like `Object`, `Bool`, `Int` together with its methods. 8 | 9 | `skc_rustlib` also defines core methods but written Rust. 10 | 11 | `builtin/*.sk` are Shiika code to define core library. 12 | 13 | ### Compilation 14 | 15 | `builtin/*.sk` and `skc_corelib` are compiled into `builtin/builtin.bc` by `shiika build_corelib`. 16 | 17 | `skc_rustlib` is compiled by running `cargo build` (as usual). 18 | 19 | `shiika run` executes `clang` to link these with user program. 20 | -------------------------------------------------------------------------------- /doc/shg/src/debug.md: -------------------------------------------------------------------------------- 1 | # Debugging 2 | 3 | Some hints to debug the Shiika compiler. 4 | 5 | ## Debug parser 6 | 7 | Uncomment this in src/parser/base.rs 8 | 9 | ```rust 10 | /// Print parser debug log (uncomment to enable) 11 | pub(super) fn debug_log(&self, _msg: &str) { 12 | //println!("{}{} {}", self.lv_space(), _msg, self.lexer.debug_info()); 13 | } 14 | ``` 15 | -------------------------------------------------------------------------------- /doc/shg/src/design_notes/enum.md: -------------------------------------------------------------------------------- 1 | # Enum (Tentative) 2 | 3 | ## Basic example 4 | 5 | ``` 6 | def foo -> Option 7 | if bar 8 | None 9 | else 10 | Some.new(123) 11 | end 12 | end 13 | ``` 14 | 15 | ``` 16 | class MyLib 17 | # This exports four constants Expr, Nil, Value and Cons. 18 | enum Expr 19 | case Nil 20 | case Value(v: T) 21 | case Cons(car: Expr, cdr: Expr) 22 | end 23 | 24 | def foo -> Expr 25 | if bar 26 | Nil 27 | elsif baz 28 | Value.new(99) 29 | else 30 | Cons.new(Nil, Nil) 31 | end 32 | end 33 | end 34 | ``` 35 | 36 | ## Enum cases 37 | 38 | ``` 39 | enum E 40 | # Defines a class E::A and its instance ::E::A 41 | case A 42 | # Defines a class E::B 43 | case B(x: Int) 44 | end 45 | 46 | A #=> ::E::A 47 | B #=> ::E::B 48 | ``` 49 | 50 | ## Why .new is needed 51 | 52 | If this is allowed, there should be a method `MyLib#Left`, that is very wierd. 53 | 54 | ``` 55 | class MyLib 56 | enum Either 57 | case Left(E) 58 | case Right(V) 59 | end 60 | 61 | def foo -> Either 62 | Left(1) # ??? 63 | end 64 | end 65 | ``` 66 | -------------------------------------------------------------------------------- /doc/shg/src/hir.md: -------------------------------------------------------------------------------- 1 | # HIR 2 | 3 | Directory: `lib/skc_hir` 4 | 5 | AST is converted into HIR (High-level Intermediate Representation) and then turns into LLVM IR (Low-level IR of Shiika). 6 | 7 | The structure of HIR resembles to AST, but most important difference is that HIR has type information. 8 | 9 | ## HirMaker 10 | 11 | File: `skc_ast2hir` 12 | 13 | This crate converts AST into HIR. 14 | 15 | ## Lambda 16 | 17 | Instances of `Fn` are called "lambda" in Shiika. There are two ways to create a lambda: 18 | 19 | 1. `fn(){ ...}` (called "fn") 20 | 2. `do ... end`, `{ ... }` (called "block") 21 | 22 | A lambda can capture outer variables. 23 | 24 | ``` 25 | var a = 1 26 | f = fn(){ p a } 27 | a = 2 28 | f() #=> prints `2` 29 | ``` 30 | 31 | `HirLambdaExpr` contains `captures`, a list of `HirLambdaCapture`. To make this: 32 | 33 | 1. When referring/updating a local variable defined in outer scope, save a `LambdaCapture` to `captures` of `LambdaCtx`. 34 | 2. Once all exprs in a lambda are processed, convert each `LambdaCapture` to `HirLambdaCapture`. 35 | -------------------------------------------------------------------------------- /doc/shg/src/parser.md: -------------------------------------------------------------------------------- 1 | # Parser 2 | 3 | Directory: `lib/shiika_parser/` 4 | 5 | ## Overview 6 | 7 | - Hand-written parser (i.e. no parser generator) 8 | - Stateful lexer (to allow omission of method call parentheses) 9 | 10 | ## Lexer 11 | 12 | File: `lexer.rs` 13 | 14 | Lexer has a state (`LexerState`). Main purpose of this is to decide operators like `-` or `+` is whether unary or binary. 15 | 16 | ``` 17 | /// - `p(-x)` # unary minus ExprBegin 18 | /// - `p(- x)` # unary minus ExprBegin 19 | /// - `p( - x)`# unary minus ExprBegin 20 | /// - `p- x` # binary minus (unusual) ExprEnd 21 | /// - `p-x` # binary minus ExprEnd 22 | /// - `p - x` # binary minus ExprArg 23 | /// - `p -x` # unary minus ExprArg 24 | /// - `1 -2` # binary minus (unusual) ExprArg 25 | ``` 26 | 27 | This state is set by the parser. 28 | -------------------------------------------------------------------------------- /doc/shg/src/perf.md: -------------------------------------------------------------------------------- 1 | # Possible performance improvements 2 | 3 | ## Unbox Int/Float/Bool 4 | 5 | Currently Int, Float, Bool has their own llvm struct type. 6 | This may lead to performance penalty (eg. to add to Int's, you need to unbox them, add them and box the result again), but this makes it easier to: 7 | 8 | - implement containers. 9 | - If all shiika values are represented by a pointer (i.e. compatible to `%Object*` via bitcast), heterogenius arrays like `[1, 2, "foo"]` can be built in the same way as building homogenius arrays. 10 | - implement lambda captures. 11 | - Captured variables need to be boxed. 12 | 13 | ## Inline non-capturing blocks 14 | 15 | Looping with `each` or `times` are slower than `while` because they involve calling lambdas. However, if the block does not capture any outer variables, it can be inlined to be as fast as `while`. 16 | -------------------------------------------------------------------------------- /doc/shg/src/tests.md: -------------------------------------------------------------------------------- 1 | # Tests 2 | 3 | Directory: `tests/` 4 | 5 | ## Unit tests 6 | 7 | File: `tests/*_test.rs` 8 | 9 | ## Integration tests 10 | 11 | File: `tests/integration_test.rs`, `tests/sk/*.sk` 12 | 13 | These are Shiika-level tests. If the test passes, it should print just `ok`; otherwise, it prints message like `ng foo`. 14 | 15 | You can select which .sk to run by `FILTER=` envvar. 16 | 17 | ``` 18 | # Run tests/sk/*block*.sk 19 | $ FILTER=block cargo test --test integration_test -- --nocapture 20 | ``` 21 | 22 | With `--nocapture`, path of the .sk file is printed. 23 | 24 | ## Doc tests 25 | 26 | Some of `src/*.rs` has doc tests. 27 | -------------------------------------------------------------------------------- /doc/spec/.gitignore: -------------------------------------------------------------------------------- 1 | book 2 | -------------------------------------------------------------------------------- /doc/spec/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["Yutaka HARA"] 3 | language = "en" 4 | multilingual = false 5 | src = "src" 6 | title = "Shiika Language Specification" 7 | -------------------------------------------------------------------------------- /doc/spec/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Shiika Language Specification 2 | 3 | - Syntax 4 | - [Basic Concepts](./basic_concepts.md) 5 | - [Types](./types.md) 6 | - [Classes](./classes.md) 7 | - [Enums](./enums.md) 8 | - [Expressions](./expressions.md) 9 | -------------------------------------------------------------------------------- /doc/spec/src/basic_concepts.md: -------------------------------------------------------------------------------- 1 | # Basic Concepts 2 | 3 | In Shiika, every value is an _object_ and belongs to a _class_. 4 | -------------------------------------------------------------------------------- /doc/spec/src/classes.md: -------------------------------------------------------------------------------- 1 | # Classes 2 | 3 | ## Class definition 4 | 5 | Example 6 | 7 | ```sk 8 | class A 9 | # A class method 10 | def self.foo -> Int 11 | 1 12 | end 13 | 14 | # An instance method 15 | def bar -> Int 16 | 2 17 | end 18 | end 19 | 20 | p A.foo #=> 1 21 | p A.new.bar #=> 2 22 | ``` 23 | 24 | ## Instance variables 25 | 26 | Name of an instance variable starts with `@`. All instance variables of a class must be initialized in the method `initialize`. 27 | 28 | Example 29 | 30 | ```sk 31 | class Book 32 | def initialize(title: String, price: Int) 33 | @title = title 34 | @price = price 35 | end 36 | end 37 | ``` 38 | 39 | Syntax sugar: 40 | 41 | ```sk 42 | class Book 43 | def initialize(@title: String, @price: Int); end 44 | end 45 | ``` 46 | 47 | Instance variables are readonly by default. To make it reassignable, declare it with `var`. 48 | 49 | ## Accessors 50 | 51 | For each instance variable, accessor methods are automatically defined unless they are defined explicitly. 52 | 53 | Example 54 | 55 | ```sk 56 | class Person 57 | def initialize(name: String, age: Int) 58 | let @name = name 59 | var @age = age 60 | end 61 | end 62 | 63 | let taro = Person.new("Taro", 20) 64 | p taro.name #=> "Taro" 65 | p taro.age #=> 20 66 | taro.age += 1 67 | ``` 68 | 69 | ## Class hierarchy 70 | 71 | ``` 72 | ^ ... superclass-subclass relationship 73 | ~ ... class-instance relationship 74 | 75 | Object Object Object 76 | ^ ^ ^ 77 | Class ~ MetaClass ~ MetaClass 78 | ^ 79 | Object ~ Meta:Object ~ MetaClass 80 | ^ ^ 81 | | | 82 | | | 83 | 123 ~ Int ~ Meta:Int ~ MetaClass 84 | ``` 85 | 86 | Example: 87 | 88 | ```sk 89 | p 123 #=> 123 90 | p 123.class #=> # 91 | p Int #=> # 92 | p 123.class == Int #=> true 93 | 94 | p Int.class #=> # 95 | p Int.class.class #=> # 96 | p Int.class.class.class #=> # 97 | ``` 98 | -------------------------------------------------------------------------------- /doc/spec/src/enums.md: -------------------------------------------------------------------------------- 1 | # Enums 2 | 3 | ## Enum definition 4 | 5 | ``` 6 | enum Enum1 7 | case Case1(a: A) 8 | case Case2(b: B) 9 | case Case3 10 | end 11 | ``` 12 | 13 | This defines these classes, 14 | 15 | - `class Enum1 : Object` 16 | - `class Enum1::Case1 : Enum1` 17 | - `class Enum1::Case2 : Enum1` 18 | - `class Enum1::Case3 : Enum1` 19 | 20 | these methods 21 | 22 | - `Enum1::Case1.new(a: A) -> Enum1::Case1` 23 | - `Enum1::Case1#a -> A` 24 | - `Enum1::Case2.new(b: B) -> Enum1::Case2` 25 | - `Enum1::Case2#b -> B` 26 | 27 | and these constants. 28 | 29 | - `::Enum1 : Meta:Enum1` 30 | - `::Enum1::Case1 : Meta:Enum1::Case1` 31 | - `::Enum1::Case2 : Meta:Enum1::Case2` 32 | - `::Enum1::Case3 : Enum1::Case3` 33 | 34 | Note that enum case with no parameters (`Case3` here) is special. 35 | 36 | - They have no type parameters. 37 | - `Never` is used for superclass type arguments. 38 | - The constant `::Enum1::Case3` holds the (only) instance, not the class. 39 | 40 | ## Enum classes 41 | 42 | The class `Enum1` is called an **Enum class**. The classes `Enum1::Case1`, `Enum1::Case2`, `Enum1::Case3`, are called **Enum case classses**. 43 | 44 | Enum classes and enum case classes cannot be an explicit superclass. 45 | 46 | ``` 47 | # error 48 | class A : Enum1 49 | end 50 | ``` 51 | -------------------------------------------------------------------------------- /doc/spec/src/types.md: -------------------------------------------------------------------------------- 1 | # Types 2 | 3 | TBA 4 | 5 | - classes 6 | - modules 7 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | dev: 5 | build: 6 | context: . 7 | dockerfile: Dockerfile 8 | volumes: 9 | - .:/app 10 | command: /bin/bash 11 | tty: true 12 | -------------------------------------------------------------------------------- /examples/fib.expected_out.txt: -------------------------------------------------------------------------------- 1 | fib(30) is... 2 | 832040 3 | -------------------------------------------------------------------------------- /examples/fib.sk: -------------------------------------------------------------------------------- 1 | class A 2 | def fib(n: Int) -> Int 3 | if n < 3 4 | 1 5 | else 6 | fib(n-1) + fib(n-2) 7 | end 8 | end 9 | end 10 | let a = A.new 11 | let n = 30 12 | puts "fib(#{n}) is..." 13 | p a.fib(n) 14 | -------------------------------------------------------------------------------- /examples/hello.expected_out.txt: -------------------------------------------------------------------------------- 1 | Hello, world! 2 | -------------------------------------------------------------------------------- /examples/hello.sk: -------------------------------------------------------------------------------- 1 | puts "Hello, world!" 2 | -------------------------------------------------------------------------------- /examples/lifegame.expected_out.txt: -------------------------------------------------------------------------------- 1 | ooo........................... 2 | o............................. 3 | .o............................ 4 | .............................. 5 | .............................. 6 | .............................. 7 | .............................. 8 | .............................. 9 | .............................. 10 | .............................. 11 | 12 | o.o........................... 13 | ..o........................... 14 | .o............................ 15 | .............................. 16 | .............................. 17 | .............................. 18 | .............................. 19 | .............................. 20 | .............................. 21 | .............................. 22 | 23 | .oo........................... 24 | ..o........................... 25 | .o............................ 26 | .............................. 27 | .............................. 28 | .............................. 29 | .............................. 30 | .............................. 31 | .............................. 32 | .............................. 33 | 34 | .oo........................... 35 | .............................. 36 | .o............................ 37 | .............................. 38 | .............................. 39 | .............................. 40 | .............................. 41 | .............................. 42 | .............................. 43 | .............................. 44 | 45 | .oo........................... 46 | .oo........................... 47 | .............................. 48 | .............................. 49 | .............................. 50 | .............................. 51 | .............................. 52 | .............................. 53 | .............................. 54 | .............................. 55 | 56 | -------------------------------------------------------------------------------- /examples/lifegame.sk: -------------------------------------------------------------------------------- 1 | class LifeGame 2 | class Board 3 | W = 30 4 | H = 10 5 | 6 | def initialize 7 | var @cells = Array.build>(H) do |y: Int| 8 | Array.build(W) do |x: Int| 9 | false 10 | end 11 | end 12 | @cells[0][0] = true 13 | @cells[0][1] = true 14 | @cells[0][2] = true 15 | @cells[1][0] = true 16 | @cells[2][1] = true 17 | end 18 | 19 | def all_dead? -> Bool 20 | var ret = true 21 | 0.upto(H-1) do |y: Int| 22 | 0.upto(W-1) do |x: Int| 23 | ret = false if @cells[y][x] 24 | end 25 | end 26 | ret 27 | end 28 | 29 | def simulate -> Array> 30 | let new_board = Array.build>(H) do |y: Int| 31 | Array.repeat(false, W) 32 | end 33 | 0.upto(H-1) do |y: Int| 34 | 0.upto(W-1) do |x: Int| 35 | let n_alive = _count_around(x, y, true) 36 | if @cells[y][x] 37 | new_board[y][x] = n_alive == 2 or n_alive == 3 38 | else 39 | new_board[y][x] = n_alive == 3 40 | end 41 | end 42 | end 43 | @cells = new_board 44 | end 45 | 46 | def _count_around(x: Int, y: Int, b: Bool) -> Int 47 | var n = 0 48 | [x-1, x, x+1].each do |xx: Int| 49 | [y-1, y, y+1].each do |yy: Int| 50 | if _is(xx, yy, b) 51 | n += 1 52 | end 53 | end 54 | end 55 | n 56 | end 57 | 58 | def _is(x: Int, y: Int, b: Bool) -> Bool 59 | return false if x < 0 or x >= W 60 | return false if y < 0 or y >= H 61 | @cells[y][x] == b 62 | end 63 | end 64 | 65 | def initialize 66 | let @board = Board.new 67 | end 68 | 69 | def run 70 | loop do 71 | _print_board(@board) 72 | @board.simulate 73 | break if @board.all_dead? 74 | end 75 | end 76 | 77 | def _print_board(board: Board) 78 | board.cells.each do |line: Array| 79 | line.each do |c: Bool| 80 | if c 81 | print "o" 82 | else 83 | print "." 84 | end 85 | end 86 | puts "" 87 | end 88 | puts "" 89 | end 90 | end 91 | 92 | LifeGame.new.run 93 | -------------------------------------------------------------------------------- /examples/mandel.expected_out.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiika-lang/shiika/c20d18b730d2f2fc2d80dd524285b7add2a4e876/examples/mandel.expected_out.pbm -------------------------------------------------------------------------------- /examples/mandel.sk: -------------------------------------------------------------------------------- 1 | # Original: https://github.com/ruby/ruby/blob/master/benchmark/so_mandelbrot.rb 2 | # How to run 3 | # $ cargo run -- run examples/mandel.sk > a.pbm 4 | # $ open a.pbm # With Preview.app or something 5 | # 6 | # This is not much a good example because Shiika is not designed for such 7 | # CPU-intensive tasks. However this was good when I just started Shiika 8 | # because this program requires little language constructs and yet produces 9 | # a fun result. 10 | let size = 600 11 | puts "P4" 12 | puts "#{size} #{size}" 13 | 14 | ITER = 49 15 | LIMIT_SQUARED = 4.0 16 | 17 | var byte_acc = 0 18 | var bit_num = 0 19 | let buf = MutableString.new 20 | 21 | let count_size = size - 1 22 | 23 | var a = 0; 24 | var b = 0; 25 | var y=0; while y <= count_size 26 | var x=0; while x <= count_size 27 | var zr = 0.0 28 | var zi = 0.0 29 | let cr = (2.0*x.to_f/size.to_f)-1.5 30 | let ci = (2.0*y.to_f/size.to_f)-1.0 31 | var escape = false 32 | 33 | var dummy = 0; while dummy <= ITER 34 | let tr = zr*zr - zi*zi + cr 35 | let ti = 2.0*zr*zi + ci 36 | zr = tr 37 | zi = ti 38 | 39 | if (zr*zr+zi*zi) > LIMIT_SQUARED 40 | escape = true 41 | break 42 | end 43 | dummy += 1 44 | end 45 | 46 | byte_acc = byte_acc.lshift(1).or(if escape then 0 else 1 end) 47 | bit_num += 1 48 | 49 | if bit_num == 8 50 | buf.append_byte(byte_acc) 51 | byte_acc = 0 52 | bit_num = 0 53 | a += 1 54 | elsif x == count_size 55 | byte_acc = byte_acc.lshift(8 - bit_num) 56 | buf.append_byte(byte_acc) 57 | byte_acc = 0 58 | bit_num = 0 59 | b += 1 60 | end 61 | 62 | x += 1 63 | end 64 | y += 1 65 | end 66 | print buf.to_s 67 | -------------------------------------------------------------------------------- /lib/shiika_ast/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "shiika_ast" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | shiika_core = { path = "../shiika_core" } 8 | anyhow = "1.0" 9 | -------------------------------------------------------------------------------- /lib/shiika_ast/README.md: -------------------------------------------------------------------------------- 1 | # shiika_ast 2 | 3 | This crate provides AST(Abstract Syntax Tree) of a Shiika program. 4 | -------------------------------------------------------------------------------- /lib/shiika_core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "shiika_core" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | serde = { version = "1.0.125", features = ["derive"] } 8 | serde_json = "1.0" 9 | nom = "7.1.3" -------------------------------------------------------------------------------- /lib/shiika_core/README.md: -------------------------------------------------------------------------------- 1 | # shiika_core 2 | 3 | This crate provides core concepts of Shiika programs such as types or names. 4 | 5 | ## mod `ty` 6 | 7 | ## mod `names` 8 | 9 | This module provides structs such as `ClassFullname`, `ModuleFullname`, or `TypeFullname`. 10 | ("Type" here means "a class or a module"; 11 | -------------------------------------------------------------------------------- /lib/shiika_core/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod names; 2 | pub mod ty; 3 | -------------------------------------------------------------------------------- /lib/shiika_core/src/names.rs: -------------------------------------------------------------------------------- 1 | mod class_name; 2 | mod const_name; 3 | mod method_name; 4 | mod module_name; 5 | mod namespace; 6 | mod type_name; 7 | pub use class_name::{ 8 | class_firstname, class_fullname, metaclass_fullname, ClassFirstname, ClassFullname, 9 | }; 10 | pub use const_name::{ 11 | const_fullname, resolved_const_name, toplevel_const, ConstFullname, ResolvedConstName, 12 | UnresolvedConstName, 13 | }; 14 | pub use method_name::{ 15 | method_firstname, method_fullname, method_fullname_raw, MethodFirstname, MethodFullname, 16 | }; 17 | pub use module_name::{module_firstname, module_fullname, ModuleFirstname, ModuleFullname}; 18 | pub use namespace::Namespace; 19 | pub use type_name::{type_fullname, TypeFullname}; 20 | -------------------------------------------------------------------------------- /lib/shiika_core/src/names/const_name.rs: -------------------------------------------------------------------------------- 1 | use super::class_name::*; 2 | use super::namespace::*; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | #[derive(Debug, PartialEq, Clone, Eq, Hash, Serialize, Deserialize)] 6 | pub struct ConstFullname(pub String); 7 | 8 | impl std::fmt::Display for ConstFullname { 9 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 10 | write!(f, "{}", self.0) 11 | } 12 | } 13 | 14 | impl ConstFullname { 15 | pub fn new(names: Vec) -> ConstFullname { 16 | const_fullname(names.join("::")) 17 | } 18 | } 19 | 20 | pub fn const_fullname(s_: impl Into) -> ConstFullname { 21 | let s = s_.into(); 22 | debug_assert!(!s.starts_with("::")); 23 | ConstFullname(format!("::{}", &s)) 24 | } 25 | 26 | pub fn toplevel_const(first_name: &str) -> ConstFullname { 27 | debug_assert!(!first_name.starts_with("::")); 28 | ConstFullname(format!("::{}", first_name)) 29 | } 30 | 31 | /// A const name not resolved yet 32 | #[derive(Debug, PartialEq, Eq, Clone)] 33 | pub struct UnresolvedConstName(pub Vec); 34 | 35 | /// Fully qualified const name. 36 | #[derive(Debug, PartialEq, Eq)] 37 | pub struct ResolvedConstName { 38 | // REFACTOR: Just ResolvedConstName(pub Vec) 39 | pub names: Vec, 40 | } 41 | 42 | impl std::fmt::Display for ResolvedConstName { 43 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 44 | write!(f, "{}", &self.string()) 45 | } 46 | } 47 | 48 | impl ResolvedConstName { 49 | pub fn new(names: Vec) -> ResolvedConstName { 50 | ResolvedConstName { names } 51 | } 52 | 53 | pub fn unsafe_create(s: String) -> ResolvedConstName { 54 | ResolvedConstName { names: vec![s] } 55 | } 56 | 57 | /// Convert to ConstFullname 58 | pub fn to_const_fullname(&self) -> ConstFullname { 59 | toplevel_const(&self.string()) 60 | } 61 | 62 | /// Convert to ClassFullname 63 | pub fn to_class_fullname(&self) -> ClassFullname { 64 | class_fullname(self.string()) 65 | } 66 | 67 | /// Returns string representation 68 | pub fn string(&self) -> String { 69 | self.names.join("::") 70 | } 71 | } 72 | 73 | /// Create a ResolvedConstName (which is not generic). 74 | pub fn resolved_const_name(namespace: Namespace, names: Vec) -> ResolvedConstName { 75 | let new_names = namespace 76 | .0 77 | .into_iter() 78 | .chain(names.into_iter()) 79 | .collect::>(); 80 | ResolvedConstName { names: new_names } 81 | } 82 | -------------------------------------------------------------------------------- /lib/shiika_core/src/names/module_name.rs: -------------------------------------------------------------------------------- 1 | use super::class_name::{metaclass_fullname, ClassFullname}; 2 | use super::const_name::{const_fullname, ConstFullname}; 3 | use super::type_name::{type_fullname, TypeFullname}; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | #[derive(Debug, PartialEq, Eq)] 7 | pub struct ModuleFirstname(pub String); 8 | 9 | impl std::fmt::Display for ModuleFirstname { 10 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 11 | write!(f, "{}", self.0) 12 | } 13 | } 14 | 15 | impl ModuleFirstname { 16 | pub fn add_namespace(&self, namespace: &str) -> ModuleFullname { 17 | if namespace.is_empty() { 18 | module_fullname(self.0.clone()) 19 | } else { 20 | module_fullname(namespace.to_string() + "::" + &self.0) 21 | } 22 | } 23 | } 24 | 25 | pub fn module_firstname(s: impl Into) -> ModuleFirstname { 26 | ModuleFirstname(s.into()) 27 | } 28 | 29 | #[derive(Debug, PartialEq, Clone, Eq, Hash, Serialize, Deserialize)] 30 | pub struct ModuleFullname(pub String); 31 | 32 | impl std::fmt::Display for ModuleFullname { 33 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 34 | write!(f, "{}", self.0) 35 | } 36 | } 37 | 38 | impl From for TypeFullname { 39 | fn from(x: ModuleFullname) -> Self { 40 | type_fullname(x.0) 41 | } 42 | } 43 | 44 | impl ModuleFullname { 45 | pub fn to_type_fullname(&self) -> TypeFullname { 46 | type_fullname(&self.0) 47 | } 48 | 49 | pub fn to_const_fullname(&self) -> ConstFullname { 50 | const_fullname(&self.0) 51 | } 52 | 53 | pub fn meta_name(&self) -> ClassFullname { 54 | metaclass_fullname(&self.0) 55 | } 56 | } 57 | 58 | pub fn module_fullname(s: impl Into) -> ModuleFullname { 59 | let name = s.into(); 60 | debug_assert!(name != "Meta:"); 61 | debug_assert!(!name.starts_with("::")); 62 | debug_assert!(!name.starts_with("Meta:Meta:")); 63 | ModuleFullname(name) 64 | } 65 | -------------------------------------------------------------------------------- /lib/shiika_core/src/ty/erasure.rs: -------------------------------------------------------------------------------- 1 | use crate::names::{ 2 | module_fullname, toplevel_const, ClassFullname, ConstFullname, ModuleFullname, TypeFullname, 3 | }; 4 | use crate::ty::{LitTy, TermTy}; 5 | use serde::{Deserialize, Serialize}; 6 | 7 | #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] 8 | pub struct Erasure { 9 | pub base_name: String, 10 | /// `true` if values of this type are classes 11 | pub is_meta: bool, 12 | } 13 | 14 | impl From for TypeFullname { 15 | fn from(x: Erasure) -> Self { 16 | TypeFullname::new(x.base_name, x.is_meta) 17 | } 18 | } 19 | 20 | impl Erasure { 21 | pub fn nonmeta(base_name_: impl Into) -> Erasure { 22 | Self::new(base_name_.into(), false) 23 | } 24 | 25 | pub fn meta(base_name_: impl Into) -> Erasure { 26 | Self::new(base_name_.into(), true) 27 | } 28 | 29 | pub fn new(base_name: String, is_meta_: bool) -> Erasure { 30 | let is_meta = if base_name == "Metaclass" { 31 | // There is no `Meta:Metaclass` 32 | true 33 | } else { 34 | is_meta_ 35 | }; 36 | Erasure { base_name, is_meta } 37 | } 38 | 39 | pub fn to_class_fullname(&self) -> ClassFullname { 40 | ClassFullname::new(&self.base_name, self.is_meta) 41 | } 42 | 43 | pub fn to_module_fullname(&self) -> ModuleFullname { 44 | debug_assert!(!self.is_meta); 45 | module_fullname(&self.base_name) 46 | } 47 | 48 | pub fn to_type_fullname(&self) -> TypeFullname { 49 | TypeFullname::new(&self.base_name, self.is_meta) 50 | } 51 | 52 | pub fn to_const_fullname(&self) -> ConstFullname { 53 | debug_assert!(self.is_meta); 54 | toplevel_const(&self.base_name) 55 | } 56 | 57 | pub fn to_term_ty(&self) -> TermTy { 58 | self.to_lit_ty().to_term_ty() 59 | } 60 | 61 | pub fn to_lit_ty(&self) -> LitTy { 62 | LitTy::new(self.base_name.clone(), Default::default(), self.is_meta) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lib/shiika_core/src/ty/typaram.rs: -------------------------------------------------------------------------------- 1 | use crate::ty::LitTy; 2 | use nom::IResult; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | /// A type parameter 6 | #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] 7 | pub struct TyParam { 8 | pub name: String, 9 | pub variance: Variance, 10 | pub upper_bound: LitTy, 11 | pub lower_bound: LitTy, 12 | } 13 | 14 | #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] 15 | pub enum Variance { 16 | Invariant, 17 | Covariant, // eg. `in T` 18 | Contravariant, // eg. `out T` 19 | } 20 | 21 | impl TyParam { 22 | pub fn new(name: impl Into, variance: Variance) -> TyParam { 23 | TyParam { 24 | name: name.into(), 25 | variance, 26 | upper_bound: LitTy::raw("Object"), 27 | lower_bound: LitTy::raw("Never"), 28 | } 29 | } 30 | 31 | /// Returns a serialized string which can be parsed by `deserialize` 32 | pub fn serialize(&self) -> String { 33 | let flag = match &self.variance { 34 | Variance::Invariant => "", 35 | Variance::Covariant => "+", 36 | Variance::Contravariant => "-", 37 | }; 38 | format!("{}{}", flag, &self.name) 39 | } 40 | 41 | /// nom parser for TyParam 42 | pub fn deserialize(s: &str) -> IResult<&str, TyParam> { 43 | let (s, c) = nom::combinator::opt(nom::character::complete::one_of("+-"))(s)?; 44 | let variance = match c { 45 | Some('+') => Variance::Covariant, 46 | Some('-') => Variance::Contravariant, 47 | _ => Variance::Invariant, 48 | }; 49 | 50 | let (s, name) = nom::character::complete::alphanumeric1(s)?; 51 | Ok((s, TyParam::new(name.to_string(), variance))) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/shiika_ffi/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "shiika_ffi" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /lib/shiika_ffi/README.md: -------------------------------------------------------------------------------- 1 | # shiika_ffi 2 | 3 | This crate provides basic functions to write Shiika library in Rust. 4 | -------------------------------------------------------------------------------- /lib/shiika_ffi/src/async_.rs: -------------------------------------------------------------------------------- 1 | pub mod chiika_env; 2 | pub use chiika_env::{ChiikaEnv, ChiikaValue}; 3 | use std::future::Future; 4 | 5 | #[allow(improper_ctypes_definitions)] 6 | pub type ContFuture = Box + Unpin + Send>; 7 | 8 | #[allow(improper_ctypes_definitions)] 9 | pub type ChiikaCont = extern "C" fn(env: *mut ChiikaEnv, value: ChiikaValue) -> ContFuture; 10 | -------------------------------------------------------------------------------- /lib/shiika_ffi/src/core_class.rs: -------------------------------------------------------------------------------- 1 | mod bool; 2 | mod int; 3 | mod object; 4 | pub use bool::SkBool; 5 | pub use int::SkInt; 6 | pub use object::SkObject; 7 | -------------------------------------------------------------------------------- /lib/shiika_ffi/src/core_class/bool.rs: -------------------------------------------------------------------------------- 1 | extern "C" { 2 | fn shiika_intrinsic_box_bool(b: bool) -> SkBool; 3 | } 4 | 5 | #[repr(C)] 6 | #[derive(Debug)] 7 | pub struct SkBool(*const ShiikaBool); 8 | 9 | unsafe impl Send for SkBool {} 10 | 11 | #[repr(C)] 12 | #[derive(Debug)] 13 | struct ShiikaBool { 14 | vtable: *const u8, 15 | class_obj: *const u8, 16 | value: bool, 17 | } 18 | 19 | impl From for bool { 20 | fn from(sk_bool: SkBool) -> Self { 21 | unsafe { (*sk_bool.0).value } 22 | } 23 | } 24 | 25 | impl From for SkBool { 26 | fn from(b: bool) -> Self { 27 | unsafe { shiika_intrinsic_box_bool(b) } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/shiika_ffi/src/core_class/int.rs: -------------------------------------------------------------------------------- 1 | extern "C" { 2 | fn shiika_intrinsic_box_int(i: i64) -> SkInt; 3 | } 4 | 5 | #[repr(C)] 6 | #[derive(Debug)] 7 | pub struct SkInt(*const ShiikaInt); 8 | 9 | unsafe impl Send for SkInt {} 10 | 11 | #[repr(C)] 12 | #[derive(Debug)] 13 | struct ShiikaInt { 14 | vtable: *const u8, 15 | class_obj: *const u8, 16 | value: i64, 17 | } 18 | 19 | impl From for i64 { 20 | fn from(sk_int: SkInt) -> Self { 21 | unsafe { (*sk_int.0).value } 22 | } 23 | } 24 | 25 | impl From for SkInt { 26 | fn from(i: i64) -> Self { 27 | unsafe { shiika_intrinsic_box_int(i) } 28 | } 29 | } 30 | 31 | impl SkInt { 32 | pub fn val(&self) -> i64 { 33 | unsafe { (*self.0).value } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/shiika_ffi/src/core_class/object.rs: -------------------------------------------------------------------------------- 1 | #[repr(C)] 2 | #[derive(Debug)] 3 | pub struct SkObject(*const ShiikaObject); 4 | 5 | unsafe impl Send for SkObject {} 6 | 7 | #[repr(C)] 8 | #[derive(Debug)] 9 | struct ShiikaObject { 10 | vtable: *const u8, 11 | class_obj: *const u8, 12 | } 13 | -------------------------------------------------------------------------------- /lib/shiika_ffi/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod async_; 2 | pub mod core_class; 3 | 4 | /// Returns the C-level name of a Shiika method 5 | /// (eg: `Int#+`, `Meta:Class#new`) 6 | pub fn mangle_method(method_name: &str) -> String { 7 | let s = method_name 8 | // Replace '_' to use '_' as delimiter 9 | .replace('_', "__") 10 | // Replace symbols to make the function callable from Rust(skc_rustlib) 11 | .replace("::", "_") 12 | .replace("Meta:", "Meta_") 13 | .replace('#', "_") 14 | .replace("+@", "uplus_") 15 | .replace("-@", "uminus_") 16 | .replace('+', "add_") 17 | .replace('-', "sub_") 18 | .replace('*', "mul_") 19 | .replace('/', "div_") 20 | .replace('%', "mod_") 21 | .replace("==", "eq_") 22 | .replace("<=", "le_") 23 | .replace(">=", "ge_") 24 | .replace('<', "lt_") 25 | .replace('>', "gt_") 26 | .replace("[]=", "aset_") 27 | .replace("[]", "aref_"); 28 | if s.ends_with('=') { 29 | format!("{}{}", "_set_", &s.replace('=', "")) 30 | } else { 31 | s 32 | } 33 | } 34 | 35 | /// Returns the C-level name of a Shiika constant. 36 | pub fn mangle_const(const_name: &str) -> String { 37 | let s = const_name 38 | // Replace '_' to use '_' as delimiter 39 | .replace('_', "__") 40 | // Trim the first "::" 41 | .trim_start_matches("::") 42 | // Replace symbols to make the global variable accesible from Rust 43 | .replace("::", "_"); 44 | format!("shiika_const_{}", &s) 45 | } 46 | -------------------------------------------------------------------------------- /lib/shiika_ffi_macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "shiika_ffi_macro" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | proc-macro = true 8 | 9 | [dependencies] 10 | shiika_ffi = { path = "../shiika_ffi" } 11 | syn = { version = "1.0.82", features = ["full"] } 12 | quote = "1.0" 13 | proc-macro2 = "1.0.43" 14 | -------------------------------------------------------------------------------- /lib/shiika_ffi_macro/README.md: -------------------------------------------------------------------------------- 1 | # shiika_ffi_macro 2 | 3 | This crate provides Rust macros to deal with Shiika objects and methods. 4 | -------------------------------------------------------------------------------- /lib/shiika_ffi_macro/src/shiika_const_ref.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Ident, Span}; 2 | use shiika_ffi::mangle_const; 3 | use syn::parse::{Parse, ParseStream}; 4 | use syn::{Result, Token}; 5 | 6 | /// Helper struct for `shiika_const_ref` macro 7 | pub struct ShiikaConstRef { 8 | pub const_name: syn::LitStr, 9 | pub const_ty: syn::Type, 10 | pub rust_func_name: syn::LitStr, 11 | } 12 | 13 | impl Parse for ShiikaConstRef { 14 | fn parse(input: ParseStream) -> Result { 15 | let const_name = input.parse()?; 16 | let _: Token![,] = input.parse()?; 17 | let const_ty = input.parse()?; 18 | let _: Token![,] = input.parse()?; 19 | let rust_func_name = input.parse()?; 20 | Ok(ShiikaConstRef { 21 | const_name, 22 | const_ty, 23 | rust_func_name, 24 | }) 25 | } 26 | } 27 | 28 | impl ShiikaConstRef { 29 | /// Returns mangled llvm constant name. 30 | pub fn mangled_name(&self) -> Ident { 31 | Ident::new(&mangle_const(&self.const_name.value()), Span::call_site()) 32 | } 33 | 34 | /// Returns user-specified wrapper function name. 35 | pub fn wrapper_name(&self) -> Ident { 36 | Ident::new(&self.rust_func_name.value(), Span::call_site()) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/shiika_ffi_macro/src/shiika_method_ref.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Ident, Span}; 2 | use shiika_ffi::mangle_method; 3 | use syn::parse::{Parse, ParseStream}; 4 | use syn::punctuated::Punctuated; 5 | use syn::{parenthesized, Field, Result, Token}; 6 | 7 | /// Helper struct for `shiika_method_ref` macro 8 | pub struct ShiikaMethodRef { 9 | pub method_name: syn::LitStr, 10 | pub parameters: Punctuated, 11 | pub ret_ty: syn::Type, 12 | pub rust_func_name: syn::LitStr, 13 | } 14 | 15 | impl Parse for ShiikaMethodRef { 16 | fn parse(input: ParseStream) -> Result { 17 | let method_name = input.parse()?; 18 | let _: Token![,] = input.parse()?; 19 | let _: Token![fn] = input.parse()?; 20 | let content; 21 | let _: syn::token::Paren = parenthesized!(content in input); 22 | let parameters = content.parse_terminated(Field::parse_named)?; 23 | let _: Token![->] = input.parse()?; 24 | let ret_ty = input.parse()?; 25 | let _: Token![,] = input.parse()?; 26 | let rust_func_name = input.parse()?; 27 | Ok(ShiikaMethodRef { 28 | method_name, 29 | parameters, 30 | ret_ty, 31 | rust_func_name, 32 | }) 33 | } 34 | } 35 | 36 | impl ShiikaMethodRef { 37 | /// Returns mangled llvm func name (eg. `Meta_Class_new`) 38 | pub fn mangled_name(&self) -> Ident { 39 | Ident::new(&mangle_method(&self.method_name.value()), Span::call_site()) 40 | } 41 | 42 | /// Returns user-specified func name. (eg. `meta_class_new`) 43 | pub fn wrapper_name(&self) -> Ident { 44 | Ident::new(&self.rust_func_name.value(), Span::call_site()) 45 | } 46 | 47 | /// Returns list of parameters for forwarding function call (eg. `a, b, c`) 48 | pub fn forwaring_args(&self) -> Punctuated { 49 | self.parameters 50 | .iter() 51 | .map(|field| field.ident.clone().expect("Field::ident is None. why?")) 52 | .collect() 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/shiika_parser/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "shiika_parser" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | shiika_core = { path = "../shiika_core" } 8 | shiika_ast = { path = "../shiika_ast" } 9 | thiserror = "1.0" 10 | ariadne = { version = "0.3.0", features = ["auto-color"] } 11 | -------------------------------------------------------------------------------- /lib/shiika_parser/README.md: -------------------------------------------------------------------------------- 1 | # shiika_parser 2 | 3 | This crate provides parser of Shiika programs. 4 | 5 | The parser is hand-written (i.e. no parser generator used.) 6 | 7 | Note: structs for ASTs (including `Token`, `Location`, `LocationSpan`, etc.) are in `lib/shiika_ast`, not here. 8 | -------------------------------------------------------------------------------- /lib/shiika_parser/src/error.rs: -------------------------------------------------------------------------------- 1 | use crate::lexer::Cursor; 2 | 3 | #[derive(thiserror::Error, Debug)] 4 | pub enum Error { 5 | /// Error on parsing 6 | #[error("{0})")] 7 | ParseError(String), 8 | /// Error on tokenizing 9 | #[error("{msg}")] 10 | LexError { msg: String, location: Cursor }, 11 | } 12 | -------------------------------------------------------------------------------- /lib/shiika_parser/src/source_file.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | use std::rc::Rc; 3 | 4 | #[derive(Debug)] 5 | pub struct SourceFile { 6 | pub path: Rc, 7 | pub content: String, 8 | } 9 | 10 | impl SourceFile { 11 | pub fn new(path: PathBuf, content: String) -> SourceFile { 12 | SourceFile { 13 | path: Rc::new(path), 14 | content, 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/skc_ast2hir/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "skc_ast2hir" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | shiika_core = { path = "../shiika_core" } 8 | shiika_ast = { path = "../shiika_ast" } 9 | skc_corelib = { path = "../skc_corelib" } 10 | skc_error = { path = "../skc_error" } 11 | skc_hir = { path = "../skc_hir" } 12 | anyhow = "1.0" 13 | thiserror = "1.0" 14 | log = "0.4.11" 15 | 16 | # TODO: used for libraryExports 17 | skc_mir = { path = "../skc_mir" } 18 | -------------------------------------------------------------------------------- /lib/skc_ast2hir/README.md: -------------------------------------------------------------------------------- 1 | # skc\_ast2hir 2 | 3 | This crate creates HIR (high-level IR) from AST. Typecheck is also done in this crate. 4 | 5 | The HIR itself is defined in another crate, [skc_hir](../skc_hir/). 6 | 7 | ## Important structs 8 | 9 | - `HirMaker` holds all the temporary information needed during compilation. 10 | - `ClassDict` holds information about all the classes (and modules.) 11 | - `HirMakerContext` is stored in `hir_maker.ctx_stack` and holds information 12 | about the "current" class, method, etc. 13 | 14 | ## Overview of compilation process 15 | 16 | 1. `skc_ast2hir::make_hir` is called 17 | 1. Create `class_dict::TypeIndex` with `type_index::create`. This is a hashmap from type name to its type parameters. 18 | 1. Create `ClassDict` with `class_dict::create`. This collects method signatures in advance of processing the method bodies. 19 | 1. Start compilation with `hir_maker::convert_toplevel_items`. This will traverse the entire AST and process each method. Compiled methods are stored as `SkMethod` in `hir_maker.method_dict`. 20 | 21 | ## Type inferece 22 | 23 | Shiika implements two types of type inference: 24 | 25 | 1. Infer block parameter type 26 | 27 | ```sk 28 | [1, 2, 3].each{|i: Int| p i} 29 | can be written as 30 | [1, 2, 3].each{|i| p i} 31 | ``` 32 | 33 | 2. Infer method-wise type argument 34 | 35 | ```sk 36 | [1, 2, 3].map{|i| i.to_s} 37 | can be written as 38 | [1, 2, 3].map{|i| i.to_s} 39 | 40 | Also, 41 | Pair.new(1, true) 42 | can be written as 43 | Pair.new(1, true) 44 | which is this form with the tyargs inferred 45 | Pair.new.new(1, true) 46 | ``` 47 | 48 | Both are done by per-method-call basis (i.e. not per method definition.) 49 | -------------------------------------------------------------------------------- /lib/skc_ast2hir/src/method_dict.rs: -------------------------------------------------------------------------------- 1 | use shiika_core::names::*; 2 | use skc_hir::*; 3 | use std::collections::HashMap; 4 | 5 | /// Contains all the methods 6 | #[derive(Debug)] 7 | pub struct MethodDict(pub SkMethods); 8 | 9 | impl MethodDict { 10 | pub fn new() -> MethodDict { 11 | MethodDict(HashMap::new()) 12 | } 13 | 14 | /// Return the vec for the method for mutation 15 | pub fn add_method(&mut self, typename: TypeFullname, method: SkMethod) { 16 | let v: &mut Vec = self.0.entry(typename).or_default(); 17 | v.push(method); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/skc_ast2hir/src/type_system.rs: -------------------------------------------------------------------------------- 1 | pub mod subtyping; 2 | pub mod type_checking; 3 | -------------------------------------------------------------------------------- /lib/skc_async_experiment/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "skc_async_experiment" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | shiika_ast = { path = "../shiika_ast" } 8 | shiika_parser = { path = "../shiika_parser" } 9 | shiika_ffi = { path = "../shiika_ffi" } 10 | shiika_core = { path = "../shiika_core" } 11 | skc_ast2hir = { path = "../skc_ast2hir" } 12 | skc_hir = { path = "../skc_hir" } 13 | skc_mir = { path = "../skc_mir" } 14 | 15 | inkwell = { git = "https://github.com/TheDan64/inkwell", features = ["llvm16-0"], rev = "4030f76" } 16 | nom = "7.1.3" 17 | peg = "0.8.2" 18 | ariadne = "0.3.0" 19 | anyhow = "1.0" 20 | either = "1.9.0" 21 | nom_locate = "4.2.0" 22 | os_info = "3.7.0" 23 | clap = { version = "4.5.35", features = ["derive"] } 24 | log = "0.4.25" 25 | env_logger = "0.11.6" 26 | # To read exports.json5 27 | json5 = "0.2.8" 28 | serde = { version = "1.0", features = ["derive"] } 29 | serde_json = "1.0.140" 30 | 31 | [dev-dependencies] 32 | insta = { version = "1.32.0", features = ["glob"] } 33 | -------------------------------------------------------------------------------- /lib/skc_async_experiment/README.md: -------------------------------------------------------------------------------- 1 | # skc_async_experiment 2 | 3 | -> doc/new_runtime.md 4 | -------------------------------------------------------------------------------- /lib/skc_async_experiment/src/build.rs: -------------------------------------------------------------------------------- 1 | pub mod cargo_builder; 2 | pub mod compiler; 3 | pub mod exe_builder; 4 | pub mod lib_builder; 5 | pub mod linker; 6 | pub mod loader; 7 | use crate::package::Package; 8 | use std::path::Path; 9 | 10 | /// Represents what to compile. (an executable or a library) 11 | pub struct CompileTarget<'a> { 12 | /// Path to the first .sk file to read 13 | pub entry_point: &'a Path, 14 | /// Directory to create the artifact 15 | pub out_dir: &'a Path, 16 | /// Direct dependencies 17 | pub deps: &'a [Package], 18 | /// Lib or Bin specific information 19 | pub detail: CompileTargetDetail<'a>, 20 | } 21 | 22 | pub enum CompileTargetDetail<'a> { 23 | Lib { 24 | package: &'a Package, 25 | }, 26 | Bin { 27 | package: Option<&'a Package>, 28 | /// Topologically sorted indirect dependencies 29 | total_deps: Vec, 30 | }, 31 | } 32 | 33 | impl<'a> CompileTarget<'a> { 34 | pub fn is_bin(&self) -> bool { 35 | matches!(self.detail, CompileTargetDetail::Bin { .. }) 36 | } 37 | 38 | fn package(&self) -> Option<&'a Package> { 39 | match &self.detail { 40 | CompileTargetDetail::Lib { package } => Some(package), 41 | CompileTargetDetail::Bin { package, .. } => package.clone(), 42 | } 43 | } 44 | 45 | pub fn package_name(&self) -> Option { 46 | self.package().map(|x| x.spec.name.clone()) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/skc_async_experiment/src/build/cargo_builder.rs: -------------------------------------------------------------------------------- 1 | use crate::cli::Cli; 2 | use crate::package::Package; 3 | use anyhow::{bail, Result}; 4 | use std::process::Command; 5 | 6 | pub fn run(cli: &mut Cli, p: &Package) -> Result<()> { 7 | let Some(rust_libs) = &p.spec.rust_libs else { 8 | return Ok(()); 9 | }; 10 | for rust_lib in rust_libs { 11 | let manifest_path = p 12 | .spec_path 13 | .parent() 14 | .unwrap() 15 | .join(rust_lib) 16 | .join("Cargo.toml"); 17 | let target_dir = cli.cargo_target_dir(&p.spec); 18 | let mut cmd = Command::new("cargo"); 19 | cmd.arg("build"); 20 | cmd.arg("--manifest-path").arg(manifest_path); 21 | cmd.arg("--target-dir").arg(target_dir); 22 | if !cmd.status()?.success() { 23 | bail!("cargo failed ({:?})", cmd); 24 | } 25 | } 26 | Ok(()) 27 | } 28 | -------------------------------------------------------------------------------- /lib/skc_async_experiment/src/build/exe_builder.rs: -------------------------------------------------------------------------------- 1 | use crate::build; 2 | use crate::cli::Cli; 3 | use crate::package::Package; 4 | use anyhow::Result; 5 | use std::path::PathBuf; 6 | 7 | /// Builds a single .sk file and generates an executable. 8 | /// Returns the path to the generated executable. 9 | pub fn run(cli: &mut Cli, entry_point: &PathBuf) -> Result { 10 | let deps = vec![Package::load_core(cli)?]; //TODO: load dependencies 11 | let total_deps = deps.iter().map(|x| x.spec.name.clone()).collect(); 12 | let out_dir = entry_point.parent().unwrap(); 13 | let target = build::CompileTarget { 14 | entry_point, 15 | out_dir: &out_dir, 16 | deps: &deps, 17 | detail: build::CompileTargetDetail::Bin { 18 | package: None, 19 | total_deps, 20 | }, 21 | }; 22 | let bc_path = build::compiler::compile(cli, &target)?; 23 | let artifacts = deps 24 | .iter() 25 | .flat_map(|pkg| pkg.artifacts.clone()) 26 | .collect::>(); 27 | build::linker::run(bc_path, &artifacts) 28 | } 29 | -------------------------------------------------------------------------------- /lib/skc_async_experiment/src/build/lib_builder.rs: -------------------------------------------------------------------------------- 1 | //! Compiles the Shiika code in a package into single .bc. 2 | use crate::build; 3 | use crate::cli::Cli; 4 | use crate::package::Package; 5 | use anyhow::Result; 6 | 7 | pub fn build(cli: &mut Cli, package: &Package) -> Result<()> { 8 | let deps = vec![]; // TODO: get deps from package 9 | let target = build::CompileTarget { 10 | entry_point: &package.entry_point(), 11 | out_dir: &cli.lib_target_dir(&package.spec), 12 | deps: &deps, 13 | detail: build::CompileTargetDetail::Lib { package }, 14 | }; 15 | build::compiler::compile(cli, &target)?; 16 | Ok(()) 17 | } 18 | -------------------------------------------------------------------------------- /lib/skc_async_experiment/src/build/loader.rs: -------------------------------------------------------------------------------- 1 | // Resolve "require" 2 | use anyhow::{Context, Result}; 3 | use shiika_parser::SourceFile; 4 | use std::fs; 5 | use std::path::{Path, PathBuf}; 6 | 7 | /// Read a .sk file (and those require'd by it) 8 | pub fn load(path: &Path) -> Result> { 9 | let mut files = vec![]; 10 | let mut loading_files = vec![]; 11 | load_file(path, &mut files, &mut loading_files)?; 12 | Ok(files) 13 | } 14 | 15 | fn load_file( 16 | path: &Path, 17 | files: &mut Vec, 18 | loading_files: &mut Vec, 19 | ) -> Result<()> { 20 | if loading_files.contains(&path.into()) { 21 | return Ok(()); 22 | } 23 | loading_files.push(path.into()); 24 | 25 | // Load require'd files first 26 | let content = fs::read_to_string(path).context(format!("failed to load {}", path.display()))?; 27 | let newpaths = resolve_requires(path, &content); 28 | for newpath in newpaths { 29 | load_file(&newpath, files, loading_files)?; 30 | } 31 | 32 | files.push(SourceFile::new(path.into(), content)); 33 | Ok(()) 34 | } 35 | 36 | /// Read require'd files into `files` 37 | #[allow(clippy::if_same_then_else)] 38 | fn resolve_requires(path: &Path, content: &str) -> Vec { 39 | let mut paths = vec![]; 40 | for line in content.lines() { 41 | if line.trim_start().starts_with("require") { 42 | paths.push(parse_require(line, path)); 43 | } else if line.trim_start().starts_with('#') { 44 | // skip comment line. 45 | } else if line.trim_start().is_empty() { 46 | // skip empty line. 47 | } else { 48 | break; 49 | } 50 | } 51 | paths 52 | } 53 | 54 | /// Expand filepath in require 55 | fn parse_require(line: &str, path: &Path) -> PathBuf { 56 | let s = line 57 | .trim_start() 58 | .trim_start_matches("require") 59 | .trim_start() 60 | .trim_start_matches('"') 61 | .trim_end() 62 | .trim_end_matches('"'); 63 | path.with_file_name(s) 64 | } 65 | -------------------------------------------------------------------------------- /lib/skc_async_experiment/src/cli/command_line_options.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use clap::{Parser, Subcommand}; 4 | 5 | #[derive(Parser)] 6 | #[command(version, about, long_about = None)] 7 | pub struct CommandLineOptions { 8 | #[command(subcommand)] 9 | pub command: Option, 10 | } 11 | 12 | #[derive(Subcommand)] 13 | pub enum Command { 14 | Build { path: PathBuf }, 15 | Compile { path: PathBuf }, 16 | Run { path: PathBuf }, 17 | } 18 | -------------------------------------------------------------------------------- /lib/skc_async_experiment/src/codegen/codegen_context.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | #[derive(Debug)] 4 | pub struct CodeGenContext<'run> { 5 | /// Current llvm function 6 | pub function: inkwell::values::FunctionValue<'run>, 7 | pub lvars: HashMap>, 8 | } 9 | -------------------------------------------------------------------------------- /lib/skc_async_experiment/src/codegen/constants.rs: -------------------------------------------------------------------------------- 1 | use crate::codegen::CodeGen; 2 | use crate::mir; 3 | use shiika_core::names::ConstFullname; 4 | use shiika_core::ty::TermTy; 5 | 6 | pub fn declare_extern_consts(gen: &mut CodeGen, constants: Vec<(ConstFullname, TermTy)>) { 7 | for (fullname, _) in constants { 8 | let name = mir::mir_const_name(fullname); 9 | let global = gen.module.add_global(gen.ptr_type(), None, &name); 10 | global.set_linkage(inkwell::module::Linkage::External); 11 | // @init_::XX 12 | //let fn_type = gen.context.void_type().fn_type(&[], false); 13 | //gen.module 14 | // .add_function(&const_initialize_func_name(fullname), fn_type, None); 15 | } 16 | } 17 | 18 | pub fn declare_const_globals(gen: &mut CodeGen, consts: &[(ConstFullname, TermTy)]) { 19 | for (fullname, _) in consts { 20 | let name = mir::mir_const_name(fullname.clone()); 21 | let global = gen.module.add_global(gen.ptr_type(), None, &name); 22 | global.set_initializer(&gen.ptr_type().const_null()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/skc_async_experiment/src/codegen/instance.rs: -------------------------------------------------------------------------------- 1 | use crate::codegen::{llvm_struct, value::SkObj, CodeGen}; 2 | use crate::names::FunctionName; 3 | use inkwell::values::BasicValue; 4 | 5 | pub fn build_ivar_load_raw<'run>( 6 | gen: &mut CodeGen<'run, '_>, 7 | sk_obj: SkObj<'run>, 8 | struct_type: inkwell::types::StructType<'run>, 9 | item_type: inkwell::types::BasicTypeEnum<'run>, 10 | idx: usize, 11 | name: &str, 12 | ) -> inkwell::values::BasicValueEnum<'run> { 13 | let i = llvm_struct::OBJ_HEADER_SIZE + idx; 14 | let ptr = sk_obj.0; 15 | llvm_struct::build_llvm_value_load(gen, struct_type, ptr, item_type, i, name) 16 | } 17 | 18 | //pub fn build_ivar_store<'run>( 19 | // gen: &mut CodeGen, 20 | // sk_obj: SkObj, 21 | // struct_type: &inkwell::types::StructType<'run>, 22 | // idx: usize, 23 | // value: SkObj<'run>, 24 | // name: &str, 25 | //) { 26 | // let llvm_value = value.0.as_basic_value_enum(); 27 | // build_ivar_store_raw(gen, sk_obj, struct_type, idx, llvm_value, name); 28 | //} 29 | 30 | pub fn build_ivar_store_raw<'run>( 31 | gen: &mut CodeGen<'run, '_>, 32 | sk_obj: SkObj<'run>, 33 | struct_type: &inkwell::types::StructType<'run>, 34 | idx: usize, 35 | value: inkwell::values::BasicValueEnum, 36 | name: &str, 37 | ) { 38 | let i = llvm_struct::OBJ_HEADER_SIZE + idx; 39 | let ptr = sk_obj.0; 40 | llvm_struct::build_llvm_value_store(gen, struct_type, ptr, i, value, name); 41 | } 42 | 43 | pub fn allocate_sk_obj<'run>(gen: &mut CodeGen<'run, '_>, name: &str) -> SkObj<'run> { 44 | let t = gen 45 | .context 46 | .get_struct_type(name) 47 | .expect(&format!("struct type not found: {}", name)); 48 | SkObj(allocate_mem(gen, &t)) 49 | } 50 | 51 | /// Allocate some memory for a value of LLVM type `t`. Returns void ptr. 52 | fn allocate_mem<'run>( 53 | gen: &mut CodeGen<'run, '_>, 54 | t: &inkwell::types::StructType<'run>, 55 | ) -> inkwell::values::PointerValue<'run> { 56 | let size = t.size_of().expect("type has no size"); 57 | shiika_malloc(gen, size) 58 | } 59 | 60 | /// Call `shiika_malloc` 61 | fn shiika_malloc<'run>( 62 | gen: &mut CodeGen<'run, '_>, 63 | size: inkwell::values::IntValue<'run>, 64 | ) -> inkwell::values::PointerValue<'run> { 65 | let func = gen.get_llvm_func(&FunctionName::mangled("shiika_malloc")); 66 | gen.builder 67 | .build_direct_call(func, &[size.as_basic_value_enum().into()], "mem") 68 | .try_as_basic_value() 69 | .left() 70 | .unwrap() 71 | .into_pointer_value() 72 | } 73 | -------------------------------------------------------------------------------- /lib/skc_async_experiment/src/codegen/mir_analysis.rs: -------------------------------------------------------------------------------- 1 | pub mod list_constants; 2 | -------------------------------------------------------------------------------- /lib/skc_async_experiment/src/codegen/mir_analysis/list_constants.rs: -------------------------------------------------------------------------------- 1 | use crate::mir; 2 | use crate::mir::visitor::MirVisitor; 3 | use anyhow::Result; 4 | 5 | /// Returns a list of constants defined 6 | pub fn run(funcs: &[mir::Function]) -> Vec<(String, mir::Ty)> { 7 | let mut visitor = ListConstants::new(); 8 | visitor.walk_funcs(funcs).unwrap(); 9 | visitor.get() 10 | } 11 | 12 | pub struct ListConstants(Vec<(String, mir::Ty)>); 13 | impl ListConstants { 14 | fn new() -> Self { 15 | ListConstants(vec![]) 16 | } 17 | 18 | fn get(self) -> Vec<(String, mir::Ty)> { 19 | self.0 20 | } 21 | } 22 | impl mir::visitor::MirVisitor for ListConstants { 23 | fn visit_expr(&mut self, texpr: &mir::TypedExpr) -> Result<()> { 24 | match texpr { 25 | (mir::Expr::ConstSet(name, _), ty) => { 26 | self.0.push((name.clone(), ty.clone())); 27 | } 28 | _ => {} 29 | } 30 | Ok(()) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/skc_async_experiment/src/codegen/value.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone)] 2 | pub struct SkObj<'run>(pub inkwell::values::PointerValue<'run>); 3 | 4 | impl<'run> From> for inkwell::values::BasicValueEnum<'run> { 5 | fn from(obj: SkObj<'run>) -> Self { 6 | obj.0.into() 7 | } 8 | } 9 | 10 | impl<'run> SkObj<'run> { 11 | pub fn from_basic_value_enum(value: inkwell::values::BasicValueEnum<'run>) -> Self { 12 | SkObj(value.into_pointer_value()) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/skc_async_experiment/src/codegen/vtables.rs: -------------------------------------------------------------------------------- 1 | use crate::codegen::CodeGen; 2 | use shiika_core::names::ClassFullname; 3 | use skc_mir; 4 | 5 | /// Declare vtable constants 6 | pub fn define(gen: &mut CodeGen, vtables: &skc_mir::VTables) { 7 | for (class_fullname, vtable) in vtables.iter() { 8 | let method_names = vtable.to_vec(); 9 | let ary_type = gen.ptr_type().array_type(method_names.len() as u32); 10 | let tmp = llvm_vtable_const_name(class_fullname); 11 | let global = gen.module.add_global(ary_type, None, &tmp); 12 | global.set_constant(true); 13 | let func_ptrs = method_names 14 | .iter() 15 | .map(|name| { 16 | let func = gen 17 | .get_llvm_func(&name.into()) 18 | .as_global_value() 19 | .as_pointer_value(); 20 | gen.builder 21 | .build_bitcast(func, gen.ptr_type(), "") 22 | .into_pointer_value() 23 | }) 24 | .collect::>(); 25 | global.set_initializer(&gen.ptr_type().const_array(&func_ptrs)); 26 | } 27 | } 28 | 29 | /// Name of llvm constant of a vtable 30 | fn llvm_vtable_const_name(classname: &ClassFullname) -> String { 31 | format!("shiika_vtable_{}", classname.0) 32 | } 33 | -------------------------------------------------------------------------------- /lib/skc_async_experiment/src/hir/ty.rs: -------------------------------------------------------------------------------- 1 | use crate::hir::Asyncness; 2 | use shiika_core::ty::{self, TermTy}; 3 | use skc_hir::MethodSignature; 4 | 5 | #[derive(Debug, Clone, PartialEq, Eq)] 6 | pub struct FunTy { 7 | pub asyncness: Asyncness, 8 | pub param_tys: Vec, 9 | pub ret_ty: TermTy, 10 | } 11 | 12 | impl FunTy { 13 | fn new(asyncness: Asyncness, param_tys: Vec, ret_ty: TermTy) -> Self { 14 | FunTy { 15 | asyncness, 16 | param_tys, 17 | ret_ty, 18 | } 19 | } 20 | 21 | pub fn from_sig(sig: &MethodSignature, asyncness: Asyncness) -> Self { 22 | Self::new(asyncness, sig.param_tys(), sig.ret_ty.clone()) 23 | } 24 | 25 | pub fn sync(param_tys: Vec, ret_ty: TermTy) -> Self { 26 | Self::new(Asyncness::Sync, param_tys, ret_ty) 27 | } 28 | 29 | pub fn async_(param_tys: Vec, ret_ty: TermTy) -> Self { 30 | Self::new(Asyncness::Async, param_tys, ret_ty) 31 | } 32 | 33 | pub fn lowered(param_tys: Vec, ret_ty: TermTy) -> Self { 34 | Self::new(Asyncness::Lowered, param_tys, ret_ty) 35 | } 36 | 37 | pub fn to_term_ty(self) -> TermTy { 38 | let base_name = format!("Fn{}", self.param_tys.len()); 39 | let mut ts = self.param_tys; 40 | ts.push(self.ret_ty); 41 | ty::nonmeta(base_name, ts) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/skc_async_experiment/src/hir_building.rs: -------------------------------------------------------------------------------- 1 | pub mod define_new; 2 | -------------------------------------------------------------------------------- /lib/skc_async_experiment/src/hir_to_mir/collect_allocs.rs: -------------------------------------------------------------------------------- 1 | use crate::hir; 2 | use crate::hir::visitor::HirVisitor; 3 | use anyhow::Result; 4 | use shiika_core::ty::TermTy; 5 | 6 | pub fn run(body_stmts: &hir::TypedExpr) -> Vec<(String, TermTy)> { 7 | Allocs::collect(body_stmts) 8 | } 9 | 10 | pub struct Allocs(Vec<(String, TermTy)>); 11 | impl Allocs { 12 | /// Collects `alloc`ed variable names and their types. 13 | pub fn collect(body_stmts: &hir::TypedExpr) -> Vec<(String, TermTy)> { 14 | let mut a = Allocs(vec![]); 15 | a.walk_expr(body_stmts).unwrap(); 16 | a.0 17 | } 18 | } 19 | impl HirVisitor for Allocs { 20 | fn visit_expr(&mut self, texpr: &hir::TypedExpr) -> Result<()> { 21 | match texpr { 22 | (hir::Expr::LVarDecl(name, rhs), _) => { 23 | self.0.push((name.clone(), rhs.1.clone())); 24 | } 25 | _ => {} 26 | } 27 | Ok(()) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/skc_async_experiment/src/hir_to_mir/constants.rs: -------------------------------------------------------------------------------- 1 | use crate::mir; 2 | use crate::names::FunctionName; 3 | use shiika_core::names::ConstFullname; 4 | 5 | pub fn create_const_init_func( 6 | package: Option<&String>, 7 | constants: Vec<(ConstFullname, mir::TypedExpr)>, 8 | ) -> mir::Function { 9 | let mut body_stmts: Vec<_> = constants 10 | .into_iter() 11 | .map(|(name, rhs)| mir::Expr::const_set(mir::mir_const_name(name), rhs)) 12 | .collect(); 13 | body_stmts.push(mir::Expr::return_(mir::Expr::number(0))); 14 | mir::Function { 15 | // PERF: Currently all const init functions are treated as async (safe side) 16 | asyncness: mir::Asyncness::Async, 17 | name: package_const_init_name(package), 18 | params: vec![], 19 | ret_ty: mir::Ty::Raw("Int".to_string()), 20 | body_stmts: mir::Expr::exprs(body_stmts), 21 | } 22 | } 23 | 24 | pub fn const_init_externs(deps: &[String]) -> Vec { 25 | deps.iter() 26 | .map(|name| mir::Extern { 27 | name: package_const_init_name(Some(name)), 28 | fun_ty: mir::FunTy::new( 29 | // PERF: Currently all const init functions are treated as async (safe side) 30 | mir::Asyncness::Async, 31 | vec![], 32 | mir::Ty::Raw("Int".to_string()), 33 | ), 34 | }) 35 | .collect() 36 | } 37 | 38 | pub fn call_all_const_inits(total_deps: &[String]) -> Vec { 39 | total_deps 40 | .iter() 41 | .map(|name| { 42 | let fname = package_const_init_name(Some(name)); 43 | let fun_ty = mir::FunTy::new( 44 | mir::Asyncness::Unknown, 45 | vec![], 46 | mir::Ty::Raw("Int".to_string()), 47 | ); 48 | mir::Expr::fun_call(mir::Expr::func_ref(fname, fun_ty), vec![]) 49 | }) 50 | .collect() 51 | } 52 | 53 | fn package_const_init_name(package_name: Option<&String>) -> FunctionName { 54 | let suffix = if let Some(pkg) = package_name { 55 | format!("{}", pkg) 56 | } else { 57 | String::new() 58 | }; 59 | // Names of functions which handled by async_splitter should be unmangled. 60 | FunctionName::unmangled(format!("shiika_init_const_{}", suffix)) 61 | } 62 | -------------------------------------------------------------------------------- /lib/skc_async_experiment/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod build; 2 | mod cli; 3 | mod codegen; 4 | pub mod hir; 5 | mod hir_building; 6 | mod hir_to_mir; 7 | mod mir; 8 | pub mod mir_lowering; 9 | mod names; 10 | mod package; 11 | pub mod prelude; 12 | pub mod run; 13 | mod targets; 14 | -------------------------------------------------------------------------------- /lib/skc_async_experiment/src/mir_lowering.rs: -------------------------------------------------------------------------------- 1 | pub mod async_splitter; 2 | pub mod asyncness_check; 3 | pub mod pass_async_env; 4 | pub mod resolve_env_op; 5 | -------------------------------------------------------------------------------- /lib/skc_async_experiment/src/names.rs: -------------------------------------------------------------------------------- 1 | use shiika_core::names::MethodFullname; 2 | use skc_hir::MethodSignature; 3 | use std::fmt; 4 | 5 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 6 | pub enum FunctionName { 7 | Unmangled(String), 8 | Mangled(String), 9 | } 10 | 11 | impl fmt::Display for FunctionName { 12 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 13 | match self { 14 | FunctionName::Unmangled(name) => write!(f, "{}", name), 15 | FunctionName::Mangled(name) => write!(f, "{}", name), 16 | } 17 | } 18 | } 19 | 20 | impl From for FunctionName { 21 | fn from(sig: MethodSignature) -> FunctionName { 22 | FunctionName::from_sig(&sig) 23 | } 24 | } 25 | 26 | impl From<&MethodSignature> for FunctionName { 27 | fn from(sig: &MethodSignature) -> FunctionName { 28 | FunctionName::from_sig(sig) 29 | } 30 | } 31 | 32 | impl From for FunctionName { 33 | fn from(name: MethodFullname) -> FunctionName { 34 | FunctionName::Unmangled(name.full_name) 35 | } 36 | } 37 | 38 | impl From<&MethodFullname> for FunctionName { 39 | fn from(name: &MethodFullname) -> FunctionName { 40 | FunctionName::Unmangled(name.full_name.clone()) 41 | } 42 | } 43 | 44 | impl FunctionName { 45 | pub fn unmangled(name: impl Into) -> FunctionName { 46 | FunctionName::Unmangled(name.into()) 47 | } 48 | 49 | pub fn from_sig(sig: &MethodSignature) -> FunctionName { 50 | FunctionName::unmangled(&sig.fullname.full_name) 51 | } 52 | 53 | pub fn method(class_name: impl AsRef, name: impl AsRef) -> FunctionName { 54 | FunctionName::Unmangled(format!("{}#{}", class_name.as_ref(), name.as_ref())) 55 | } 56 | 57 | pub fn mangled(name: impl Into) -> FunctionName { 58 | FunctionName::Mangled(name.into()) 59 | } 60 | 61 | pub fn mangle(&self) -> String { 62 | match self { 63 | FunctionName::Unmangled(name) => shiika_ffi::mangle_method(name), 64 | FunctionName::Mangled(name) => name.clone(), 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/skc_async_experiment/src/run.rs: -------------------------------------------------------------------------------- 1 | use crate::cli; 2 | use anyhow::Result; 3 | use clap::Parser; 4 | 5 | pub fn main() -> Result<()> { 6 | env_logger::init(); 7 | let mut cli = cli::Cli::init()?; 8 | let options = cli::CommandLineOptions::try_parse()?; 9 | match &options.command { 10 | Some(cli::Command::Build { path }) => { 11 | cli.build(path)?; 12 | } 13 | Some(cli::Command::Compile { path }) => { 14 | cli.compile(path)?; 15 | } 16 | Some(cli::Command::Run { path }) => { 17 | cli.run(path)?; 18 | } 19 | None => {} 20 | } 21 | Ok(()) 22 | } 23 | -------------------------------------------------------------------------------- /lib/skc_async_experiment/src/targets.rs: -------------------------------------------------------------------------------- 1 | use os_info::{Type, Version}; 2 | 3 | /// Returns default `TargetTriple` 4 | pub fn default_triple() -> inkwell::targets::TargetTriple { 5 | let info = os_info::get(); 6 | if info.os_type() == Type::Macos { 7 | // #281: calculate target triple to avoid clang's warning 8 | if let Some(arch) = info.architecture() { 9 | if let Version::Semantic(major, minor, patch) = info.version() { 10 | let s = format!("{}-apple-macosx{}.{}.{}", arch, major, minor, patch); 11 | return inkwell::targets::TargetTriple::create(&s); 12 | } 13 | } 14 | } 15 | inkwell::targets::TargetMachine::get_default_triple() 16 | } 17 | -------------------------------------------------------------------------------- /lib/skc_async_experiment/tests/cps.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use insta::{assert_snapshot, glob}; 3 | use shiika_parser::{Parser, SourceFile}; 4 | use skc_async_experiment::{hir, hir_lowering, prelude}; 5 | use std::path::Path; 6 | 7 | #[test] 8 | fn test_cps_transformation() -> Result<()> { 9 | let base = Path::new(".").canonicalize()?; 10 | glob!("cps/**/*.sk", |sk_path_| { 11 | // Make the path relative to the project root so that the resulting .snap will be 12 | // identical on my machine and in the CI environment. 13 | let sk_path = sk_path_.strip_prefix(&base).unwrap(); 14 | assert_snapshot!(compile(sk_path).unwrap()); 15 | }); 16 | Ok(()) 17 | } 18 | 19 | fn compile(sk_path: &Path) -> Result { 20 | let txt = std::fs::read_to_string(sk_path).unwrap(); 21 | let src = SourceFile::new(sk_path.to_path_buf(), txt); 22 | let ast = Parser::parse_files(&[src])?; 23 | let mut hir = hir::untyped::create(&ast)?; 24 | hir.externs = prelude::lib_externs(Path::new("../skc_runtime/"))? 25 | .into_iter() 26 | .map(|(name, fun_ty)| hir::Extern { name, fun_ty }) 27 | .collect(); 28 | hir::typing::run(&mut hir)?; 29 | hir = hir::asyncness_check::run(hir); 30 | hir = hir_lowering::async_splitter::run(hir)?; 31 | let output = hir.to_string(); 32 | Ok(output) 33 | } 34 | -------------------------------------------------------------------------------- /lib/skc_async_experiment/tests/cps/async.sk: -------------------------------------------------------------------------------- 1 | class App 2 | def self.run -> Int 3 | sleep_sec(1) 4 | 2 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /lib/skc_async_experiment/tests/cps/if_async_then.sk: -------------------------------------------------------------------------------- 1 | class App 2 | def self.run -> Int 3 | if true 4 | sleep_sec(1) 5 | 1 6 | else 7 | 2 8 | end 9 | 3 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/skc_async_experiment/tests/cps/if_sync.sk: -------------------------------------------------------------------------------- 1 | class App 2 | def self.run -> Int 3 | if true 4 | 1 5 | else 6 | 2 7 | end 8 | 3 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/skc_async_experiment/tests/snapshots/cps__cps_transformation@async.sk.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: lib/skc_async_experiment/tests/cps.rs 3 | expression: compile(sk_path).unwrap() 4 | input_file: lib/skc_async_experiment/tests/cps/async.sk 5 | --- 6 | extern([+]) print (Int)->Void; 7 | extern() sleep_sec (ChiikaEnv,Int,(ChiikaEnv,Void)->RustFuture)->RustFuture; 8 | extern([+]) Int#+ (Int,Int)->Int; 9 | extern([+]) Int#- (Int,Int)->Int; 10 | extern([+]) Int#* (Int,Int)->Int; 11 | extern([+]) Int#% (Int,Int)->Int; 12 | extern([+]) Int#and (Int,Int)->Int; 13 | extern([+]) Int#or (Int,Int)->Int; 14 | extern([+]) Int#xor (Int,Int)->Int; 15 | extern([+]) Int#lshift (Int,Int)->Int; 16 | extern([+]) Int#rshift (Int,Int)->Int; 17 | extern([+]) Int#< (Int,Int)->Bool; 18 | extern([+]) Int#<= (Int,Int)->Bool; 19 | extern([+]) Int#> (Int,Int)->Bool; 20 | extern([+]) Int#>= (Int,Int)->Bool; 21 | extern([+]) Int#== (Int,Int)->Bool; 22 | fun run(ChiikaEnv $env, (ChiikaEnv,Int)->RustFuture $cont) -> RustFuture { 23 | chiika_env_push_frame(%arg0, 1) #-> Void 24 | chiika_env_set(%arg0, 0, (%arg1 as Any), 6) #-> Void 25 | return sleep_sec[*](%arg0, 1, run_1) # RustFuture #-> Never 26 | } 27 | fun run_1(ChiikaEnv $env, Void $async_result) -> RustFuture { 28 | %arg1 #-> Void 29 | alloc $0 #-> Void 30 | $0 = 2 #-> Void 31 | return (chiika_env_pop_frame(%arg0, 1) as (ChiikaEnv,Int)->RustFuture)(%arg0, $0) # RustFuture #-> Never 32 | } 33 | -------------------------------------------------------------------------------- /lib/skc_async_experiment/tests/snapshots/cps__cps_transformation@if_async_then.sk.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: lib/skc_async_experiment/tests/cps.rs 3 | expression: compile(sk_path).unwrap() 4 | input_file: lib/skc_async_experiment/tests/cps/if_async_then.sk 5 | --- 6 | extern([+]) print (Int)->Void; 7 | extern() sleep_sec (ChiikaEnv,Int,(ChiikaEnv,Void)->RustFuture)->RustFuture; 8 | extern([+]) Int#+ (Int,Int)->Int; 9 | extern([+]) Int#- (Int,Int)->Int; 10 | extern([+]) Int#* (Int,Int)->Int; 11 | extern([+]) Int#% (Int,Int)->Int; 12 | extern([+]) Int#and (Int,Int)->Int; 13 | extern([+]) Int#or (Int,Int)->Int; 14 | extern([+]) Int#xor (Int,Int)->Int; 15 | extern([+]) Int#lshift (Int,Int)->Int; 16 | extern([+]) Int#rshift (Int,Int)->Int; 17 | extern([+]) Int#< (Int,Int)->Bool; 18 | extern([+]) Int#<= (Int,Int)->Bool; 19 | extern([+]) Int#> (Int,Int)->Bool; 20 | extern([+]) Int#>= (Int,Int)->Bool; 21 | extern([+]) Int#== (Int,Int)->Bool; 22 | fun run(ChiikaEnv $env, (ChiikaEnv,Int)->RustFuture $cont) -> RustFuture { 23 | chiika_env_push_frame(%arg0, 1) #-> Void 24 | chiika_env_set(%arg0, 0, (%arg1 as Any), 6) #-> Void 25 | if true 26 | return run't[*](%arg0) # RustFuture 27 | else 28 | return run'f[*](%arg0) # RustFuture 29 | end #-> Never 30 | } 31 | fun run't(ChiikaEnv $env) -> RustFuture { 32 | return sleep_sec[*](%arg0, 1, run_2) # RustFuture #-> Never 33 | } 34 | fun run_2(ChiikaEnv $env, Void $async_result) -> RustFuture { 35 | %arg1 #-> Void 36 | return run'e(%arg0, 1) # RustFuture #-> Never 37 | } 38 | fun run'f(ChiikaEnv $env) -> RustFuture { 39 | return run'e(%arg0, 2) # RustFuture #-> Never 40 | } 41 | fun run'e(ChiikaEnv $env, Int $ifResult) -> RustFuture { 42 | %arg1 #-> Int 43 | alloc $0 #-> Void 44 | $0 = 3 #-> Void 45 | return (chiika_env_pop_frame(%arg0, 1) as (ChiikaEnv,Int)->RustFuture)(%arg0, $0) # RustFuture #-> Never 46 | } 47 | -------------------------------------------------------------------------------- /lib/skc_async_experiment/tests/snapshots/cps__cps_transformation@if_sync.sk.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: lib/skc_async_experiment/tests/cps.rs 3 | expression: compile(sk_path).unwrap() 4 | input_file: lib/skc_async_experiment/tests/cps/if_sync.sk 5 | --- 6 | extern([+]) print (Int)->Void; 7 | extern() sleep_sec (ChiikaEnv,Int,(ChiikaEnv,Void)->RustFuture)->RustFuture; 8 | extern([+]) Int#+ (Int,Int)->Int; 9 | extern([+]) Int#- (Int,Int)->Int; 10 | extern([+]) Int#* (Int,Int)->Int; 11 | extern([+]) Int#% (Int,Int)->Int; 12 | extern([+]) Int#and (Int,Int)->Int; 13 | extern([+]) Int#or (Int,Int)->Int; 14 | extern([+]) Int#xor (Int,Int)->Int; 15 | extern([+]) Int#lshift (Int,Int)->Int; 16 | extern([+]) Int#rshift (Int,Int)->Int; 17 | extern([+]) Int#< (Int,Int)->Bool; 18 | extern([+]) Int#<= (Int,Int)->Bool; 19 | extern([+]) Int#> (Int,Int)->Bool; 20 | extern([+]) Int#>= (Int,Int)->Bool; 21 | extern([+]) Int#== (Int,Int)->Bool; 22 | fun run() -> Int { 23 | if true 24 | 1 #-> Int 25 | else 26 | 2 #-> Int 27 | end #-> Int 28 | return 3 # Int #-> Never 29 | } 30 | -------------------------------------------------------------------------------- /lib/skc_codegen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "skc_codegen" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | shiika_core = { path = "../shiika_core" } 8 | shiika_ast = { path = "../shiika_ast" } 9 | shiika_ffi = { path = "../shiika_ffi" } 10 | skc_corelib = { path = "../skc_corelib" } 11 | skc_error = { path = "../skc_error" } 12 | skc_hir = { path = "../skc_hir" } 13 | skc_mir = { path = "../skc_mir" } 14 | anyhow = "1.0" 15 | serde = { version = "1.0.125", features = ["derive"] } 16 | serde_json = "1.0" 17 | inkwell = { git = "https://github.com/TheDan64/inkwell", features = ["llvm16-0"], rev = "4030f76" } 18 | either = "1.5.3" 19 | log = "0.4.11" 20 | -------------------------------------------------------------------------------- /lib/skc_corelib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "skc_corelib" 3 | version = "0.1.0" 4 | authors = ["Yutaka HARA "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | shiika_core = { path = "../shiika_core" } 9 | shiika_ast = { path = "../shiika_ast" } 10 | shiika_parser = { path = "../shiika_parser" } 11 | skc_hir = { path = "../skc_hir" } 12 | anyhow = "1.0" 13 | serde = { version = "1.0.125", features = ["derive"] } 14 | json5 = "0.2.8" 15 | -------------------------------------------------------------------------------- /lib/skc_corelib/src/class.rs: -------------------------------------------------------------------------------- 1 | //! The class `Class`. 2 | //! Instances of this class are class objects. 3 | use shiika_core::ty; 4 | use skc_hir::SkIVar; 5 | use std::collections::HashMap; 6 | 7 | pub const N_IVARS: usize = 2; 8 | pub const IVAR_NAME_IDX: usize = 0; 9 | 10 | pub fn ivars() -> HashMap { 11 | let mut ivars = HashMap::new(); 12 | ivars.insert( 13 | "@name".to_string(), 14 | SkIVar { 15 | name: "@name".to_string(), 16 | idx: 0, 17 | ty: ty::raw("String"), 18 | readonly: true, 19 | }, 20 | ); 21 | ivars.insert( 22 | "@specialized_classes".to_string(), 23 | SkIVar { 24 | name: "@specialized_classes".to_string(), 25 | idx: 1, 26 | ty: ty::raw("Object"), 27 | readonly: true, 28 | }, 29 | ); 30 | ivars.insert( 31 | "@type_args".to_string(), 32 | SkIVar { 33 | name: "@type_args".to_string(), 34 | idx: 2, 35 | ty: ty::raw("Object"), 36 | readonly: true, 37 | }, 38 | ); 39 | ivars.insert( 40 | "@witness_table".to_string(), 41 | SkIVar { 42 | name: "@witness_table".to_string(), 43 | idx: 3, 44 | ty: ty::raw("Object"), 45 | readonly: true, 46 | }, 47 | ); 48 | ivars 49 | } 50 | -------------------------------------------------------------------------------- /lib/skc_corelib/src/fn_x.rs: -------------------------------------------------------------------------------- 1 | use crate::ClassItem; 2 | use shiika_core::ty; 3 | use skc_hir::{SkIVar, Supertype}; 4 | use std::collections::HashMap; 5 | 6 | pub const IVAR_FUNC_IDX: usize = 0; 7 | pub const IVAR_THE_SELF_IDX: usize = 1; 8 | pub const IVAR_CAPTURES_IDX: usize = 2; 9 | pub const IVAR_EXIT_STATUS_IDX: usize = 3; 10 | 11 | macro_rules! fn_item { 12 | ($i:expr) => {{ 13 | let mut typarams = (1..=$i).map(|i| format!("S{}", i)).collect::>(); 14 | typarams.push("T".to_string()); 15 | 16 | ( 17 | format!("Fn{}", $i), 18 | Some(Supertype::simple("Fn")), 19 | ivars(), 20 | typarams, 21 | ) 22 | }}; 23 | } 24 | 25 | fn ivars() -> HashMap { 26 | let mut ivars = HashMap::new(); 27 | ivars.insert( 28 | "@func".to_string(), 29 | SkIVar { 30 | name: "@func".to_string(), 31 | idx: 0, 32 | ty: ty::raw("Shiika::Internal::Ptr"), 33 | readonly: true, 34 | }, 35 | ); 36 | ivars.insert( 37 | "@the_self".to_string(), 38 | SkIVar { 39 | name: "@the_self".to_string(), 40 | idx: 1, 41 | ty: ty::raw("Object"), 42 | readonly: true, 43 | }, 44 | ); 45 | ivars.insert( 46 | "@captures".to_string(), 47 | SkIVar { 48 | name: "@captures".to_string(), 49 | idx: 2, 50 | ty: ty::raw("Shiika::Internal::Ptr"), 51 | readonly: true, 52 | }, 53 | ); 54 | ivars.insert( 55 | "@exit_status".to_string(), 56 | SkIVar { 57 | name: "@exit_status".to_string(), 58 | idx: 3, 59 | ty: ty::raw("Int"), 60 | readonly: false, 61 | }, 62 | ); 63 | ivars 64 | } 65 | 66 | #[allow(clippy::reversed_empty_ranges)] 67 | pub fn fn_items() -> Vec { 68 | vec![ 69 | fn_item!(0), 70 | fn_item!(1), 71 | fn_item!(2), 72 | fn_item!(3), 73 | fn_item!(4), 74 | fn_item!(5), 75 | fn_item!(6), 76 | fn_item!(7), 77 | fn_item!(8), 78 | fn_item!(9), 79 | ] 80 | } 81 | -------------------------------------------------------------------------------- /lib/skc_corelib/src/rustlib_methods.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | use json5; 3 | use shiika_ast::AstMethodSignature; 4 | use shiika_core::names::{class_fullname, ClassFullname}; 5 | use shiika_parser::Parser; 6 | use std::fs; 7 | use std::io::Read; 8 | 9 | /// Returns signatures of corelib methods implemented in Rust 10 | pub fn provided_methods() -> Vec<(ClassFullname, AstMethodSignature)> { 11 | load_methods_json() 12 | .unwrap() 13 | .iter() 14 | .map(parse_signature) 15 | .collect() 16 | } 17 | 18 | // Read provided_methods.json 19 | fn load_methods_json() -> Result> { 20 | let mut f = fs::File::open("lib/skc_rustlib/provided_methods.json5") 21 | .context("./lib/skc_rustlib/provided_methods.json5 not found")?; 22 | let mut contents = String::new(); 23 | f.read_to_string(&mut contents) 24 | .context("failed to read provided_methods.json5")?; 25 | json5::from_str(&contents).context("provided_methods.json5 is broken") 26 | } 27 | 28 | // Parse signature into AstMethodSignature 29 | fn parse_signature(item: &(String, String)) -> (ClassFullname, AstMethodSignature) { 30 | let (classname, sig_str) = item; 31 | let ast_sig = Parser::parse_signature(sig_str).unwrap(); 32 | (class_fullname(classname), ast_sig) 33 | } 34 | -------------------------------------------------------------------------------- /lib/skc_error/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "skc_error" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | shiika_ast = { path = "../shiika_ast" } 8 | ariadne = { version = "0.3.0", features = ["auto-color"] } 9 | -------------------------------------------------------------------------------- /lib/skc_error/README.md: -------------------------------------------------------------------------------- 1 | # skc_error 2 | 3 | Provides `skc_error::build_report`, a helper to build an error report with the [ariadne](https://crates.io/crates/ariadne) crate. 4 | -------------------------------------------------------------------------------- /lib/skc_error/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod report_builder; 2 | pub use ariadne::Label; 3 | use ariadne::{Report, ReportBuilder, ReportKind, Source}; 4 | use report_builder::Builder; 5 | use shiika_ast::LocationSpan; 6 | use std::fs; 7 | use std::ops::Range; 8 | 9 | type AriadneSpan<'a> = (&'a String, Range); 10 | 11 | /// Helper for building report with ariadne crate. 12 | /// (TODO: migrate to `report_builder`) 13 | pub fn build_report(main_msg: String, locs: &LocationSpan, f: F) -> String 14 | where 15 | F: for<'b> FnOnce( 16 | ReportBuilder<'b, AriadneSpan<'b>>, 17 | AriadneSpan<'b>, 18 | ) -> ReportBuilder<'b, AriadneSpan<'b>>, 19 | { 20 | if let LocationSpan::Just { 21 | filepath, 22 | begin, 23 | end, 24 | } = locs 25 | { 26 | // ariadne::Id for the file `locs.filepath` 27 | // ariadne 0.1.5 needs Id: Display (zesterer/ariadne#12) 28 | let id = format!("{}", filepath.display()); 29 | // ariadne::Span equivalent to `locs` 30 | let locs_span = (&id, begin.pos..end.pos); 31 | 32 | let src = Source::from(fs::read_to_string(&**filepath).unwrap_or_default()); 33 | let report = f(Report::build(ReportKind::Error, &id, begin.pos), locs_span) 34 | .with_message(main_msg.clone()) 35 | .finish(); 36 | 37 | match std::panic::catch_unwind(|| { 38 | let mut rendered = vec![]; 39 | report.write((&id, src), &mut rendered).unwrap(); 40 | String::from_utf8_lossy(&rendered).to_string() 41 | }) { 42 | Ok(u8str) => u8str, 43 | Err(e) => { 44 | println!("[BUG] ariadne crate crashed!"); 45 | dbg!(&e); 46 | main_msg 47 | } 48 | } 49 | } else { 50 | // No location information available 51 | main_msg 52 | } 53 | } 54 | 55 | /// Helper for building report with ariadne crate. 56 | pub fn report_builder() -> crate::report_builder::Builder { 57 | Builder::new() 58 | } 59 | -------------------------------------------------------------------------------- /lib/skc_error/src/report_builder.rs: -------------------------------------------------------------------------------- 1 | use ariadne::{Label, Report, ReportBuilder, ReportKind, Source}; 2 | use shiika_ast::LocationSpan; 3 | use std::fs; 4 | 5 | type AriadneSpan<'a> = (&'a String, std::ops::Range); 6 | 7 | pub struct Builder { 8 | annotations: Vec<(LocationSpan, String)>, 9 | } 10 | 11 | impl Builder { 12 | pub fn new() -> Builder { 13 | Builder { 14 | annotations: vec![], 15 | } 16 | } 17 | 18 | pub fn annotate(mut self, locs: LocationSpan, msg: String) -> Self { 19 | self.annotations.push((locs, msg)); 20 | self 21 | } 22 | 23 | pub fn build(self, main_msg: String, main_locs: &LocationSpan) -> String { 24 | if let LocationSpan::Just { 25 | filepath, begin, .. 26 | } = main_locs 27 | { 28 | // ariadne::Id for the file `locs.filepath` 29 | // ariadne 0.1.5 needs Id: Display (zesterer/ariadne#12) 30 | let id = format!("{}", filepath.display()); 31 | 32 | let src = Source::from(fs::read_to_string(&**filepath).unwrap_or_default()); 33 | let mut r: ReportBuilder = 34 | Report::build(ReportKind::Error, &id, begin.pos); 35 | for (locs, msg) in self.annotations { 36 | let LocationSpan::Just { begin, end, .. } = locs else { 37 | panic!("got LocationSpan::None"); 38 | }; 39 | let locs_span = (&id, begin.pos..end.pos); 40 | r.add_label(Label::new(locs_span).with_message(msg)); 41 | } 42 | let report = r.with_message(main_msg.clone()).finish(); 43 | 44 | match std::panic::catch_unwind(|| { 45 | let mut rendered = vec![]; 46 | report.write((&id, src), &mut rendered).unwrap(); 47 | String::from_utf8_lossy(&rendered).to_string() 48 | }) { 49 | Ok(u8str) => u8str, 50 | Err(e) => { 51 | println!("[BUG] ariadne crate crashed!"); 52 | dbg!(&e); 53 | main_msg 54 | } 55 | } 56 | } else { 57 | // No location information available 58 | main_msg 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/skc_hir/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "skc_hir" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | shiika_core = { path = "../shiika_core" } 8 | shiika_ast = { path = "../shiika_ast" } 9 | anyhow = "1.0" 10 | serde = { version = "1.0.125", features = ["derive"] } 11 | serde_json = "1.0" 12 | nom = "7.1.3" 13 | either = "1.5.3" 14 | log = "0.4.11" 15 | -------------------------------------------------------------------------------- /lib/skc_hir/src/pattern_match.rs: -------------------------------------------------------------------------------- 1 | use crate::{HirExpression, HirLVars}; 2 | 3 | #[derive(Debug, Clone)] 4 | pub enum Component { 5 | /// A boolean expression that is a part of match condition 6 | Test(HirExpression), 7 | /// A local variable binding introduced by match 8 | Bind(String, HirExpression), 9 | } 10 | 11 | #[derive(Debug, Clone)] 12 | pub struct MatchClause { 13 | pub components: Vec, 14 | pub body_hir: HirExpression, 15 | /// Local variables declared in this clause 16 | pub lvars: HirLVars, 17 | } 18 | -------------------------------------------------------------------------------- /lib/skc_hir/src/signatures.rs: -------------------------------------------------------------------------------- 1 | use super::signature::MethodSignature; 2 | use serde::{Deserialize, Serialize}; 3 | use shiika_core::names::MethodFirstname; 4 | use std::collections::HashMap; 5 | 6 | /// A method list like an ordered map. 7 | #[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)] 8 | pub struct MethodSignatures(HashMap); 9 | 10 | impl MethodSignatures { 11 | pub fn new() -> MethodSignatures { 12 | Default::default() 13 | } 14 | 15 | pub fn from_iterator(iter: impl Iterator) -> MethodSignatures { 16 | let mut ss = MethodSignatures::new(); 17 | iter.for_each(|s| ss.insert(s)); 18 | ss 19 | } 20 | 21 | /// Returns the signature, if any 22 | pub fn get(&self, name: &MethodFirstname) -> Option<&(MethodSignature, usize)> { 23 | self.0.get(name) 24 | } 25 | 26 | /// Returns if the name is contained 27 | pub fn contains_key(&self, name: &MethodFirstname) -> bool { 28 | self.0.contains_key(name) 29 | } 30 | 31 | /// Insert a signature as the "last" element. 32 | pub fn insert(&mut self, sig: MethodSignature) { 33 | let n = self.0.len(); 34 | let key = sig.fullname.first_name.clone(); 35 | self.0.insert(key, (sig, n)); 36 | } 37 | 38 | /// Destructively append `other` to `self`. 39 | pub fn append(&mut self, other: MethodSignatures) { 40 | other 41 | .into_ordered() 42 | .into_iter() 43 | .for_each(|(s, _)| self.insert(s)); 44 | } 45 | 46 | /// Destructively append `other` to `self`. 47 | pub fn append_vec(&mut self, other: Vec) { 48 | other.into_iter().for_each(|s| self.insert(s)); 49 | } 50 | 51 | /// Returns list of signatures in the order. 52 | fn into_ordered(self) -> Vec<(MethodSignature, usize)> { 53 | let mut v = self.0.into_values().collect::>(); 54 | // This is stable because n is unique. 55 | v.sort_unstable_by_key(|(_, n)| *n); 56 | v 57 | } 58 | 59 | /// Returns list of signatures in the order. 60 | pub fn to_ordered(&self) -> Vec<&(MethodSignature, usize)> { 61 | let mut v = self.0.values().collect::>(); 62 | // This is stable because n is unique. 63 | v.sort_unstable_by_key(|(_, n)| n); 64 | v 65 | } 66 | 67 | /// Returns iterator over signatures (not ordered.) 68 | pub fn unordered_iter(&self) -> impl Iterator { 69 | self.0.values() 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /lib/skc_hir/src/sk_method.rs: -------------------------------------------------------------------------------- 1 | use crate::signature::MethodSignature; 2 | use crate::{HirExpression, HirLVars}; 3 | use shiika_core::names::*; 4 | use shiika_core::ty::TermTy; 5 | use std::collections::HashMap; 6 | 7 | #[derive(Debug)] 8 | pub struct SkMethod { 9 | pub signature: MethodSignature, 10 | pub body: SkMethodBody, 11 | pub lvars: HirLVars, 12 | } 13 | 14 | pub type SkMethods = HashMap>; 15 | 16 | #[derive(Debug)] 17 | pub enum SkMethodBody { 18 | /// A method defined with Shiika expressions 19 | Normal { exprs: HirExpression }, 20 | /// A method defined in skc_rustlib 21 | RustLib, 22 | /// The method .new 23 | New { 24 | classname: ClassFullname, 25 | initialize_name: MethodFullname, 26 | init_cls_name: ClassFullname, 27 | arity: usize, 28 | const_is_obj: bool, 29 | }, 30 | /// A method that just return the value of `idx`th ivar 31 | Getter { 32 | idx: usize, 33 | name: String, 34 | ty: TermTy, 35 | self_ty: TermTy, 36 | }, 37 | /// A method that just update the value of `idx`th ivar 38 | Setter { 39 | idx: usize, 40 | name: String, 41 | ty: TermTy, 42 | self_ty: TermTy, 43 | }, 44 | } 45 | 46 | impl SkMethod { 47 | /// Create a SkMethod which does not use lvar at all. 48 | pub fn simple(signature: MethodSignature, body: SkMethodBody) -> SkMethod { 49 | SkMethod { 50 | signature, 51 | body, 52 | lvars: Default::default(), 53 | } 54 | } 55 | 56 | /// Returns if this method is defined by skc_rustlib 57 | pub fn is_rustlib(&self) -> bool { 58 | matches!(&self.body, SkMethodBody::RustLib) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /lib/skc_hir/src/sk_type/sk_module.rs: -------------------------------------------------------------------------------- 1 | use super::SkTypeBase; 2 | use crate::signature::MethodSignature; 3 | use serde::{Deserialize, Serialize}; 4 | use shiika_core::names::ModuleFullname; 5 | 6 | #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] 7 | pub struct SkModule { 8 | pub base: SkTypeBase, 9 | pub requirements: Vec, 10 | } 11 | 12 | impl SkModule { 13 | /// Creates new `SkModule`. Also inserts `requirements` into `method_sigs` 14 | pub fn new(mut base: SkTypeBase, requirements: Vec) -> SkModule { 15 | requirements 16 | .iter() 17 | .for_each(|sig| base.method_sigs.insert(sig.clone())); 18 | SkModule { base, requirements } 19 | } 20 | 21 | pub fn fullname(&self) -> ModuleFullname { 22 | self.base.erasure.to_module_fullname() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/skc_hir/src/sk_type/sk_type_base.rs: -------------------------------------------------------------------------------- 1 | use crate::signatures::MethodSignatures; 2 | use serde::{Deserialize, Serialize}; 3 | use shiika_core::names::*; 4 | use shiika_core::ty::{self, *}; 5 | 6 | #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] 7 | pub struct SkTypeBase { 8 | pub erasure: Erasure, 9 | pub typarams: Vec, 10 | pub method_sigs: MethodSignatures, 11 | /// true if this class is an imported one 12 | // TODO: is this used now? 13 | pub foreign: bool, 14 | } 15 | 16 | impl SkTypeBase { 17 | pub fn fullname(&self) -> TypeFullname { 18 | self.erasure.to_type_fullname() 19 | } 20 | 21 | pub fn term_ty(&self) -> TermTy { 22 | let type_args = ty::typarams_to_tyargs(&self.typarams); 23 | self.erasure.to_term_ty().specialized_ty(type_args) 24 | } 25 | 26 | // TODO: remove this 27 | pub fn fullname_(&self) -> ClassFullname { 28 | self.erasure.to_class_fullname() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/skc_hir/src/sk_type/wtable.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use shiika_core::names::*; 3 | use std::collections::HashMap; 4 | 5 | /// Witness table of a Shiika class. Mapping from every Shiika module 6 | /// which the class includes to the list of MethodFullname which are 7 | /// the actual implementation of the methods of the module. 8 | /// 9 | /// eg. for the class `Array` which includes `Enumerable`, WTable will 10 | /// look like this. 11 | /// {"Enumerable" => ["Enumerable#all?", "Array#each", ...]} 12 | #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Default)] 13 | pub struct WTable(pub HashMap>); 14 | 15 | impl WTable { 16 | pub fn new(h: HashMap>) -> WTable { 17 | WTable(h) 18 | } 19 | 20 | // Returns empty wtable. 21 | pub fn default() -> WTable { 22 | WTable(Default::default()) 23 | } 24 | 25 | pub fn is_empty(&self) -> bool { 26 | self.0.is_empty() 27 | } 28 | 29 | pub fn get_len(&self, key: &ModuleFullname) -> usize { 30 | self.0.get(key).unwrap().len() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/skc_hir/src/supertype.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use shiika_core::{names::*, ty::*}; 3 | 4 | /// Represents supertype i.e. ancestor class of a class or included module of 5 | /// a class. 6 | /// Note that superclass can have type parameters eg. 7 | /// `class Foo : Pair>` 8 | #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] 9 | pub struct Supertype(LitTy); 10 | 11 | impl Supertype { 12 | /// Create a `Supertype` 13 | pub fn from_ty(t: LitTy) -> Supertype { 14 | Supertype(t) 15 | } 16 | 17 | /// Shortcut from a class name 18 | pub fn simple(s: &str) -> Supertype { 19 | Supertype::from_ty(LitTy::raw(s)) 20 | } 21 | 22 | /// Default superclass (= Object) 23 | pub fn default() -> Supertype { 24 | Supertype::simple("Object") 25 | } 26 | 27 | pub fn ty(&self) -> &LitTy { 28 | &self.0 29 | } 30 | 31 | pub fn to_term_ty(&self) -> TermTy { 32 | self.0.to_term_ty() 33 | } 34 | 35 | pub fn type_args(&self) -> &[TermTy] { 36 | &self.0.type_args 37 | } 38 | 39 | pub fn erasure(&self) -> Erasure { 40 | self.0.erasure() 41 | } 42 | 43 | pub fn base_fullname(&self) -> ClassFullname { 44 | self.0.erasure().to_class_fullname() 45 | } 46 | 47 | /// Create concrete superclass of a generic class 48 | pub fn substitute(&self, tyargs: &[TermTy]) -> Supertype { 49 | let t = self.0.substitute(tyargs, Default::default()); 50 | Supertype::from_ty(t) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/skc_language_server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "skc_language_server" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | tower-lsp = "0.19.0" 8 | async-channel = "1.8" 9 | tokio = { version = "1.43.1", features = ["full"] } 10 | -------------------------------------------------------------------------------- /lib/skc_language_server/README.md: -------------------------------------------------------------------------------- 1 | # skc_language_server 2 | 3 | An implementation of LSP(Language Server Protocol) for Shiika. 4 | 5 | ## State 6 | 7 | Just a prototype; has no actual feature now. 8 | 9 | Also, this is a bin-crate provides an executable but eventually this 10 | should be a lib-crate that implements `shiika language-server` command. 11 | 12 | ## File structures 13 | 14 | - backend.rs 15 | - `Backend::initialize` returns server capabilities to the editor. 16 | - The rest of the methods forwards messages to `crate::server::Server` asynchronously. 17 | - server.rs 18 | - The body of the language server. 19 | 20 | ## Acknowledgement 21 | 22 | The basic part of this crate is based on https://github.com/dalance/veryl/tree/master/crates/languageserver . Thank you! 23 | -------------------------------------------------------------------------------- /lib/skc_language_server/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "skc_language_server", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": {} 6 | } 7 | -------------------------------------------------------------------------------- /lib/skc_language_server/src/main.rs: -------------------------------------------------------------------------------- 1 | use tower_lsp::{LspService, Server}; 2 | 3 | mod backend; 4 | mod server; 5 | use backend::Backend; 6 | 7 | #[tokio::main] 8 | async fn main() { 9 | let stdin = tokio::io::stdin(); 10 | let stdout = tokio::io::stdout(); 11 | 12 | let (service, socket) = LspService::new(Backend::new); 13 | Server::new(stdin, stdout, socket).serve(service).await; 14 | } 15 | -------------------------------------------------------------------------------- /lib/skc_mir/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "skc_mir" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | shiika_core = { path = "../shiika_core" } 8 | skc_hir = { path = "../skc_hir" } 9 | serde = { version = "1.0.125", features = ["derive"] } 10 | -------------------------------------------------------------------------------- /lib/skc_mir/README.md: -------------------------------------------------------------------------------- 1 | # skc_mir 2 | 3 | This crate provides `struct Mir` and `Mir::build`. 4 | 5 | ## `Mir` 6 | 7 | `Hir` represents high-level semantics of a Shiika program but not suitable 8 | for generating LLVM code directly. 9 | `Mir::build` takes a `Hir` and generate various information needed for 10 | LLVM IR generation. 11 | 12 | ## `mir::VTable` 13 | 14 | Vtables are used to implement virtual function call. While languages such as 15 | C++ or C# has the keyword `virtual`, it is not a keyword in Shiika and 16 | all the method calls are invoked via vtable. 17 | -------------------------------------------------------------------------------- /lib/skc_mir/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod library; 2 | mod vtable; 3 | mod vtables; 4 | pub use crate::library::LibraryExports; 5 | pub use crate::vtable::VTable; 6 | pub use crate::vtables::VTables; 7 | use skc_hir::Hir; 8 | 9 | #[derive(Debug)] 10 | pub struct Mir { 11 | pub hir: Hir, 12 | pub vtables: VTables, 13 | pub imports: LibraryExports, 14 | } 15 | 16 | pub fn build(hir: Hir, imports: LibraryExports) -> Mir { 17 | let vtables = VTables::build(&hir.sk_types, &imports); 18 | Mir { 19 | hir, 20 | vtables, 21 | imports, 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/skc_mir/src/library.rs: -------------------------------------------------------------------------------- 1 | use crate::{Mir, VTables}; 2 | use serde::{Deserialize, Serialize}; 3 | use shiika_core::{names::ConstFullname, ty::TermTy}; 4 | use skc_hir::SkTypes; 5 | use std::collections::HashMap; 6 | 7 | #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Default)] 8 | pub struct LibraryExports { 9 | pub sk_types: SkTypes, 10 | pub vtables: VTables, 11 | // TODO: This should be Vec because initialize order matters 12 | pub constants: HashMap, 13 | } 14 | 15 | impl LibraryExports { 16 | pub fn new(mir: &Mir) -> LibraryExports { 17 | LibraryExports { 18 | // PERF: how to generate json without cloning? 19 | sk_types: mir.hir.sk_types.clone(), 20 | vtables: mir.vtables.clone(), 21 | constants: mir.hir.constants.clone(), 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/skc_mir/src/vtable.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use shiika_core::names::*; 3 | use skc_hir::SkClass; 4 | use std::collections::HashMap; 5 | 6 | #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)] 7 | pub struct VTable { 8 | /// List of methods, ordered by index 9 | fullnames: Vec, 10 | /// Mapping from firstname to index 11 | index: HashMap, 12 | } 13 | 14 | impl VTable { 15 | /// Create an empty VTable 16 | pub fn null() -> VTable { 17 | VTable { 18 | fullnames: vec![], 19 | index: HashMap::new(), 20 | } 21 | } 22 | 23 | /// Build a VTable of a class 24 | pub fn build(super_vtable: &VTable, class: &SkClass) -> VTable { 25 | let mut vtable = super_vtable.clone(); 26 | // Not needed to be ordered, but this may help debugging 27 | for (sig, _) in class.base.method_sigs.to_ordered() { 28 | if vtable.contains(&sig.fullname.first_name) { 29 | vtable.update(sig.fullname.clone()); 30 | } else { 31 | vtable.push(sig.fullname.clone()); 32 | } 33 | } 34 | vtable 35 | } 36 | 37 | fn contains(&self, name: &MethodFirstname) -> bool { 38 | self.index.contains_key(name) 39 | } 40 | 41 | fn update(&mut self, name: MethodFullname) { 42 | let i = self.index.get(&name.first_name).unwrap(); 43 | let elem = self.fullnames.get_mut(*i).unwrap(); 44 | *elem = name; 45 | } 46 | 47 | fn push(&mut self, name: MethodFullname) { 48 | let i = self.fullnames.len(); 49 | self.index.insert(name.first_name.clone(), i); 50 | self.fullnames.push(name); 51 | } 52 | 53 | /// Returns the size 54 | pub fn size(&self) -> usize { 55 | self.fullnames.len() 56 | } 57 | 58 | /// Returns the index of the method 59 | pub fn get(&self, name: &MethodFirstname) -> Option<&usize> { 60 | self.index.get(name) 61 | } 62 | 63 | /// Returns the list of method names, ordered by the index. 64 | pub fn to_vec(&self) -> &Vec { 65 | &self.fullnames 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/skc_rustlib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "skc_rustlib" 3 | version = "0.1.0" 4 | authors = ["Yutaka HARA "] 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["staticlib"] 9 | 10 | [dependencies] 11 | shiika_ffi_macro = { path = "../shiika_ffi_macro" } 12 | bdwgc-alloc = { version = "0.6.5", default-features=false, features = ["cmake"] } 13 | plain = "0.2.3" 14 | # For Random 15 | rand = "0.8.5" 16 | # For String#chars 17 | unicode-segmentation = "1.7.1" 18 | # For Time 19 | chrono = "0.4" 20 | chrono-tz = "0.6" 21 | # For File 22 | libc = "0.2" 23 | -------------------------------------------------------------------------------- /lib/skc_rustlib/README.md: -------------------------------------------------------------------------------- 1 | # lib/skc_rustlib 2 | 3 | This crate provides 4 | 5 | - Runtime functions of Shiika 6 | - Built-in methods that cannot be implemented with pure Shiika 7 | -------------------------------------------------------------------------------- /lib/skc_rustlib/src/allocator.rs: -------------------------------------------------------------------------------- 1 | /// Allocate memory with bdwgc 2 | use bdwgc_alloc::Allocator; 3 | use std::alloc::Layout; 4 | use std::os::raw::c_void; 5 | 6 | #[global_allocator] 7 | static GLOBAL_ALLOCATOR: Allocator = Allocator; 8 | 9 | const DEFAULT_ALIGNMENT: usize = 8; 10 | 11 | #[no_mangle] 12 | pub extern "C" fn shiika_malloc(size: usize) -> *mut c_void { 13 | (unsafe { std::alloc::alloc(Layout::from_size_align(size, DEFAULT_ALIGNMENT).unwrap()) }) 14 | as *mut c_void 15 | } 16 | 17 | #[no_mangle] 18 | pub extern "C" fn shiika_realloc(pointer: *mut c_void, size: usize) -> *mut c_void { 19 | // Layouts are ignored by the bdwgc global allocator. 20 | (unsafe { 21 | std::alloc::realloc( 22 | pointer as *mut u8, 23 | Layout::from_size_align(0, DEFAULT_ALIGNMENT).unwrap(), 24 | size, 25 | ) 26 | }) as *mut c_void 27 | } 28 | -------------------------------------------------------------------------------- /lib/skc_rustlib/src/builtin.rs: -------------------------------------------------------------------------------- 1 | pub mod array; 2 | pub mod bool; 3 | pub mod class; 4 | mod error; 5 | mod file; 6 | pub mod float; 7 | mod random; 8 | mod result; 9 | mod time; 10 | //mod fn_x; 11 | pub mod int; 12 | mod math; 13 | pub mod object; 14 | mod shiika_internal_memory; 15 | pub mod shiika_internal_ptr; 16 | //pub mod shiika_internal_ptr_typed; 17 | pub mod string; 18 | mod void; 19 | pub use self::array::SkAry; 20 | pub use self::bool::SkBool; 21 | pub use self::class::SkClass; 22 | pub use self::error::SkError; 23 | pub use self::float::SkFloat; 24 | pub use self::result::{SkFail, SkOk, SkResult}; 25 | //pub use self::fn_x::SkFn1; 26 | pub use self::int::SkInt; 27 | pub use self::object::SkObj; 28 | pub use self::shiika_internal_ptr::SkPtr; 29 | pub use self::string::SkStr; 30 | pub use self::void::SkVoid; 31 | 32 | /// Get the function pointer from wtable 33 | #[no_mangle] 34 | pub extern "C" fn shiika_lookup_wtable(receiver: SkObj, key: u64, idx: usize) -> *const u8 { 35 | receiver.class().witness_table().get(key, idx) 36 | } 37 | 38 | /// Insert into wtable of the class 39 | #[no_mangle] 40 | pub extern "C" fn shiika_insert_wtable( 41 | mut class: SkClass, 42 | key: u64, 43 | funcs: *const *const u8, 44 | n_funcs: usize, 45 | ) { 46 | class.witness_table_mut().insert(key, funcs, n_funcs) 47 | } 48 | -------------------------------------------------------------------------------- /lib/skc_rustlib/src/builtin/bool.rs: -------------------------------------------------------------------------------- 1 | //! An instance of `Bool`. Interchangable to Rust bool via `into`. 2 | //! 3 | //! # Example 4 | //! 5 | //! ```rust 6 | //! let b = true; 7 | //! let sk_bool: SkBool = b.into(); 8 | //! let rust_bool: bool = sk_bool.into(); 9 | //! ``` 10 | 11 | extern "C" { 12 | fn box_bool(b: bool) -> SkBool; 13 | } 14 | 15 | #[repr(C)] 16 | pub struct SkBool(*const ShiikaBool); 17 | 18 | #[repr(C)] 19 | #[derive(Debug)] 20 | struct ShiikaBool { 21 | vtable: *const u8, 22 | class_obj: *const u8, 23 | value: bool, 24 | } 25 | 26 | impl From for bool { 27 | fn from(sk_bool: SkBool) -> Self { 28 | unsafe { (*sk_bool.0).value } 29 | } 30 | } 31 | 32 | impl From for SkBool { 33 | fn from(b: bool) -> Self { 34 | unsafe { box_bool(b) } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/skc_rustlib/src/builtin/class/witness_table.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | /// Witness table 4 | #[repr(C)] 5 | #[derive(Debug)] 6 | pub struct WitnessTable(HashMap); 7 | 8 | impl WitnessTable { 9 | pub fn new() -> WitnessTable { 10 | WitnessTable(HashMap::new()) 11 | } 12 | 13 | /// key: Unique integer for a Shiika Module 14 | /// funcs: LLVM Array of function pointers 15 | /// len: The length of `funcs` (for safety check) 16 | pub fn insert(&mut self, key: u64, funcs: *const *const u8, len: usize) { 17 | self.0.insert(key, (len, funcs)); 18 | } 19 | 20 | /// Get the function pointer 21 | /// Panics if not found 22 | pub fn get(&self, key: u64, idx: usize) -> *const u8 { 23 | let (len, funcs) = self 24 | .0 25 | .get(&key) 26 | .unwrap_or_else(|| panic!("[BUG] WitnessTable::get: key {} not found", key)); 27 | if idx >= *len { 28 | panic!( 29 | "[BUG] WitnessTable::get: idx({}) is larger than len({})", 30 | idx, len 31 | ); 32 | } 33 | unsafe { 34 | let ptr = funcs.offset(idx as isize); 35 | *ptr 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/skc_rustlib/src/builtin/error.rs: -------------------------------------------------------------------------------- 1 | use crate::builtin::{SkClass, SkStr}; 2 | use shiika_ffi_macro::{shiika_const_ref, shiika_method_ref}; 3 | 4 | shiika_const_ref!("::Error", SkClass, "sk_Error"); 5 | shiika_method_ref!( 6 | "Meta:Error#new", 7 | fn(receiver: SkClass, msg: SkStr) -> SkError, 8 | "meta_error_new" 9 | ); 10 | 11 | #[repr(C)] 12 | pub struct SkError(*const ShiikaError); 13 | 14 | #[repr(C)] 15 | #[derive(Debug)] 16 | struct ShiikaError { 17 | vtable: *const u8, 18 | class_obj: *const u8, 19 | msg: SkStr, 20 | } 21 | 22 | impl SkError { 23 | pub fn new(msg: impl Into) -> SkError { 24 | meta_error_new(sk_Error(), msg.into()) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/skc_rustlib/src/builtin/fn_x.rs: -------------------------------------------------------------------------------- 1 | use crate::builtin::shiika_internal_ptr_typed::SkPtrTyped; 2 | use crate::builtin::{SkAry, SkInt, SkObj}; 3 | 4 | // TODO: implement SkFn0, SkFn2..SkFn9 5 | 6 | #[repr(C)] 7 | pub struct SkFn1(*const ShiikaFn1); 8 | 9 | #[repr(C)] 10 | struct ShiikaFn1 { 11 | vtable: *const u8, 12 | class_obj: *const u8, 13 | func: SkPtrTyped, A) -> R>, 14 | the_self: SkObj, 15 | captures: SkAry<*const u8>, 16 | exit_status: SkInt, 17 | } 18 | 19 | impl SkFn1 { 20 | pub fn call(&self, arg: A) -> R { 21 | unsafe { 22 | let f = (*self.0).func.get(); 23 | f(self.0, arg) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/skc_rustlib/src/builtin/math.rs: -------------------------------------------------------------------------------- 1 | use crate::builtin::SkFloat; 2 | use shiika_ffi_macro::shiika_method; 3 | 4 | #[shiika_method("Meta:Math#sin")] 5 | pub extern "C" fn math_sin(_receiver: *const u8, x: SkFloat) -> SkFloat { 6 | x.val().sin().into() 7 | } 8 | 9 | #[shiika_method("Meta:Math#cos")] 10 | pub extern "C" fn math_cos(_receiver: *const u8, x: SkFloat) -> SkFloat { 11 | x.val().cos().into() 12 | } 13 | 14 | #[shiika_method("Meta:Math#sqrt")] 15 | pub extern "C" fn math_sqrt(_receiver: *const u8, x: SkFloat) -> SkFloat { 16 | x.val().sqrt().into() 17 | } 18 | -------------------------------------------------------------------------------- /lib/skc_rustlib/src/builtin/random.rs: -------------------------------------------------------------------------------- 1 | use crate::builtin::{SkClass, SkFloat, SkInt}; 2 | use rand::prelude::{Rng, SeedableRng, StdRng}; 3 | use shiika_ffi_macro::shiika_method; 4 | use shiika_ffi_macro::{shiika_const_ref, shiika_method_ref}; 5 | 6 | shiika_const_ref!("::Random", SkClass, "sk_Random"); 7 | shiika_method_ref!( 8 | "Meta:Random#new", 9 | fn(receiver: SkClass, seed: SkInt) -> SkRandom, 10 | "meta_random_new" 11 | ); 12 | 13 | #[repr(C)] 14 | #[derive(Debug)] 15 | pub struct SkRandom(*mut ShiikaRandom); 16 | 17 | #[repr(C)] 18 | #[derive(Debug)] 19 | pub struct ShiikaRandom { 20 | vtable: *const u8, 21 | class_obj: *const u8, 22 | rng: *mut StdRng, 23 | } 24 | 25 | impl SkRandom { 26 | /// Returns the rng. 27 | fn rng(&mut self) -> &mut StdRng { 28 | unsafe { (*self.0).rng.as_mut().unwrap() } 29 | } 30 | } 31 | 32 | /// Called from `Random.new` and initializes internal fields. 33 | #[shiika_method("Random#_initialize_rustlib")] 34 | #[allow(non_snake_case)] 35 | pub extern "C" fn random__initialize_rustlib(receiver: SkRandom, seed: SkInt) { 36 | let rng = SeedableRng::seed_from_u64(seed.into()); 37 | unsafe { 38 | (*receiver.0).rng = Box::leak(Box::new(rng)); 39 | } 40 | } 41 | 42 | /// Create an instance of `Random` without explicit seed. 43 | #[shiika_method("Meta:Random#_without_seed")] 44 | #[allow(non_snake_case)] 45 | pub extern "C" fn meta_random__without_seed(_receiver: SkClass) -> SkRandom { 46 | let rnd = meta_random_new(sk_Random(), 0.into()); 47 | // Replace the rng 48 | unsafe { 49 | (*rnd.0).rng = Box::leak(Box::new(SeedableRng::from_entropy())); 50 | } 51 | rnd 52 | } 53 | 54 | /// Returns a random float between 0.0 and 1.0 (end-exclusive). 55 | #[shiika_method("Random#float")] 56 | pub extern "C" fn random_float(mut receiver: SkRandom) -> SkFloat { 57 | receiver.rng().gen::().into() 58 | } 59 | 60 | /// Returns a random integer (end-exclusive). 61 | #[shiika_method("Random#int")] 62 | pub extern "C" fn random_int(mut receiver: SkRandom, from: SkInt, to: SkInt) -> SkInt { 63 | let f: i64 = from.into(); 64 | let t: i64 = to.into(); 65 | receiver.rng().gen_range(f..t).into() 66 | } 67 | -------------------------------------------------------------------------------- /lib/skc_rustlib/src/builtin/result.rs: -------------------------------------------------------------------------------- 1 | use crate::builtin::{SkClass, SkError, SkObj, SkStr, SkVoid}; 2 | use shiika_ffi_macro::{shiika_const_ref, shiika_method_ref}; 3 | use std::mem::ManuallyDrop; 4 | 5 | shiika_const_ref!("::Result::Ok", SkClass, "sk_Ok"); 6 | shiika_const_ref!("::Result::Fail", SkClass, "sk_Fail"); 7 | shiika_method_ref!( 8 | "Meta:Result::Fail#new", 9 | fn(receiver: SkClass, error: SkError) -> SkFail, 10 | "meta_result_fail_new" 11 | ); 12 | shiika_method_ref!( 13 | "Meta:Result::Ok#new", 14 | fn(receiver: SkClass, value: SkObj) -> SkOk, 15 | "meta_result_ok_new" 16 | ); 17 | 18 | #[repr(C)] 19 | pub union SkResult { 20 | pub ok: ManuallyDrop>, 21 | pub fail: ManuallyDrop, 22 | } 23 | #[repr(C)] 24 | #[derive(Debug)] 25 | pub struct SkOk(*const ShiikaOk); 26 | #[repr(C)] 27 | #[derive(Debug)] 28 | pub struct SkFail(*const ShiikaFail); 29 | 30 | impl, E: std::fmt::Display> From> for SkResult { 31 | fn from(x: Result) -> Self { 32 | match x { 33 | Ok(value) => SkResult::ok(value), 34 | Err(e) => SkResult::fail(format!("{}", e)), 35 | } 36 | } 37 | } 38 | 39 | impl From> for SkResult { 40 | fn from(x: Result<(), E>) -> Self { 41 | match x { 42 | Ok(_) => SkResult::ok(().into()), 43 | Err(e) => SkResult::fail(format!("{}", e)), 44 | } 45 | } 46 | } 47 | 48 | impl> SkResult { 49 | pub fn ok(value: T) -> SkResult { 50 | SkResult { 51 | ok: ManuallyDrop::new(SkOk::new(value)), 52 | } 53 | } 54 | 55 | pub fn fail(msg: impl Into) -> SkResult { 56 | SkResult { 57 | fail: ManuallyDrop::new(SkFail::new(msg)), 58 | } 59 | } 60 | } 61 | 62 | #[repr(C)] 63 | #[derive(Debug)] 64 | struct ShiikaOk { 65 | vtable: *const u8, 66 | class_obj: *const u8, 67 | value: T, 68 | } 69 | 70 | impl> SkOk { 71 | pub fn new(value: T) -> SkOk { 72 | let ok_obj = meta_result_ok_new(sk_Ok(), value.into()); 73 | SkOk(ok_obj.0 as *const ShiikaOk) 74 | } 75 | } 76 | 77 | #[repr(C)] 78 | #[derive(Debug)] 79 | struct ShiikaFail { 80 | vtable: *const u8, 81 | class_obj: *const u8, 82 | } 83 | 84 | impl SkFail { 85 | pub fn new(msg: impl Into) -> SkFail { 86 | meta_result_fail_new(sk_Fail(), SkError::new(msg)) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /lib/skc_rustlib/src/builtin/shiika_internal_memory.rs: -------------------------------------------------------------------------------- 1 | //! Provides (unsafe) utilities for memories. 2 | //! 3 | //! Should be removed once `String` is re-implemented in skc_rustlib. 4 | use crate::allocator; 5 | use crate::builtin::int::SkInt; 6 | use crate::builtin::object::SkObj; 7 | use crate::builtin::shiika_internal_ptr::SkPtr; 8 | use shiika_ffi_macro::shiika_method; 9 | use std::convert::TryInto; 10 | use std::os::raw::c_void; 11 | use std::ptr; 12 | 13 | #[shiika_method("Meta:Shiika::Internal::Memory#force_gc")] 14 | pub extern "C" fn memory_force_gc() { 15 | bdwgc_alloc::Allocator::force_collect(); 16 | } 17 | 18 | #[shiika_method("Meta:Shiika::Internal::Memory#memcpy")] 19 | pub extern "C" fn memory_memcpy(_receiver: SkObj, dst: SkPtr, src: SkPtr, n_bytes: SkInt) { 20 | let n: usize = n_bytes.val().try_into().unwrap(); 21 | unsafe { 22 | ptr::copy(src.unbox(), dst.unbox_mut(), n); 23 | } 24 | } 25 | 26 | #[shiika_method("Meta:Shiika::Internal::Memory#gc_malloc")] 27 | pub extern "C" fn memory_gc_malloc(_receiver: SkObj, n_bytes: SkInt) -> SkPtr { 28 | let size = n_bytes.val() as usize; 29 | allocator::shiika_malloc(size).into() 30 | } 31 | 32 | #[shiika_method("Meta:Shiika::Internal::Memory#gc_realloc")] 33 | pub extern "C" fn memory_gc_realloc(_receiver: SkObj, ptr: SkPtr, n_bytes: SkInt) -> SkPtr { 34 | let p = ptr.unbox_mut() as *mut c_void; 35 | let size = n_bytes.val() as usize; 36 | allocator::shiika_realloc(p, size).into() 37 | } 38 | -------------------------------------------------------------------------------- /lib/skc_rustlib/src/builtin/shiika_internal_ptr_typed.rs: -------------------------------------------------------------------------------- 1 | //! Instance of `::Shiika::Internal::Ptr` but typed. 2 | //! 3 | //! Should be removed once `Array`, etc. is re-implemented in skc_rustlib. 4 | use std::marker::PhantomData; 5 | 6 | #[repr(C)] 7 | #[derive(Debug)] 8 | pub struct SkPtrTyped(*const ShiikaPointerTyped); 9 | 10 | #[repr(C)] 11 | #[derive(Debug)] 12 | struct ShiikaPointerTyped { 13 | vtable: *const u8, 14 | class_obj: *const u8, 15 | /// The wrapped pointer 16 | value: T, 17 | /// Type marker 18 | _marker: PhantomData, 19 | } 20 | 21 | impl SkPtrTyped { 22 | pub fn get(&self) -> &T { 23 | unsafe { &(*self.0).value } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/skc_rustlib/src/builtin/time/rs_zone.rs: -------------------------------------------------------------------------------- 1 | pub enum RsZone { 2 | Utc, 3 | Local, 4 | } 5 | -------------------------------------------------------------------------------- /lib/skc_rustlib/src/builtin/time/sk_instant.rs: -------------------------------------------------------------------------------- 1 | use crate::builtin::SkInt; 2 | 3 | #[repr(C)] 4 | #[derive(Debug)] 5 | pub struct SkInstant(*mut ShiikaInstant); 6 | 7 | #[repr(C)] 8 | #[derive(Debug)] 9 | struct ShiikaInstant { 10 | vtable: *const u8, 11 | class_obj: *const u8, 12 | nano_timestamp: SkInt, 13 | } 14 | 15 | impl SkInstant { 16 | pub fn nano_timestamp(&self) -> i64 { 17 | unsafe { (*self.0).nano_timestamp.val() } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/skc_rustlib/src/builtin/time/sk_plain.rs: -------------------------------------------------------------------------------- 1 | #[repr(C)] 2 | #[derive(Debug)] 3 | pub struct SkPlainDateTime(*mut ShiikaPlainDateTime); 4 | #[repr(C)] 5 | #[derive(Debug)] 6 | struct ShiikaPlainDateTime { 7 | vtable: *const u8, 8 | class_obj: *const u8, 9 | } 10 | 11 | #[repr(C)] 12 | #[derive(Debug)] 13 | pub struct SkPlainDate(*mut ShiikaPlainDate); 14 | #[repr(C)] 15 | #[derive(Debug)] 16 | struct ShiikaPlainDate { 17 | vtable: *const u8, 18 | class_obj: *const u8, 19 | } 20 | 21 | #[repr(C)] 22 | #[derive(Debug)] 23 | pub struct SkPlainTime(*mut ShiikaPlainTime); 24 | #[repr(C)] 25 | #[derive(Debug)] 26 | struct ShiikaPlainTime { 27 | vtable: *const u8, 28 | class_obj: *const u8, 29 | } 30 | -------------------------------------------------------------------------------- /lib/skc_rustlib/src/builtin/time/sk_time.rs: -------------------------------------------------------------------------------- 1 | use crate::builtin::time::rs_zone::RsZone; 2 | use crate::builtin::time::sk_instant::SkInstant; 3 | use crate::builtin::time::SkZone; 4 | 5 | #[repr(C)] 6 | #[derive(Debug)] 7 | pub struct SkTime(*mut ShiikaTime); 8 | 9 | #[repr(C)] 10 | #[derive(Debug)] 11 | struct ShiikaTime { 12 | vtable: *const u8, 13 | class_obj: *const u8, 14 | instant: SkInstant, 15 | zone: SkZone, 16 | } 17 | 18 | impl SkTime { 19 | pub fn epoch(&self) -> i64 { 20 | let sk_instant = unsafe { &(*self.0).instant }; 21 | sk_instant.nano_timestamp() 22 | } 23 | 24 | pub fn zone(&self) -> RsZone { 25 | unsafe { (*self.0).zone.to_rs_zone() } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/skc_rustlib/src/builtin/time/sk_zone.rs: -------------------------------------------------------------------------------- 1 | use crate::builtin::time::rs_zone::RsZone; 2 | use crate::builtin::SkObj; 3 | 4 | #[repr(C)] 5 | #[derive(Debug)] 6 | pub struct SkZone(*mut ShiikaZone); 7 | 8 | extern "C" { 9 | #[allow(improper_ctypes)] 10 | static shiika_const_Time_Zone_Utc: SkObj; 11 | #[allow(improper_ctypes)] 12 | static shiika_const_Time_Zone_Local: SkObj; 13 | } 14 | 15 | #[repr(C)] 16 | #[derive(Debug)] 17 | struct ShiikaZone { 18 | vtable: *const u8, 19 | class_obj: SkObj, 20 | } 21 | 22 | impl SkZone { 23 | // Maybe there should be a macro to do this conversion. 24 | pub fn to_rs_zone(&self) -> RsZone { 25 | unsafe { 26 | if shiika_const_Time_Zone_Utc.same_object(self.0) { 27 | RsZone::Utc 28 | } else if shiika_const_Time_Zone_Local.same_object(self.0) { 29 | RsZone::Local 30 | } else { 31 | panic!("SkZone::to_rs_zone failed"); 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/skc_rustlib/src/builtin/void.rs: -------------------------------------------------------------------------------- 1 | use crate::builtin::object::ShiikaObject; 2 | use crate::builtin::SkObj; 3 | use shiika_ffi_macro::shiika_const_ref; 4 | 5 | shiika_const_ref!("::Void", SkVoid, "sk_Void"); 6 | 7 | #[repr(C)] 8 | #[derive(Debug)] 9 | pub struct SkVoid(*const ShiikaVoid); 10 | 11 | impl From<()> for SkVoid { 12 | fn from(_: ()) -> Self { 13 | sk_Void() 14 | } 15 | } 16 | 17 | impl From for SkObj { 18 | fn from(s: SkVoid) -> SkObj { 19 | SkObj::new(s.0 as *const ShiikaObject) 20 | } 21 | } 22 | 23 | impl SkVoid { 24 | pub fn dup(&self) -> SkVoid { 25 | SkVoid(self.0) 26 | } 27 | } 28 | 29 | #[repr(C)] 30 | #[derive(Debug)] 31 | pub struct ShiikaVoid { 32 | vtable: *const u8, 33 | class_obj: *const u8, 34 | } 35 | -------------------------------------------------------------------------------- /lib/skc_rustlib/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod allocator; 2 | mod builtin; 3 | mod sk_cls; 4 | mod sk_methods; 5 | -------------------------------------------------------------------------------- /lib/skc_rustlib/src/sk_cls.rs: -------------------------------------------------------------------------------- 1 | use crate::builtin::SkClass; 2 | 3 | pub trait SkCls { 4 | fn get_class_object() -> SkClass; 5 | } 6 | -------------------------------------------------------------------------------- /lib/skc_rustlib/src/sk_methods.rs: -------------------------------------------------------------------------------- 1 | //! This module provides Rust bindings for llvm functions for Shiika methods. 2 | //! 3 | use crate::builtin::{SkAry, SkClass, SkObj}; 4 | use shiika_ffi_macro::shiika_method_ref; 5 | 6 | // This macro call expands into: 7 | // 8 | // extern "C" { 9 | // #[allow(improper_ctypes)] 10 | // fn Meta_Array_new(receiver: *const u8) -> SkAry; 11 | // } 12 | // pub fn meta_array_new(receiver: *const u8) -> SkAry { 13 | // unsafe { Meta_Array_new(receiver) } 14 | // } 15 | shiika_method_ref!( 16 | "Meta:Array#new", 17 | fn(receiver: *const u8) -> SkAry, 18 | "meta_array_new" 19 | ); 20 | 21 | shiika_method_ref!( 22 | "Meta:Class#new", 23 | fn(receiver: *const u8) -> SkClass, 24 | "meta_class_new" 25 | ); 26 | -------------------------------------------------------------------------------- /packages/core/ext/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ext" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["staticlib"] 8 | 9 | [dependencies] 10 | shiika_ffi = { path = "../../../lib/shiika_ffi" } 11 | shiika_ffi_macro = { path = "../../../lib/shiika_ffi_macro" } 12 | tokio = { version = "1.43.1", features = ["full"] } 13 | bdwgc-alloc = { version = "0.6.5", default-features=false, features = ["cmake"] } 14 | -------------------------------------------------------------------------------- /packages/core/ext/exports.json5: -------------------------------------------------------------------------------- 1 | // List of methods defined in ./ext 2 | // format: [class_name, method_signature, is_async] 3 | [ 4 | ["Int", "+(other: Int) -> Int", false], 5 | ["Int", "-(other: Int) -> Int", false], 6 | ["Int", "*(other: Int) -> Int", false], 7 | //["Int", "/(other: Int) -> Float", false], 8 | ["Int", "%(other: Int) -> Int", false], 9 | ["Int", "and(other: Int) -> Int", false], 10 | ["Int", "or(other: Int) -> Int", false], 11 | ["Int", "xor(other: Int) -> Int", false], 12 | ["Int", "lshift(n_bits: Int) -> Int", false], 13 | ["Int", "rshift(n_bits: Int) -> Int", false], 14 | ["Int", "<(other: Int) -> Bool", false], 15 | ["Int", "<=(other: Int) -> Bool", false], 16 | ["Int", ">(other: Int) -> Bool", false], 17 | ["Int", ">=(other: Int) -> Bool", false], 18 | ["Int", "==(other: Int) -> Bool", false], 19 | //["Int", "to_f -> Float", false], 20 | ["Object", "initialize", false], 21 | ["Object", "sleep_sec(sec: Int)", true], 22 | ["Object", "print(n: Int)", false], 23 | ] 24 | -------------------------------------------------------------------------------- /packages/core/ext/src/core_class.rs: -------------------------------------------------------------------------------- 1 | mod int; 2 | mod object; 3 | -------------------------------------------------------------------------------- /packages/core/ext/src/core_class/object.rs: -------------------------------------------------------------------------------- 1 | use shiika_ffi::async_::{ChiikaCont, ChiikaEnv, ContFuture}; 2 | use shiika_ffi::core_class::{SkInt, SkObject}; 3 | use shiika_ffi_macro::shiika_method; 4 | use std::future::{poll_fn, Future}; 5 | use std::task::Poll; 6 | use std::time::Duration; 7 | 8 | #[shiika_method("Object#initialize")] 9 | pub extern "C" fn object_initialize(_receiver: SkObject) {} 10 | 11 | #[shiika_method("Object#print")] 12 | pub extern "C" fn print(_receiver: SkInt, n: SkInt) { 13 | println!("{}", n.val()); 14 | } 15 | 16 | #[shiika_method("Object#sleep_sec")] 17 | #[allow(improper_ctypes_definitions)] 18 | pub extern "C" fn sleep_sec( 19 | env: &'static mut ChiikaEnv, 20 | _receiver: SkInt, 21 | nn: SkInt, 22 | cont: ChiikaCont, 23 | ) -> ContFuture { 24 | async fn sleep_sec(n: SkInt) { 25 | // Hand written part (all the rest will be macro-generated) 26 | let sec = n.val() as u64; 27 | tokio::time::sleep(Duration::from_secs(sec)).await; 28 | } 29 | env.cont = Some(cont); 30 | let mut future = Box::pin(sleep_sec(nn)); 31 | Box::new(poll_fn(move |ctx| match future.as_mut().poll(ctx) { 32 | Poll::Ready(_) => Poll::Ready(0), 33 | Poll::Pending => Poll::Pending, 34 | })) 35 | } 36 | -------------------------------------------------------------------------------- /packages/core/ext/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod core_class; 2 | mod runtime; 3 | -------------------------------------------------------------------------------- /packages/core/ext/src/runtime.rs: -------------------------------------------------------------------------------- 1 | mod allocator; 2 | use shiika_ffi::async_::{ChiikaCont, ChiikaEnv, ChiikaValue, ContFuture}; 3 | use std::future::{poll_fn, Future}; 4 | use std::pin::Pin; 5 | use std::task::Poll; 6 | 7 | #[allow(improper_ctypes_definitions)] 8 | type ChiikaThunk = unsafe extern "C" fn(env: *mut ChiikaEnv, cont: ChiikaCont) -> ContFuture; 9 | 10 | #[allow(improper_ctypes)] 11 | extern "C" { 12 | fn chiika_start_user(env: *mut ChiikaEnv, cont: ChiikaCont) -> ContFuture; 13 | } 14 | 15 | #[allow(improper_ctypes_definitions)] 16 | extern "C" fn chiika_finish(env: *mut ChiikaEnv, _v: ChiikaValue) -> ContFuture { 17 | unsafe { 18 | (*env).cont = None; 19 | } 20 | Box::new(poll_fn(move |_context| Poll::Ready(_v))) 21 | } 22 | 23 | #[no_mangle] 24 | #[allow(improper_ctypes_definitions)] 25 | pub extern "C" fn chiika_spawn(f: ChiikaThunk) -> u64 { 26 | let poller = make_poller(f); 27 | tokio::spawn(poller); 28 | 0 29 | } 30 | 31 | #[no_mangle] 32 | pub extern "C" fn chiika_start_tokio() { 33 | let poller = make_poller(chiika_start_user); 34 | tokio::runtime::Builder::new_current_thread() 35 | .enable_all() 36 | .build() 37 | .unwrap() 38 | .block_on(poller); 39 | 40 | // Q: Need this? 41 | // sleep(Duration::from_millis(50)).await; 42 | } 43 | 44 | fn make_poller(f: ChiikaThunk) -> impl Future { 45 | let mut env = ChiikaEnv::new(); 46 | poll_fn(move |context| loop { 47 | let future = env 48 | .pop_rust_frame() 49 | .unwrap_or_else(|| unsafe { f(&mut env, chiika_finish) }); 50 | let mut pinned = Pin::new(future); 51 | let tmp = pinned.as_mut().poll(context); 52 | match tmp { 53 | Poll::Ready(value) => { 54 | if let Some(cont) = env.cont { 55 | let new_future = cont(&mut env, value); 56 | env.push_rust_frame(new_future); 57 | } else { 58 | return Poll::Ready(()); 59 | } 60 | } 61 | Poll::Pending => { 62 | env.push_rust_frame(Pin::into_inner(pinned)); 63 | return Poll::Pending; 64 | } 65 | } 66 | }) 67 | } 68 | -------------------------------------------------------------------------------- /packages/core/ext/src/runtime/allocator.rs: -------------------------------------------------------------------------------- 1 | /// Allocate memory with bdwgc 2 | use bdwgc_alloc::Allocator; 3 | use std::alloc::Layout; 4 | use std::os::raw::c_void; 5 | 6 | #[global_allocator] 7 | static GLOBAL_ALLOCATOR: Allocator = Allocator; 8 | 9 | const DEFAULT_ALIGNMENT: usize = 8; 10 | 11 | #[no_mangle] 12 | pub extern "C" fn shiika_malloc(size: usize) -> *mut c_void { 13 | (unsafe { std::alloc::alloc(Layout::from_size_align(size, DEFAULT_ALIGNMENT).unwrap()) }) 14 | as *mut c_void 15 | } 16 | 17 | #[no_mangle] 18 | pub extern "C" fn shiika_realloc(pointer: *mut c_void, size: usize) -> *mut c_void { 19 | // Layouts are ignored by the bdwgc global allocator. 20 | (unsafe { 21 | std::alloc::realloc( 22 | pointer as *mut u8, 23 | Layout::from_size_align(0, DEFAULT_ALIGNMENT).unwrap(), 24 | size, 25 | ) 26 | }) as *mut c_void 27 | } 28 | -------------------------------------------------------------------------------- /packages/core/index.sk: -------------------------------------------------------------------------------- 1 | #require "./lib/int.sk" 2 | -------------------------------------------------------------------------------- /packages/core/package.json5: -------------------------------------------------------------------------------- 1 | { 2 | name: "core", 3 | version: "0.1.0", 4 | rust_libs: ["ext"], 5 | } 6 | -------------------------------------------------------------------------------- /release_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | cargo run -- run examples/fib.sk > examples/fib.actual.txt 5 | diff examples/fib.actual.txt examples/fib.expected_out.txt 6 | 7 | cargo run -- run examples/hello.sk > examples/hello.actual.txt 8 | diff examples/hello.actual.txt examples/hello.expected_out.txt 9 | 10 | cargo run -- run examples/lifegame.sk > examples/lifegame.actual.txt 11 | diff examples/lifegame.actual.txt examples/lifegame.expected_out.txt 12 | 13 | cargo run -- run examples/mandel.sk > examples/mandel.actual.pbm 14 | diff examples/mandel.actual.pbm examples/mandel.expected_out.pbm 15 | 16 | cargo run -- run examples/ray.sk > examples/ray.actual.ppm 17 | diff examples/ray.actual.ppm examples/ray.expected_out.ppm 18 | -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # build Shiika 5 | cargo build 6 | 7 | # build corelib 8 | cd lib/skc_rustlib 9 | cargo build 10 | cd ../.. 11 | cargo run -- build-corelib 12 | 13 | echo "Shiika setup completed successfully!" 14 | -------------------------------------------------------------------------------- /shiika_logo_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiika-lang/shiika/c20d18b730d2f2fc2d80dd524285b7add2a4e876/shiika_logo_small.png -------------------------------------------------------------------------------- /src/bin/exp_shiika.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use skc_async_experiment::run; 3 | 4 | fn main() -> Result<()> { 5 | run::main() 6 | } 7 | -------------------------------------------------------------------------------- /src/cli.rs: -------------------------------------------------------------------------------- 1 | use clap::{Parser, Subcommand}; 2 | 3 | #[derive(clap::Parser, Debug)] 4 | #[clap(name = "shiika", author, version, about)] 5 | pub struct Arguments { 6 | #[clap(subcommand)] 7 | pub command: Command, 8 | } 9 | 10 | #[derive(Debug, Subcommand)] 11 | pub enum Command { 12 | /// Compile shiika program 13 | Compile { filepath: String }, 14 | /// Compile and execute shiika program 15 | Run { filepath: String }, 16 | /// Build corelib 17 | BuildCorelib, 18 | } 19 | 20 | pub fn parse_command_line_args() -> Arguments { 21 | Arguments::parse() 22 | } 23 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use anyhow; 2 | use std::backtrace::Backtrace; 3 | use thiserror; 4 | 5 | #[derive(thiserror::Error, Debug)] 6 | pub enum Error { 7 | #[error("{msg})")] 8 | RunnerError { msg: String, backtrace: Backtrace, 9 | #[backtrace] 10 | source: anyhow::Error 11 | }, 12 | } 13 | 14 | pub fn runner_error(msg: &str) -> anyhow::Error { 15 | Error::RunnerError { 16 | msg: msg.to_string(), 17 | backtrace: Backtrace::capture(), 18 | } 19 | .into() 20 | } 21 | 22 | pub fn type_error(msg: &str) -> anyhow::Error { 23 | Error::TypeError { 24 | msg: msg.to_string(), 25 | backtrace: Backtrace::capture(), 26 | } 27 | .into() 28 | } 29 | 30 | pub fn name_error(msg: &str) -> anyhow::Error { 31 | Error::NameError { 32 | msg: msg.to_string(), 33 | backtrace: Backtrace::capture(), 34 | } 35 | .into() 36 | } 37 | 38 | pub fn program_error(msg: &str) -> anyhow::Error { 39 | Error::ProgramError { 40 | msg: msg.to_string(), 41 | backtrace: Backtrace::capture(), 42 | } 43 | .into() 44 | } 45 | 46 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod cli; 2 | mod loader; 3 | pub mod runner; 4 | mod targets; 5 | -------------------------------------------------------------------------------- /src/loader.rs: -------------------------------------------------------------------------------- 1 | // Resolve "require" 2 | use anyhow::{Context, Result}; 3 | use shiika_parser::SourceFile; 4 | use std::fs; 5 | use std::path::{Path, PathBuf}; 6 | 7 | /// Read a .sk file (and those require'd by it) 8 | pub fn load(path: &Path) -> Result> { 9 | let mut files = vec![]; 10 | let mut loading_files = vec![]; 11 | load_file(path, &mut files, &mut loading_files)?; 12 | Ok(files) 13 | } 14 | 15 | fn load_file( 16 | path: &Path, 17 | files: &mut Vec, 18 | loading_files: &mut Vec, 19 | ) -> Result<()> { 20 | if loading_files.contains(&path.into()) { 21 | return Ok(()); 22 | } 23 | loading_files.push(path.into()); 24 | 25 | // Load require'd files first 26 | let content = fs::read_to_string(path).context(format!("failed to load {}", path.display()))?; 27 | let newpaths = resolve_requires(path, &content); 28 | for newpath in newpaths { 29 | load_file(&newpath, files, loading_files)?; 30 | } 31 | 32 | files.push(SourceFile::new(path.into(), content)); 33 | Ok(()) 34 | } 35 | 36 | /// Read require'd files into `files` 37 | #[allow(clippy::if_same_then_else)] 38 | fn resolve_requires(path: &Path, content: &str) -> Vec { 39 | let mut paths = vec![]; 40 | for line in content.lines() { 41 | if line.trim_start().starts_with("require") { 42 | paths.push(parse_require(line, path)); 43 | } else if line.trim_start().starts_with('#') { 44 | // skip comment line. 45 | } else if line.trim_start().is_empty() { 46 | // skip empty line. 47 | } else { 48 | break; 49 | } 50 | } 51 | paths 52 | } 53 | 54 | /// Expand filepath in require 55 | fn parse_require(line: &str, path: &Path) -> PathBuf { 56 | let s = line 57 | .trim_start() 58 | .trim_start_matches("require") 59 | .trim_start() 60 | .trim_start_matches('"') 61 | .trim_end() 62 | .trim_end_matches('"'); 63 | path.with_file_name(s) 64 | } 65 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use shiika::cli; 3 | use shiika::runner; 4 | 5 | fn main() -> Result<()> { 6 | env_logger::init(); 7 | let args = cli::parse_command_line_args(); 8 | 9 | match &args.command { 10 | cli::Command::Compile { filepath } => { 11 | runner::compile(filepath)?; 12 | } 13 | cli::Command::Run { filepath } => { 14 | runner::compile(filepath)?; 15 | runner::run(filepath)?; 16 | } 17 | cli::Command::BuildCorelib => { 18 | runner::build_corelib()?; 19 | } 20 | } 21 | 22 | Ok(()) 23 | } 24 | 25 | //fn print_err(err: Error) { 26 | // println!("{}", err.msg); 27 | // for frame in err.backtrace.frames() { 28 | // for symbol in frame.symbols() { 29 | // if let Some(name) = symbol.name() { 30 | // let s = format!("{}", name); 31 | // if s.starts_with("shiika") { 32 | // println!("- {}", s); 33 | // } 34 | // } 35 | // } 36 | // } 37 | //} 38 | -------------------------------------------------------------------------------- /src/targets.rs: -------------------------------------------------------------------------------- 1 | use os_info::{Type, Version}; 2 | 3 | /// Returns default `TargetTriple` 4 | pub fn default_triple() -> inkwell::targets::TargetTriple { 5 | let info = os_info::get(); 6 | if info.os_type() == Type::Macos { 7 | // #281: calculate target triple to avoid clang's warning 8 | if let Some(arch) = info.architecture() { 9 | if let Version::Semantic(major, minor, patch) = info.version() { 10 | let s = format!("{}-apple-macosx{}.{}.{}", arch, major, minor, patch); 11 | return inkwell::targets::TargetTriple::create(&s); 12 | } 13 | } 14 | } 15 | inkwell::targets::TargetMachine::get_default_triple() 16 | } 17 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | # Tests 2 | 3 | ## `tests/sk` 4 | 5 | This directory contains various Shiika programs and run by `tests/integration_test.rs`. 6 | 7 | ### Conventions 8 | 9 | .sk in this directory 10 | 11 | - must ends with `puts "ok"` 12 | - must not print anything other if succeed 13 | - should print error message if failed 14 | 15 | ## `tests/erroneous` 16 | 17 | This directory contains various Shiika programs which is expected to cause compilation error. 18 | The expected output is stored in `snapshots` directory with the [insta crate](https://insta.rs/docs/). 19 | 20 | However this does not mean current error messages are considered perfect; PRs to improve them are welcome. 21 | 22 | Rather than that, erroneous tests are for: 23 | 24 | - assuring the type checker detects various type errors 25 | - assuring the compiler does not crash with an erroneous program 26 | - investigating the impact of a modification to the type checker 27 | 28 | ### How to add new .sk to `tests/erroneous` 29 | 30 | prerequisites: `cargo install cargo-insta` 31 | 32 | 1. Create .sk 33 | 2. `cargo test test_erroneous` (this will create `tests/snapshots/*.snap.new`) 34 | 3. `cargo insta review` and accept the snapshot (this will rename `.snap.new` to `.snap`) 35 | 4. Commit `.snap` to git 36 | 37 | ### How to fix CI fail after changing error message 38 | 39 | TBA 40 | -------------------------------------------------------------------------------- /tests/erroneous.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use concolor; 3 | use insta::{assert_snapshot, glob}; 4 | use shiika::runner; 5 | use std::path::Path; 6 | 7 | #[test] 8 | fn test_erroneous() -> Result<()> { 9 | concolor::set(concolor::ColorChoice::Never); 10 | let base = Path::new(".").canonicalize()?; 11 | glob!("erroneous/**/*.sk", |sk_path_| { 12 | // Make the path relative to the project root so that the resulting .snap will be 13 | // identical on my machine and in the CI environment. 14 | let sk_path = sk_path_.strip_prefix(&base).unwrap(); 15 | let compiler_output = match runner::compile(sk_path) { 16 | Ok(_) => "".to_string(), 17 | Err(comp_err) => comp_err.to_string(), 18 | }; 19 | assert_snapshot!(compiler_output); 20 | }); 21 | Ok(()) 22 | } 23 | -------------------------------------------------------------------------------- /tests/erroneous/class_definition/no_class_name.sk: -------------------------------------------------------------------------------- 1 | class 2 | end 3 | -------------------------------------------------------------------------------- /tests/erroneous/class_definition/unknown_superclass.sk: -------------------------------------------------------------------------------- 1 | class A : XXX 2 | end 3 | -------------------------------------------------------------------------------- /tests/erroneous/method_call/extra_argument_error.sk: -------------------------------------------------------------------------------- 1 | class A 2 | def self.run 3 | end 4 | end 5 | A.run(1) 6 | -------------------------------------------------------------------------------- /tests/erroneous/method_call/invalid_type.sk: -------------------------------------------------------------------------------- 1 | puts 123 2 | -------------------------------------------------------------------------------- /tests/erroneous/parsing/semicolon_after_binary_op.sk: -------------------------------------------------------------------------------- 1 | 1+;2 2 | -------------------------------------------------------------------------------- /tests/erroneous/parsing/semicolon_within_parentheses1.sk: -------------------------------------------------------------------------------- 1 | class Test 2 | def foo(a: Int, b: Int) -> Int 3 | return a + b 4 | end 5 | end 6 | 7 | Test.new.foo(;1,2) 8 | -------------------------------------------------------------------------------- /tests/erroneous/parsing/semicolon_within_parentheses2.sk: -------------------------------------------------------------------------------- 1 | class Test 2 | def foo(a: Int, b: Int) -> Int 3 | return a + b 4 | end 5 | end 6 | 7 | Test.new.foo(1;,2) 8 | -------------------------------------------------------------------------------- /tests/erroneous/parsing/semicolon_within_parentheses3.sk: -------------------------------------------------------------------------------- 1 | class Test 2 | def foo(a: Int, b: Int) -> Int 3 | return a + b 4 | end 5 | end 6 | 7 | Test.new.foo(1,;2) 8 | -------------------------------------------------------------------------------- /tests/erroneous/parsing/semicolon_within_parentheses4.sk: -------------------------------------------------------------------------------- 1 | class Test 2 | def foo(a: Int, b: Int) -> Int 3 | return a + b 4 | end 5 | end 6 | 7 | Test.new.foo(1,2;) 8 | -------------------------------------------------------------------------------- /tests/integration_test.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Result}; 2 | use shiika::runner; 3 | use std::env; 4 | use std::fs; 5 | 6 | #[test] 7 | fn test_compile_and_run() -> Result<()> { 8 | let filter = env::var("FILTER").ok(); 9 | let paths = fs::read_dir("tests/sk/")?; 10 | for item in paths { 11 | let pathbuf = item?.path(); 12 | let path = pathbuf 13 | .to_str() 14 | .ok_or_else(|| anyhow!("Filename not utf8"))?; 15 | if path.ends_with(".sk") { 16 | if let Some(s) = &filter { 17 | if !path.contains(s) { 18 | continue; 19 | } 20 | } 21 | run_sk_test(path)?; 22 | } 23 | } 24 | Ok(()) 25 | } 26 | 27 | #[test] 28 | fn test_no_panic() -> Result<()> { 29 | let path = "tests/no_panic.sk"; 30 | // `compile` may return an Err here; it just should not panic. 31 | let _ = runner::compile(path); 32 | runner::cleanup(path)?; 33 | Ok(()) 34 | } 35 | 36 | /// Execute tests/sk/x.sk 37 | /// Fail if it prints something 38 | fn run_sk_test(path: &str) -> Result<()> { 39 | dbg!(&path); 40 | runner::compile(path)?; 41 | let (stdout, stderr) = runner::run_and_capture(path)?; 42 | assert_eq!(stderr, ""); 43 | assert_eq!(stdout, "ok\n"); 44 | runner::cleanup(path)?; 45 | Ok(()) 46 | } 47 | -------------------------------------------------------------------------------- /tests/new_runtime/arg_ref.expected_out: -------------------------------------------------------------------------------- 1 | 3 2 | 3 3 | -------------------------------------------------------------------------------- /tests/new_runtime/arg_ref.sk: -------------------------------------------------------------------------------- 1 | class Main 2 | def self.foo(a: Int, b: Int) -> Int 3 | if a < b 4 | print(a + b) 5 | sleep_sec(0) 6 | return a + b 7 | else 8 | print(a + b) 9 | sleep_sec(0) 10 | return a + b 11 | end 12 | end 13 | 14 | def self.run() -> Int 15 | print(foo(1, 2)) 16 | return 0 17 | end 18 | end 19 | Main.run -------------------------------------------------------------------------------- /tests/new_runtime/async.expected_out: -------------------------------------------------------------------------------- 1 | 123 2 | 456 3 | -------------------------------------------------------------------------------- /tests/new_runtime/async.sk: -------------------------------------------------------------------------------- 1 | class Main 2 | def self.run() -> Int 3 | print(123) 4 | sleep_sec(0) 5 | print(456) 6 | return 0 7 | end 8 | end 9 | Main.run -------------------------------------------------------------------------------- /tests/new_runtime/async_if.expected_out: -------------------------------------------------------------------------------- 1 | 456 2 | -------------------------------------------------------------------------------- /tests/new_runtime/async_if.sk: -------------------------------------------------------------------------------- 1 | class Main 2 | def self.run() -> Int 3 | if true 4 | sleep_sec(0) 5 | print(456) 6 | else 7 | print(789) 8 | end 9 | return 0 10 | end 11 | end 12 | Main.run -------------------------------------------------------------------------------- /tests/new_runtime/async_lvar.expected_out: -------------------------------------------------------------------------------- 1 | 3 2 | 3 3 | -------------------------------------------------------------------------------- /tests/new_runtime/async_lvar.sk: -------------------------------------------------------------------------------- 1 | class Main 2 | def self.run() -> Int 3 | let x = 3 4 | print(x) 5 | sleep_sec(0) 6 | print(x) 7 | return 0 8 | end 9 | end 10 | Main.run -------------------------------------------------------------------------------- /tests/new_runtime/async_valued_if.expected_out: -------------------------------------------------------------------------------- 1 | 456 2 | -------------------------------------------------------------------------------- /tests/new_runtime/async_valued_if.sk: -------------------------------------------------------------------------------- 1 | class Main 2 | def self.run() -> Int 3 | print( 4 | if true 5 | sleep_sec(0) 6 | 456 7 | else 8 | 789 9 | end 10 | ) 11 | return 0 12 | end 13 | end 14 | Main.run -------------------------------------------------------------------------------- /tests/new_runtime/countdown.expected_out: -------------------------------------------------------------------------------- 1 | 3 2 | 2 3 | 1 4 | 0 5 | -------------------------------------------------------------------------------- /tests/new_runtime/countdown.sk: -------------------------------------------------------------------------------- 1 | class Main 2 | def self.countdown(i: Int) -> Void 3 | print(i) 4 | sleep_sec(0) 5 | if i > 0 6 | return countdown(i - 1) 7 | else 8 | return null 9 | end 10 | end 11 | 12 | def self.run() -> Int 13 | countdown(3) 14 | return 0 15 | end 16 | end 17 | Main.run -------------------------------------------------------------------------------- /tests/new_runtime/countdown2.expected_out: -------------------------------------------------------------------------------- 1 | 3 2 | 2 3 | 1 4 | 0 5 | -------------------------------------------------------------------------------- /tests/new_runtime/countdown2.sk: -------------------------------------------------------------------------------- 1 | class Main 2 | def self.countdown(i: Int) -> Void 3 | print(i) 4 | sleep_sec(0) 5 | if i > 0 6 | countdown(i - 1) 7 | end 8 | end 9 | 10 | def self.run() -> Int 11 | countdown(3) 12 | return 0 13 | end 14 | end 15 | 16 | Main.run -------------------------------------------------------------------------------- /tests/new_runtime/countdown3.expected_out: -------------------------------------------------------------------------------- 1 | 3 2 | 2 3 | 1 4 | 0 5 | -------------------------------------------------------------------------------- /tests/new_runtime/countdown3.sk: -------------------------------------------------------------------------------- 1 | class Main 2 | def self.countdown(i: Int) -> Int 3 | print(i) 4 | sleep_sec(0) 5 | if i > 0 6 | countdown(i - 1) 7 | else 8 | 0 9 | end 10 | end 11 | 12 | def self.run() -> Int 13 | countdown(3) 14 | return 0 15 | end 16 | end 17 | 18 | Main.run -------------------------------------------------------------------------------- /tests/new_runtime/early_return.expected_out: -------------------------------------------------------------------------------- 1 | 123 2 | -------------------------------------------------------------------------------- /tests/new_runtime/early_return.sk: -------------------------------------------------------------------------------- 1 | class Main 2 | def self.run() -> Int 3 | if true 4 | sleep_sec(0) 5 | print(123) 6 | return 0 7 | end 8 | print(456) 9 | return 0 10 | end 11 | end 12 | Main.run -------------------------------------------------------------------------------- /tests/new_runtime/if.expected_out: -------------------------------------------------------------------------------- 1 | 456 2 | -------------------------------------------------------------------------------- /tests/new_runtime/if.sk: -------------------------------------------------------------------------------- 1 | class Main 2 | def self.run() -> Int 3 | if true 4 | print(456) 5 | else 6 | print(789) 7 | end 8 | return 0 9 | end 10 | end 11 | Main.run -------------------------------------------------------------------------------- /tests/new_runtime/if_return_return.expected_out: -------------------------------------------------------------------------------- 1 | 123 2 | -------------------------------------------------------------------------------- /tests/new_runtime/if_return_return.sk: -------------------------------------------------------------------------------- 1 | class Main 2 | def self.run() -> Int 3 | if true 4 | sleep_sec(0) 5 | print(123) 6 | return 0 7 | else 8 | print(456) 9 | return 0 10 | end 11 | end 12 | end 13 | Main.run -------------------------------------------------------------------------------- /tests/new_runtime/sync.expected_out: -------------------------------------------------------------------------------- 1 | 123 2 | -------------------------------------------------------------------------------- /tests/new_runtime/sync.sk: -------------------------------------------------------------------------------- 1 | class Main 2 | def self.run() -> Int 3 | print(123) 4 | return 0 5 | end 6 | end 7 | Main.run -------------------------------------------------------------------------------- /tests/new_runtime/valued_if.expected_out: -------------------------------------------------------------------------------- 1 | 456 2 | -------------------------------------------------------------------------------- /tests/new_runtime/valued_if.sk: -------------------------------------------------------------------------------- 1 | class Main 2 | def self.run() -> Int 3 | print( 4 | if true 5 | 456 6 | else 7 | 789 8 | end 9 | ) 10 | return 0 11 | end 12 | end 13 | Main.run -------------------------------------------------------------------------------- /tests/new_runtime/while_arg.expected_out: -------------------------------------------------------------------------------- 1 | 7 2 | 8 3 | 9 4 | 7 5 | -------------------------------------------------------------------------------- /tests/new_runtime/while_arg.sk: -------------------------------------------------------------------------------- 1 | class Main 2 | def self.foo(x: Int) 3 | var i = 0 4 | while i < 3 5 | print x + i 6 | sleep_sec 0 7 | i += 1 8 | end 9 | print x 10 | end 11 | def self.run() -> Int 12 | foo(7) 13 | return 0 14 | end 15 | end 16 | 17 | Main.run -------------------------------------------------------------------------------- /tests/new_runtime/while_lvar.expected_out: -------------------------------------------------------------------------------- 1 | 123 2 | 124 3 | 125 4 | 123 5 | -------------------------------------------------------------------------------- /tests/new_runtime/while_lvar.sk: -------------------------------------------------------------------------------- 1 | class Main 2 | def self.run() -> Int 3 | let a = 123 4 | var i = 0 5 | while i < 3 6 | print a + i 7 | sleep_sec 0 8 | i += 1 9 | end 10 | print a 11 | return 0 12 | end 13 | end 14 | 15 | Main.run -------------------------------------------------------------------------------- /tests/new_runtime/while_return.expected_out: -------------------------------------------------------------------------------- 1 | 0 2 | 1 3 | -------------------------------------------------------------------------------- /tests/new_runtime/while_return.sk: -------------------------------------------------------------------------------- 1 | class Main 2 | def self.run() -> Int 3 | var i = 0 4 | while i < 3 5 | print i 6 | sleep_sec 0 7 | return 0 if i == 1 8 | i += 1 9 | end 10 | return 0 11 | end 12 | end 13 | 14 | Main.run -------------------------------------------------------------------------------- /tests/new_runtime/while_sync.expected_out: -------------------------------------------------------------------------------- 1 | 2 2 | 3 3 | 4 4 | 6 5 | -------------------------------------------------------------------------------- /tests/new_runtime/while_sync.sk: -------------------------------------------------------------------------------- 1 | class Main 2 | def self.foo() 3 | var i = 1 4 | while i < 3 5 | var j = 2 6 | while j < 4 7 | print i * j 8 | j += 1 9 | end 10 | i += 1 11 | end 12 | end 13 | def self.run() -> Int 14 | foo() 15 | return 0 16 | end 17 | end 18 | 19 | 20 | Main.run -------------------------------------------------------------------------------- /tests/new_runtime/while_while.expected_out: -------------------------------------------------------------------------------- 1 | 2 2 | 3 3 | 4 4 | 6 5 | -------------------------------------------------------------------------------- /tests/new_runtime/while_while.sk: -------------------------------------------------------------------------------- 1 | class Main 2 | def self.run() -> Int 3 | var i = 1 4 | while i < 3 5 | var j = 2 6 | while j < 4 7 | print i * j 8 | sleep_sec 0 9 | j += 1 10 | end 11 | i += 1 12 | end 13 | return 0 14 | end 15 | end 16 | 17 | Main.run -------------------------------------------------------------------------------- /tests/sk/array.sk: -------------------------------------------------------------------------------- 1 | let a = [123] 2 | let fst = a.first 3 | match a.first 4 | when Some(v) 5 | puts "ng #first (#{v})" unless v == 123 6 | else 7 | puts "ng #first" 8 | end 9 | 10 | let b = Array.new 11 | b.push(123) 12 | unless b[0] == 123; puts "ng Array.new"; end 13 | 14 | # flat_map 15 | let fa = [1, 2].flat_map{|i: Int| [i.to_f, i.to_f / 2.0]} 16 | unless fa == [1.0, 0.5, 2.0, 1.0]; puts "ng flat_map"; end 17 | 18 | let some_array = [1,2,3] 19 | unless some_array[ 0 ] == 1; puts "ng array index"; end 20 | 21 | puts "ok" 22 | -------------------------------------------------------------------------------- /tests/sk/block.sk: -------------------------------------------------------------------------------- 1 | var result = 0 2 | class A 3 | def self.one(f: Fn1) -> Int 4 | f(1) 5 | end 6 | end 7 | 8 | # TODO #172 9 | #A.one do |i: Int| result = 2 end 10 | #unless result == 2; puts "do/oneline"; end 11 | # 12 | #A.one do |i: Int| 13 | # putd i 14 | # result = 3 15 | #end 16 | #unless result == 3; puts "do/multiline"; end 17 | 18 | result = A.one do |i: Int| i + 1 end 19 | unless result == 2; puts "assignment rhs/do/oneline"; end 20 | 21 | result = A.one{|i: Int| i + 1} 22 | unless result == 2; puts "assignment rhs/brace/oneline"; end 23 | 24 | result = A.one do |i: Int| 25 | i + 1 26 | end 27 | unless result == 2; puts "assignment rhs/do/multiline"; end 28 | 29 | result = A.one{|i: Int| 30 | i + 1 31 | } 32 | unless result == 2; puts "assignment rhs/brace/multiline"; end 33 | 34 | # Should pass type check (#426) 35 | class B 36 | def foo(g: Fn1) 37 | bar(){ 38 | let z = Some.new(0) 39 | z.map{|x| g(x + 1)} 40 | } 41 | end 42 | def bar(f: Fn0>); end 43 | end 44 | 45 | # Should pass type check (#452) 46 | class C 47 | def self.foo -> Array 48 | "aaa".lines.map{|l| 49 | C.new 50 | } 51 | end 52 | end 53 | 54 | puts "ok" 55 | -------------------------------------------------------------------------------- /tests/sk/class_hierarchy.sk: -------------------------------------------------------------------------------- 1 | unless Int.name == "Int"; puts "ng Int"; end 2 | unless Int.class.name == "Meta:Int"; puts "ng Meta:Int"; end 3 | unless Int.class.class.name == "Metaclass"; puts "ng Int Metaclass"; end 4 | 5 | unless Class.name == "Class"; puts "ng Class"; end 6 | unless Class.class.name == "Meta:Class"; puts "ng Meta:Class"; end 7 | unless Class.class.class.name == "Metaclass"; puts "ng Class Metaclass"; end 8 | 9 | let metaclass = Int.class.class 10 | unless Metaclass == metaclass; puts "ng Metaclass"; end 11 | unless metaclass.name == "Metaclass"; puts "ng Metaclass"; end 12 | unless metaclass.class == Metaclass; puts "ng metaclass.class"; end 13 | 14 | unless Array.new.class == Array; puts "ng Array"; end 15 | unless [1].class == Array; puts "ng [1].class"; end 16 | unless Array.name == "Array"; puts "ng Array.name"; end 17 | 18 | puts "ok" 19 | -------------------------------------------------------------------------------- /tests/sk/class_instance_variable.sk: -------------------------------------------------------------------------------- 1 | # Basic example 2 | class A 3 | def self.initialize 4 | let @a = 0 5 | end 6 | end 7 | if A.a != 0; puts "A.a returned \{A.a}"; end 8 | 9 | # Generics 10 | # 11 | #CT = [0] # https://github.com/shiika-lang/shiika/issues/390 12 | #class B 13 | # def self.initialize 14 | # CT[0] += 1 15 | # end 16 | #end 17 | #if CT[0] != 1; puts "B.initialize did not called"; end 18 | #B.new 19 | #if CT[0] > 1; puts "B.initialize called more than once"; end 20 | 21 | puts "ok" 22 | 23 | -------------------------------------------------------------------------------- /tests/sk/conditional_expr.sk: -------------------------------------------------------------------------------- 1 | class Helper 2 | def self.void; end 3 | end 4 | 5 | # Void + Never 6 | let a = if true 7 | Helper.void # Void 8 | else 9 | panic "" # Never 10 | end 11 | 12 | # Never + Never 13 | class A 14 | def foo 15 | if true 16 | panic "" 17 | else 18 | panic "" 19 | end 20 | end 21 | end 22 | 23 | # String + Object 24 | let b = if true 25 | "" 26 | else 27 | Object.new 28 | end 29 | 30 | puts "ok" 31 | -------------------------------------------------------------------------------- /tests/sk/constants.sk: -------------------------------------------------------------------------------- 1 | class A 2 | class B 3 | end 4 | 5 | # TODO class C : B 6 | class C 7 | def foo 8 | B.new #=> should be resolved to A::B 9 | Array.new 10 | end 11 | end 12 | end 13 | 14 | puts "ok" 15 | -------------------------------------------------------------------------------- /tests/sk/enum.sk: -------------------------------------------------------------------------------- 1 | let a = Some.new(1) 2 | let b = None 3 | let f = fn(x: Maybe) { x } 4 | f(a) 5 | f(b) 6 | unless a.value == 1; puts "ng Some#value"; end 7 | 8 | let o = Ok.new(0) 9 | let e = Fail.new(Error.new("fail")) 10 | 11 | # Class method of enum 12 | enum EnumWithClassMethod 13 | def self.foo -> Int 14 | 1 15 | end 16 | end 17 | unless EnumWithClassMethod.foo == 1; puts "ng EnumWithClassMethod.foo"; end 18 | 19 | # Inner class of enum 20 | enum EnumWithInnerClass 21 | class InnerClassInEnum 22 | def foo -> Int 23 | 1 24 | end 25 | end 26 | end 27 | let obj = EnumWithInnerClass::InnerClassInEnum.new 28 | unless obj.foo == 1; puts "ng InnerClassInEnum#foo"; end 29 | 30 | puts "ok" 31 | -------------------------------------------------------------------------------- /tests/sk/enumerable.sk: -------------------------------------------------------------------------------- 1 | let s = [1,2,3].map{|i: Int| 2 | i.to_s 3 | }.join(", ") 4 | unless s == "1, 2, 3"; puts "ng Array#map"; end 5 | 6 | puts "ok" 7 | -------------------------------------------------------------------------------- /tests/sk/file.sk: -------------------------------------------------------------------------------- 1 | # File.write 2 | match File.write("tests/tmp/hello.txt", "hello") 3 | when Ok(x) 4 | unless x == Void; puts "File.write returned \{x}"; end 5 | when Fail(e) 6 | puts "File.write failed: #{e}" 7 | end 8 | 9 | # File.read 10 | match File.read("tests/tmp/hello.txt") 11 | when Ok(s) 12 | unless s == "hello"; puts "File.read failed: #{s}"; end 13 | when Fail(e) 14 | puts "File.read failed: #{e}" 15 | end 16 | 17 | class FileOpen 18 | def self.run 19 | File.open("tests/tmp/hello.txt"){|f| 20 | match f.read 21 | when Ok(s) 22 | unless s == "hello"; puts "FileOpen failed: #{s}"; end 23 | when Fail(e) 24 | puts "FileOpen failed: #{e}" 25 | end 26 | } 27 | end 28 | end 29 | FileOpen.run 30 | 31 | puts "ok" 32 | -------------------------------------------------------------------------------- /tests/sk/float.sk: -------------------------------------------------------------------------------- 1 | class Helper 2 | def self.eq(x: Float, y: Float) -> Bool 3 | (x-y).abs < 0.000001 4 | end 5 | end 6 | 7 | unless Helper.eq(1.0 + 2.0, 3.0) then puts "ng 1" end 8 | unless Helper.eq(3.0 - 2.0, 1.0) then puts "ng 2" end 9 | unless Helper.eq(3.0 * 2.0, 6.0) then puts "ng 3" end 10 | unless Helper.eq(3.0 / 2.0, 1.5) then puts "ng 4" end 11 | unless Helper.eq(3.0 % 2, 1.0) then puts "ng 5" end 12 | 13 | # TODO: unless -3**2 == -9 then puts "ng -3**2" end 14 | 15 | puts "ok" 16 | -------------------------------------------------------------------------------- /tests/sk/generic_method.sk: -------------------------------------------------------------------------------- 1 | class GenericMethodTest 2 | def self.run 3 | unless X == Int; puts "failed GenericMethodTest"; end 4 | end 5 | end 6 | GenericMethodTest.run 7 | 8 | class InferredMethodTyArgTest 9 | def self.run(x: X) 10 | unless X == Int; puts "failed InferredMethodTyArgTest"; end 11 | end 12 | end 13 | InferredMethodTyArgTest.run(99) 14 | 15 | class ConstIsObjTyArgTest 16 | # `Void` is a special class that the constant holds the only instance rather 17 | # than the class object (which can be accessed as `Void.class`). 18 | def self.run1(x: X) 19 | unless X == Void.class; puts "failed ConstIsObjTyArgTest 1"; end 20 | end 21 | # Other than `Void`, enum cases with no arguments (eg. Maybe::None) are set 22 | # the `const_is_obj` flag set to true. 23 | def self.run2(x: X) 24 | unless X == None.class; puts "failed ConstIsObjTyArgTest 2"; end 25 | end 26 | end 27 | ConstIsObjTyArgTest.run1(Void) 28 | ConstIsObjTyArgTest.run2(None) 29 | 30 | class CaptureMethodTyargTest 31 | def self.run 32 | let f = fn(){ 33 | unless X == Int; puts "failed CaptureMethodTyargTest"; end 34 | } 35 | f() 36 | end 37 | end 38 | CaptureMethodTyargTest.run 39 | 40 | module ModuleWithGenericMethod 41 | def run 42 | unless X == Int; puts "failed GenericModuleMethodTest"; end 43 | end 44 | end 45 | class GenericModuleMethodTest : ModuleWithGenericMethod 46 | end 47 | GenericModuleMethodTest.new.run 48 | 49 | puts "ok" 50 | -------------------------------------------------------------------------------- /tests/sk/generics.sk: -------------------------------------------------------------------------------- 1 | class A 2 | def self.foo(a: Array) -> Int 3 | a[0] 4 | end 5 | end 6 | if A.foo([99]) != 99; puts "ng 1"; end 7 | 8 | let a = Array>.new 9 | unless a.length == 0; puts "ng 2"; end 10 | 11 | # Type parameter substitution 12 | class Foo 13 | def bar(f: Fn1) -> Int 14 | let foo = Foo.new 15 | foo.bar{|arg: W| 0} if false 16 | 99 17 | end 18 | end 19 | unless Foo.new.bar{|arg: String| 0} == 99; puts "ng Type parameter substitution"; end 20 | 21 | class Issue422 22 | def foo(x: X) -> Array 23 | [1,2,3].map{|i| x} 24 | end 25 | end 26 | unless Issue422.new.foo(true) == [true,true,true]; puts "ng #422"; end 27 | 28 | puts "ok" 29 | -------------------------------------------------------------------------------- /tests/sk/inheritance.sk: -------------------------------------------------------------------------------- 1 | # Inherit methods 2 | class Base1 3 | def foo -> Int 4 | 1 5 | end 6 | end 7 | 8 | class Sub1 : Base1 9 | end 10 | 11 | let sub1 = Sub1.new 12 | unless sub1.foo == 1 then puts "ng 1" end 13 | 14 | # Inherit initializer 15 | class Base2 16 | def initialize 17 | let @a = 9 18 | end 19 | end 20 | 21 | class Mid2 : Base2 22 | end 23 | 24 | class Sub2 : Mid2 25 | end 26 | 27 | let sub2 = Sub2.new 28 | unless sub2.a == 9 then puts "ng 2" end 29 | 30 | # Inherit ivars 31 | class Base3 32 | def initialize(i: Int) 33 | let @a = i 34 | end 35 | end 36 | 37 | class Sub3 : Base3 38 | def initialize 39 | let @a = 2 40 | let @b = 2.0 41 | end 42 | end 43 | 44 | let sub3 = Sub3.new 45 | unless sub3.a == 2 then puts "ng 3" end 46 | unless sub3.b == 2.0 then puts "ng 4" end 47 | 48 | ## Type compatibility 49 | #class Base4 50 | # def self.foo(x: Base4); end 51 | #end 52 | # 53 | #class Sub4 : Base4; end 54 | # 55 | #sub4 = Sub4.new 56 | #Base4.foo(sub4) 57 | 58 | puts "ok" 59 | -------------------------------------------------------------------------------- /tests/sk/instantiation.sk: -------------------------------------------------------------------------------- 1 | unless Array.new.class == Array; puts "fail 1"; end 2 | unless Array.new.class == Array; puts "fail 2"; end 3 | unless Pair.new(1, true).class == Pair; puts "fail 3"; end 4 | 5 | puts "ok" 6 | -------------------------------------------------------------------------------- /tests/sk/int.sk: -------------------------------------------------------------------------------- 1 | unless 2.and(3) == 2; puts "ng and"; end 2 | unless 2.or(3) == 3; puts "ng or"; end 3 | unless 2.xor(3) == 1; puts "ng xor"; end 4 | unless 1.lshift(3) == 8; puts "ng lshift"; end 5 | unless 8.rshift(1) == 4; puts "ng rshift"; end 6 | 7 | puts "ok" 8 | -------------------------------------------------------------------------------- /tests/sk/ivar.sk: -------------------------------------------------------------------------------- 1 | class A 2 | def initialize 3 | var @i = 1 4 | end 5 | end 6 | let a = A.new 7 | unless a.i == 1 then puts "ng 1" end 8 | a.i = 2 9 | unless a.i == 2 then puts "ng 2" end 10 | 11 | # Store function to ivar and call it 12 | class IvarFn 13 | def initialize 14 | let @inc = fn(x: Int){ x + 1 } 15 | end 16 | 17 | def foo -> Int 18 | (@inc)(2) 19 | end 20 | end 21 | unless IvarFn.new.foo == 3; puts "ng IvarFn"; end 22 | 23 | puts "ok" 24 | -------------------------------------------------------------------------------- /tests/sk/lambda.sk: -------------------------------------------------------------------------------- 1 | # Basic 2 | var f0 = fn{ false }; 3 | if f0(); puts "ng 0"; end 4 | var f1 = fn(x: Int){ x + 1 } 5 | if f1(1) != 2; puts "ng 1"; end 6 | let f2 = fn(x: Int, y: Int){ x + y } 7 | if f2(1, 2) != 3; puts "ng 1-1"; end 8 | 9 | # Lambda in a method 10 | class A 11 | def self.foo(i: Int) -> Int 12 | let f = fn(x: Int){ x + i } 13 | f(1) 14 | end 15 | end 16 | if A.foo(1) != 2; puts "ng 2"; end 17 | 18 | # Capturing free vars 19 | var a = 1 20 | let g = fn(x: Int){ 21 | let b = 2 22 | let h = fn(y: Int, z: Int) { 23 | a + b + x + y + z 24 | } 25 | h(3, 4) 26 | } 27 | unless g(5) == 15; puts "ng 3"; end 28 | 29 | # Capturing bool 30 | let t = true 31 | let f1b = fn(x: Int) { t } 32 | unless f1b(0); puts "ng 4"; end 33 | 34 | # Updating value 35 | a = 0 36 | f1 = fn(x: Int) { a = 1 } 37 | f1(0) 38 | unless a == 1; puts "ng 5"; end 39 | 40 | class UpdateFromNestedClosure 41 | def self.run 42 | var a = 0 43 | [1].each do |i| 44 | [1].each do |j| 45 | a = 1 46 | end 47 | end 48 | unless a == 1; puts "ng UpdateFromNestedClosure"; end 49 | end 50 | end 51 | UpdateFromNestedClosure.run 52 | 53 | puts "ok" 54 | -------------------------------------------------------------------------------- /tests/sk/logical_operator.sk: -------------------------------------------------------------------------------- 1 | if false then puts "ng 1" end 2 | if not true then puts "ng 2.5" end 3 | if not(true and true) then puts "ng 3" end 4 | if (true and false) then puts "ng 4" end 5 | if (false and true) then puts "ng 5" end 6 | if (false and false) then puts "ng 6" end 7 | if not(true or true) then puts "ng 11" end 8 | if not(true or false) then puts "ng 12" end 9 | if not(false or true) then puts "ng 13" end 10 | if (false or false) then puts "ng 14" end 11 | 12 | puts "ok" 13 | -------------------------------------------------------------------------------- /tests/sk/loops_and_jumps.sk: -------------------------------------------------------------------------------- 1 | # while 2 | var i = 0 3 | while i < 3 4 | i += 1 5 | end 6 | unless i == 3 then puts "ng 1" end 7 | 8 | # nested while 9 | i = 0 10 | var j = 0 11 | while i < 1 12 | i += 1 13 | while j < 1 14 | j += 1 15 | end 16 | end 17 | 18 | # while in lambda 19 | [3].each do |n: Int| 20 | i = 0; while i < n 21 | i += 1 22 | break if i == 2 23 | end 24 | end 25 | unless i == 2 then puts "ng: while in lambda" end 26 | 27 | # break 28 | i = 0 29 | while i < 3 30 | if i == 3 then break end 31 | if i == 2 then break end 32 | i += 1 33 | end 34 | unless i == 2 then puts "ng 2" end 35 | 36 | # break from block 37 | var n = 0 38 | [1, 2, 3].each{|i: Int| 39 | n += i 40 | break if i == 2 41 | n += i 42 | } 43 | unless n == 4 then puts "ng: break from block" end 44 | 45 | # break in match expr 46 | class Issue376 47 | def self.foo 48 | loop do 49 | match 1 50 | when a 51 | p a 52 | break 53 | end 54 | end 55 | end 56 | end 57 | 58 | class A 59 | # return without arguments 60 | def self.wo_arg 61 | return if false 62 | 1 63 | end 64 | 65 | # return without arguments on the end of a method 66 | def self.wo_arg_end 67 | return 68 | end 69 | 70 | # return with an argument 71 | def self.w_arg -> Int 72 | return 1 if false 73 | 2 74 | end 75 | 76 | # return with an argument on the end of a method 77 | def self.w_arg_end -> Int 78 | return 1 79 | end 80 | 81 | # #266 82 | # def self.return_from_block -> Int 83 | # [1, 2, 3].each do |i: Int| 84 | # return 99 # Jumps to the end of the lambda, then 85 | # end # jumps to the end of `each`, and then 86 | # return 0 # jumps to the end of the method 87 | # end 88 | 89 | def self.return_from_fn -> Int 90 | let f = fn(){ return 1; 2 } # Jumps to the end of this fn 91 | f() 92 | end 93 | end 94 | A.wo_arg 95 | A.wo_arg_end 96 | unless A.w_arg == 2; puts "ng w_arg" end 97 | unless A.w_arg_end == 1; puts "ng w_arg_end" end 98 | #unless A.return_from_block == 99; puts "ng return_from_block" end 99 | unless A.return_from_fn == 1; puts "ng return_from_fn" end 100 | 101 | # #484 break from lambda in lambda 102 | class Issue484 103 | def self.run 104 | let a = Array.new 105 | "he\nllo".each_char do |b| 106 | break if b == "\n" 107 | a.push(b) 108 | end 109 | unless a == ["h", "e"]; puts "ng Issue484"; end 110 | end 111 | end 112 | Issue484.run 113 | 114 | puts "ok" 115 | -------------------------------------------------------------------------------- /tests/sk/match_expression.sk: -------------------------------------------------------------------------------- 1 | class A 2 | def self.foo -> Maybe 3 | if true 4 | Some.new(99) 5 | else 6 | None 7 | end 8 | end 9 | end 10 | 11 | let a = match A.foo 12 | when Some(n) 13 | n 14 | when None then 0 # KwThen is allowed here 15 | end 16 | unless a == 99; puts "failed"; end 17 | 18 | # Type inference of bindings 19 | let f = fn(){ 20 | Pair.new(11, false) 21 | } 22 | match f() 23 | when Pair(i, b) 24 | puts "ng Pair 1" if b # b is Bool 25 | else 26 | puts "ng Pair 2" 27 | end 28 | 29 | # Lvar scope of match clause (#359) 30 | enum E 31 | case E1(b: Int) 32 | case E2(c: Int) 33 | end 34 | class B 35 | def self.bar(a: E) -> String 36 | match a 37 | when E::E1(a) # This `a` shadows the parameter `a` 38 | "E1" 39 | when E::E2(x) 40 | "E2" 41 | end 42 | end 43 | end 44 | unless B.bar(E::E2.new(123)) == "E2"; puts "ng #359"; end 45 | 46 | puts "ok" 47 | -------------------------------------------------------------------------------- /tests/sk/method_call.sk: -------------------------------------------------------------------------------- 1 | class KwArgTest 2 | def foo(a: Int, b: String, c: Fn0) -> String 3 | "#{a}#{b}#{c()}" 4 | end 5 | 6 | def test 7 | unless foo(0, " "){ "~" } == "0 ~"; puts "fail 0 ~"; end 8 | unless foo(1, " ", fn(){ "~" }) == "1 ~"; puts "fail 1 ~"; end 9 | unless foo(2, b: " "){ "~" } == "2 ~"; puts "fail 2 ~"; end 10 | unless foo(a: 3, b: " "){ "~" } == "3 ~"; puts "fail 3 ~"; end 11 | unless foo(b: " ", a: 4){ "~" } == "4 ~"; puts "fail 4 ~"; end 12 | unless foo(a: 5, b: " ", c: fn(){ "~" }) == "5 ~"; puts "fail 5 ~"; end 13 | unless (foo 6, " " do "~" end) == "6 ~"; puts "fail 6 ~"; end 14 | end 15 | end 16 | KwArgTest.new.test 17 | 18 | class DefaultArgTest 19 | def self.x -> Int 20 | 99 21 | end 22 | 23 | def foo(a: Int, b: Int = 1, c: Int = DefaultArgTest.x) -> String 24 | "#{a}#{b}#{c}" 25 | end 26 | 27 | def test 28 | unless foo(1, 2, 3) == "123"; puts "fail 123"; end 29 | unless foo(1, 2) == "1299"; puts "fail 1299"; end 30 | unless foo(1) == "1199"; puts "fail 1199"; end 31 | end 32 | end 33 | DefaultArgTest.new.test 34 | 35 | puts "ok" 36 | -------------------------------------------------------------------------------- /tests/sk/multi_line_function_call.sk: -------------------------------------------------------------------------------- 1 | class Test 2 | def foo(a: Int, b: Int) -> Int 3 | return a + b 4 | end 5 | end 6 | 7 | var result = 0 8 | 9 | result = Test.new.foo(1, 10 | 2) 11 | 12 | unless result == 3 then puts "ng 1" end 13 | 14 | result = Test.new.foo(1,2); 15 | 16 | unless result == 3 then puts "ng 2" end 17 | 18 | puts "ok" 19 | -------------------------------------------------------------------------------- /tests/sk/nested_class.sk: -------------------------------------------------------------------------------- 1 | class A 2 | class B; end 3 | class C : B 4 | def foo(b: B) -> Int 5 | 1 6 | end 7 | end 8 | # TODO: If B is here, indexing C fails as it finds B#initialize 9 | #class B; end 10 | end 11 | let b = A::B.new 12 | unless A::C.new.foo(b) == 1 then puts "ng 1" end 13 | 14 | puts "ok" 15 | -------------------------------------------------------------------------------- /tests/sk/new_line_starts_with_dot.sk: -------------------------------------------------------------------------------- 1 | 123 2 | .to_s 3 | 4 | [1,2,3] 5 | .select{|i: Int| i % 2 == 0} 6 | .map{|i: Int| i * 2} 7 | 8 | puts "ok" 9 | -------------------------------------------------------------------------------- /tests/sk/parse_unary_plus.sk: -------------------------------------------------------------------------------- 1 | let a = +12 2 | let b = +a 3 | 4 | puts "ok" if a == b 5 | -------------------------------------------------------------------------------- /tests/sk/readable.sk: -------------------------------------------------------------------------------- 1 | HELLO_TXT_PATH = "tests/tmp/hello.txt" 2 | 3 | # Readable#lines, #read_line 4 | File.write(HELLO_TXT_PATH, "hello\nworld") 5 | File.open(HELLO_TXT_PATH) do |f| 6 | match f.lines 7 | when Ok(ls) 8 | unless ls == ["hello", "world"]; puts "#lines failed: \{ls}"; end 9 | when Fail(e) 10 | puts "#lines failed: #{e}" 11 | end 12 | end 13 | 14 | puts "ok" 15 | -------------------------------------------------------------------------------- /tests/sk/relational_operator.sk: -------------------------------------------------------------------------------- 1 | unless 0 < 1; puts "ng 1"; end 2 | if 0 < 0; puts "ng 2"; end 3 | unless 0 <= 1; puts "ng 3"; end 4 | unless 0 <= 0; puts "ng 4"; end 5 | unless 1 > 0; puts "ng 5"; end 6 | if 0 > 0; puts "ng 6"; end 7 | unless 1 >= 0; puts "ng 7"; end 8 | unless 0 >= 0; puts "ng 8"; end 9 | 10 | unless 0 == 0; puts "ng 9"; end 11 | if 0 == 1; puts "ng 10"; end 12 | unless 0 != 1; puts "ng 11"; end 13 | if 0 != 0; puts "ng 12"; end 14 | 15 | puts "ok" 16 | -------------------------------------------------------------------------------- /tests/sk/result.sk: -------------------------------------------------------------------------------- 1 | class TestTryBang 2 | def self.run 3 | match try_calc(1) 4 | when Ok(v) 5 | unless v == 2; puts "TestTryBang fail A"; end 6 | when Fail(e) 7 | puts "TestTryBang fail B" 8 | end 9 | match try_calc(-1) 10 | when Ok(v) 11 | puts "TestTryBang fail C" 12 | when Fail(e) 13 | # ok 14 | end 15 | end 16 | 17 | def self.try_calc(n: Int) -> Result 18 | let a = calc_inner(n).try! 19 | Ok.new(a + 1) 20 | end 21 | 22 | def self.calc_inner(n: Int) -> Result 23 | if n > 0 24 | Ok.new(n) 25 | else 26 | Fail.new(Error.new("failed")) 27 | end 28 | end 29 | end 30 | TestTryBang.run 31 | 32 | puts "ok" 33 | -------------------------------------------------------------------------------- /tests/sk/string.sk: -------------------------------------------------------------------------------- 1 | var a = Array.new 2 | 3 | # interpolation 4 | let x = 1; let y = 2 5 | unless "x=#{x}, y=#{y}" == "x=1, y=2"; puts "interpolation1: fail"; end 6 | let b = [1,2,3]; let c = [4,5] 7 | unless "b=\{b}, c=\{c}" == "b=[1, 2, 3], c=[4, 5]"; puts "interpolation2: fail"; end 8 | 9 | # split 10 | a = "a<>bc<>d".split("<>") 11 | unless a.length == 3; puts "split1: bad length"; end 12 | unless a[0] == "a"; puts "split1: fail a"; end 13 | unless a[1] == "bc"; puts "split1: fail b"; end 14 | unless a[2] == "d"; puts "split1: fail c"; end 15 | 16 | a = "abc".split("<>") 17 | unless a.length == 1; puts "split2: bad length"; end 18 | unless a[0] == "abc"; puts "split2: fail abc"; end 19 | 20 | a = "".split("<>") 21 | unless a.length == 0; puts "split3: bad length"; end 22 | 23 | a = "abc".split("") 24 | unless a.length == 1; puts "split4: bad length"; end 25 | unless a[0] == "abc"; puts "split4: fail abc"; end 26 | 27 | a = "abc<>".split("<>") 28 | unless a.length == 1; puts "split5: bad length"; end 29 | unless a[0] == "abc"; puts "split5: fail abc"; end 30 | 31 | puts "ok" 32 | -------------------------------------------------------------------------------- /tests/sk/time.sk: -------------------------------------------------------------------------------- 1 | let l = Time.local 2 | unless l.zone == Time::Zone::Local; puts "failed l.zone"; end 3 | 4 | let u = Time.utc 5 | unless u.zone == Time::Zone::Utc; puts "failed u.zone"; end 6 | 7 | unless u.inspect.class == String; puts "failed inspect"; end 8 | 9 | puts "ok" 10 | -------------------------------------------------------------------------------- /tests/sk/type_system.sk: -------------------------------------------------------------------------------- 1 | # `(String) -> Int` conforms to `(T) -> Void` (#197) 2 | [""].each do |s: String| 3 | 0 4 | end 5 | 6 | class A 7 | # `Never` conforms to `Int` 8 | def self.todo -> Int 9 | panic "todo" 10 | end 11 | end 12 | 13 | # Enum cases conforms to the enum class 14 | (fn(x: Maybe){ x })(Some.new(123)) 15 | (fn(x: Maybe){ x })(None) 16 | 17 | # Array literal type inference 18 | [] # Array 19 | class B; end 20 | class C : B; end 21 | [B.new, C.new] # Array 22 | 23 | # Modules 24 | module M; end 25 | class D : M 26 | def self.foo(x: Object); end 27 | end 28 | (fn(x: M){ D.foo(x) })(D.new) 29 | 30 | puts "ok" 31 | -------------------------------------------------------------------------------- /tests/sk/virtual_method.sk: -------------------------------------------------------------------------------- 1 | class A 2 | def foo -> Int 3 | 1 4 | end 5 | end 6 | 7 | class B : A 8 | def foo -> Int 9 | 2 10 | end 11 | 12 | def self.test(x: A) -> Int 13 | # A#foo if x is A, B#foo is x is B 14 | x.foo 15 | end 16 | end 17 | 18 | unless B.test(B.new) == 2; puts "ng 1"; end 19 | 20 | puts "ok" 21 | -------------------------------------------------------------------------------- /tests/sk/void.sk: -------------------------------------------------------------------------------- 1 | unless Void.to_s == "Void"; puts "ng #to_s"; end 2 | 3 | puts "ok" 4 | -------------------------------------------------------------------------------- /tests/sk/xxx-eq.sk: -------------------------------------------------------------------------------- 1 | var a = 1 2 | var b = 1.0 3 | 4 | a += 2 5 | if a != 3 then puts "ng +=" end 6 | a -= 2 7 | if a != 1 then puts "ng -=" end 8 | a *= 60 9 | if a != 60 then puts "ng *=" end 10 | b /= 2.0 11 | if b != 0.5 then puts "ng /=" end 12 | a = 7 13 | a %= 5 14 | if a != 2 then puts "ng %=" end 15 | 16 | puts "ok" 17 | -------------------------------------------------------------------------------- /tests/snapshots/erroneous__erroneous@class_definition__no_class_name.sk.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/erroneous.rs 3 | expression: compiler_output 4 | input_file: tests/erroneous/class_definition/no_class_name.sk 5 | --- 6 | Error: class name must start with A-Z but got Newline 7 | ╭─[tests/erroneous/class_definition/no_class_name.sk:1:6] 8 | │ 9 | 1 │ class 10 | │ ─ 11 | │ 12 | ───╯ 13 | ) 14 | -------------------------------------------------------------------------------- /tests/snapshots/erroneous__erroneous@class_definition__unknown_superclass.sk.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/erroneous.rs 3 | expression: compiler_output 4 | input_file: tests/erroneous/class_definition/unknown_superclass.sk 5 | --- 6 | Error: unknown type XXX in Namespace([]) 7 | ╭─[tests/erroneous/class_definition/unknown_superclass.sk:1:11] 8 | │ 9 | 1 │ class A : XXX 10 | │ ─┬─ 11 | │ ╰─── unknown type 12 | ───╯ 13 | 14 | -------------------------------------------------------------------------------- /tests/snapshots/erroneous__erroneous@method_call__extra_argument_error.sk.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/erroneous.rs 3 | expression: compiler_output 4 | input_file: tests/erroneous/method_call/extra_argument_error.sk 5 | --- 6 | Error: wrong number of arguments: expected 0, got 1 7 | ╭─[tests/erroneous/method_call/extra_argument_error.sk:5:2] 8 | │ 9 | 5 │ A.run(1) 10 | │ ───┬─── 11 | │ ╰───── expected 0, got 1 12 | ───╯ 13 | 14 | -------------------------------------------------------------------------------- /tests/snapshots/erroneous__erroneous@method_call__invalid_type.sk.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/erroneous.rs 3 | expression: compiler_output 4 | input_file: tests/erroneous/method_call/invalid_type.sk 5 | --- 6 | Error: the argument `str' of `Object#puts' should be String but got Int 7 | ╭─[tests/erroneous/method_call/invalid_type.sk:1:6] 8 | │ 9 | 1 │ puts 123 10 | │ ─┬─ 11 | │ ╰─── Int 12 | ───╯ 13 | 14 | -------------------------------------------------------------------------------- /tests/snapshots/erroneous__erroneous@parsing__semicolon_after_binary_op.sk.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/erroneous.rs 3 | expression: compiler_output 4 | input_file: tests/erroneous/parsing/semicolon_after_binary_op.sk 5 | --- 6 | Error: unexpected token: Semicolon 7 | ╭─[tests/erroneous/parsing/semicolon_after_binary_op.sk:1:3] 8 | │ 9 | 1 │ 1+;2 10 | │ ─ 11 | │ 12 | ───╯ 13 | ) 14 | -------------------------------------------------------------------------------- /tests/snapshots/erroneous__erroneous@parsing__semicolon_within_parentheses1.sk.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/erroneous.rs 3 | expression: compiler_output 4 | input_file: tests/erroneous/parsing/semicolon_within_parentheses1.sk 5 | --- 6 | Error: unexpected token: Semicolon 7 | ╭─[tests/erroneous/parsing/semicolon_within_parentheses1.sk:7:14] 8 | │ 9 | 7 │ Test.new.foo(;1,2) 10 | │ ─ 11 | │ 12 | ───╯ 13 | ) 14 | -------------------------------------------------------------------------------- /tests/snapshots/erroneous__erroneous@parsing__semicolon_within_parentheses2.sk.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/erroneous.rs 3 | expression: compiler_output 4 | input_file: tests/erroneous/parsing/semicolon_within_parentheses2.sk 5 | --- 6 | Error: unexpected token: Semicolon 7 | ╭─[tests/erroneous/parsing/semicolon_within_parentheses2.sk:7:15] 8 | │ 9 | 7 │ Test.new.foo(1;,2) 10 | │ ─ 11 | │ 12 | ───╯ 13 | ) 14 | -------------------------------------------------------------------------------- /tests/snapshots/erroneous__erroneous@parsing__semicolon_within_parentheses3.sk.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/erroneous.rs 3 | expression: compiler_output 4 | input_file: tests/erroneous/parsing/semicolon_within_parentheses3.sk 5 | --- 6 | Error: unexpected token: Semicolon 7 | ╭─[tests/erroneous/parsing/semicolon_within_parentheses3.sk:7:16] 8 | │ 9 | 7 │ Test.new.foo(1,;2) 10 | │ ─ 11 | │ 12 | ───╯ 13 | ) 14 | -------------------------------------------------------------------------------- /tests/snapshots/erroneous__erroneous@parsing__semicolon_within_parentheses4.sk.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/erroneous.rs 3 | expression: compiler_output 4 | input_file: tests/erroneous/parsing/semicolon_within_parentheses4.sk 5 | --- 6 | Error: unexpected token: Semicolon 7 | ╭─[tests/erroneous/parsing/semicolon_within_parentheses4.sk:7:17] 8 | │ 9 | 7 │ Test.new.foo(1,2;) 10 | │ ─ 11 | │ 12 | ───╯ 13 | ) 14 | -------------------------------------------------------------------------------- /tests/tmp/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiika-lang/shiika/c20d18b730d2f2fc2d80dd524285b7add2a4e876/tests/tmp/.keep --------------------------------------------------------------------------------