1. 15 Feb, 2021 1 commit
    • Yorick Peterse's avatar
      Support building with Rust 1.50 · d9b18a71
      Yorick Peterse authored
      Rust 1.50 includes some changes related to UNIX file descriptors,
      essentially making -1 a reserved value. This means we need to use a
      different value to indicate a file that has already been closed. We
      don't want to use Option<File> here as this would require double
      checking: once to unwrap the option, and once at the OS level to check
      if the file descriptor is valid. If/when we move to single ownership
      this won't be a problem, as we can just drop the entire File when an
      object is dropped.
      
      In addition, we fix some atomic operation deprecations, due to Rust 1.50
      deprecating the use of compare_and_swap() in favour of
      compare_exchange().
      d9b18a71
  2. 14 Feb, 2021 1 commit
    • Yorick Peterse's avatar
      Move and/or from Object to Boolean · 06b0c830
      Yorick Peterse authored
      In practise these methods are only used with booleans. By defining these
      methods only on Boolean, objects are free to define their own methods
      using these names.
      06b0c830
  3. 12 Feb, 2021 1 commit
  4. 11 Feb, 2021 2 commits
    • Yorick Peterse's avatar
      Don't implement ToArray for Array · 17a342b6
      Yorick Peterse authored
      Converting an Array to an Array doesn't make much sense, as the input is
      already an Array. In addition, users may think `to_array` creates a new
      Array when in reality it just returns `self`. Cloning the entire array
      and its values would be expensive, and wouldn't bring any benefits.
      17a342b6
    • Yorick Peterse's avatar
      Simplify the Error trait · 80cd6cba
      Yorick Peterse authored
      Error no longer requires a `message` method, instead only requiring the
      `ToString` implementation. This simplifies implementing the trait for
      custom error types, and means the user doesn't have to ask themselves
      whether they should use `to_string` or `message`.
      80cd6cba
  5. 07 Feb, 2021 1 commit
    • Yorick Peterse's avatar
      Use a "match" for detecting keywords · 69406505
      Yorick Peterse authored
      This is much simpler compared to the previous perfect hashing approach,
      and also performs better due to not needing as many method calls. In the
      future the compiler may optimise this further, but it's good enough for
      the time being.
      69406505
  6. 05 Feb, 2021 1 commit
  7. 27 Jan, 2021 1 commit
  8. 26 Jan, 2021 2 commits
    • Yorick Peterse's avatar
      Compile release builds with panic=abort · ec1caa33
      Yorick Peterse authored
      This reduces the binary size by about 200 KB, and is possible now that
      we no longer catch Rust panics in the VM. In addition, aborting
      immediately ensures we don't end up making the panic worse by running
      cleanup routines or other code.
      
      In dev mode we still use panic=unwind, as this may make debugging in
      certain cases easier.
      ec1caa33
    • Yorick Peterse's avatar
      Refactor std::test and remove panic handlers · d7034d52
      Yorick Peterse authored
      The std::test module has been refactored to no longer depend on global
      state. Instead, each module must define a `tests` method that takes a
      `Tests` type as its argument. This type is used for registering test
      groups and tests, without relying on global state (using a process). In
      addition, the assertion methods are now located in std::test itself, and
      use the naming pattern `assert_X`. They also throw instead of panicking,
      requiring the use of `try`.
      
      These changes are made to reduce the places where we panic, to
      provide a nicer unit testing interface, and to not rely on global state
      (provided through a process).
      
      As part of these changes, support for panic handlers has been removed.
      Panics are meant for errors that you can't recover from, but panic
      handlers contradict that goal. In addition, they introduce a fair bit of
      complexity, and were never truly guaranteed to run. The removal of panic
      handlers simplifies the VM, makes it more clear what panics are for,...
      d7034d52
  9. 24 Jan, 2021 1 commit
  10. 23 Jan, 2021 3 commits
  11. 22 Jan, 2021 1 commit
    • Yorick Peterse's avatar
      Rework the IO Error type · 9a33f792
      Yorick Peterse authored
      The type std::io::Error now is a custom class, wrapping both an IO error
      code and message. The error code makes it easier to determine at runtime
      what lead to a failure. Storing the message in addition to the error
      allows for custom error messages where needed.
      
      This fixes #236
      9a33f792
  12. 21 Jan, 2021 1 commit
    • Yorick Peterse's avatar
      Move more instructions over to external functions · 54ef5882
      Yorick Peterse authored
      The instructions moved over are instructions that aren't frequently
      used, or likely can't be made faster in the future with a JIT (when/if
      we add one). As such, these have been moved out of the interpreter loop,
      reducing the instruction set to 98 instructions.
      54ef5882
  13. 18 Jan, 2021 2 commits
    • Yorick Peterse's avatar
      Added some docs on external functions · f7e086cf
      Yorick Peterse authored
      f7e086cf
    • Yorick Peterse's avatar
      Add the ability to define/use external functions · 2c3c75fd
      Yorick Peterse authored
      Inko's instruction set was nearing almost 190 instructions, many of
      which are high-level instructions (e.g. writing to STDOUT). We want to
      add the ability to spawn OS commands, which would need (based on our
      current estimates) around 5 VM instructions.
      
      Every time an instruction is added, it adds more code to the main
      interpreter loop. We also need to extend the compiler to support the new
      instructions, and only have limited type-checking applied when these
      instructions are used.
      
      To solve these problems, we introduce the concept of external functions.
      An external function is a function with a fixed signature, registered in
      the VM to a name. The Inko runtime can load these functions using the
      ExternalFunctionLoad instruction, then call them using the
      ExternalFunctionCall instruction. Loading these functions is done using
      the syntax `extern def` like so:
      
          extern def stdout_write_string(input: String) !! String -> Integer
      
      These functions are then called ...
      2c3c75fd
  14. 16 Jan, 2021 1 commit
  15. 10 Jan, 2021 2 commits
    • Yorick Peterse's avatar
      Allow return/try/throw in lambdas · b655eb1b
      Yorick Peterse authored
      Lambdas now allow the use of the keywords `return`, `try`, and `throw`.
      This removes the need for the more verbose `local try` and `local throw`
      keywords, and allows early returns in lambdas; something that wasn't
      possible before.
      b655eb1b
    • Yorick Peterse's avatar
      Fix setting the exit status in the CLI · 1c437385
      Yorick Peterse authored
      The CLI wouldn't set the exit status if a program terminated without a
      panic or CLI related error (e.g. an invalid flag).
      1c437385
  16. 09 Jan, 2021 1 commit
  17. 31 Dec, 2020 1 commit
  18. 30 Dec, 2020 3 commits
    • Yorick Peterse's avatar
      Rename the "object" keyword to "class" · a2ff11d2
      Yorick Peterse authored
      Back when Inko was more dynamic/prototype based, the use of `object`
      made more sense. As we're planning on changing the memory layout to
      resemble a more traditional class based approach, the use of "object"
      instead of "class" can be confusing. In addition, users coming from
      other languages may wonder why we're not just using "class" instead of
      "object".
      
      This commit changes the "object" keyword to "class", solving these
      problems.
      a2ff11d2
    • Yorick Peterse's avatar
      Infer block return types as Any · 23a8a1bb
      Yorick Peterse authored
      When using a closure or lambda in a type signature, leaving out the
      return type infers it as Any; not Nil. Inferring the type as Nil can
      lead to soundness problems, such as this:
      
          import std::mirror
          import std::stdio::stdout
      
          def example(callback: do) -> Nil {
            callback.call
          }
      
          let result = example { 10 }
      
          stdout.print(mirror.reflect(result).inspect)
      
      This will print `10` to STDOUT, instead of `Nil`. By inferring
      `callback` as `do -> Any` instead of `do -> Nil` we can solve this, as
      the above `example` method wouldn't compile (as `Any` isn't compatible
      with `Nil`).
      23a8a1bb
    • Yorick Peterse's avatar
      Fix soundness hole when using type parameters · e88d3667
      Yorick Peterse authored
      When using type parameters inside a block, other types were incorrectly
      treated as being compatible with the parameter (assuming its
      requirements are met). This can lead to soundness issues, such as the
      following:
      
          def push!(A)(values: Array!(A)) {
            values.push(10)
          }
      
          push(Array.new('foo'))
      
      Here `push` is unsound because the exact type of `A` isn't known until
      runtime, meaning it's not safe to push `10` into the array.
      
      To solve this, we introduce the concept of "rigid type parameters".
      These are type parameters that can't be further inferred as/assigned new
      types, and other types aren't compatible with these type parameters.
      
      The term "rigid type parameter" originates from Haskell. Rigid type
      parameters are also called "skolem type parameter", but we don't use
      that term because it's unlikely to be widely known or understood.
      e88d3667
  19. 28 Dec, 2020 2 commits
  20. 23 Dec, 2020 5 commits
  21. 21 Dec, 2020 2 commits
    • Yorick Peterse's avatar
      Merge ffi::types into ffi and remove constants · 814d8e9d
      Yorick Peterse authored
      The contents of std::ffi::types have been moved into std::ffi. The
      various Type constants (VOID, Pointer, etc) have all been removed in
      favour of the module methods that returned these types. The
      corresponding unit tests have been updated to not depend on constants
      containing complex objects (e.g. an FFI library).
      814d8e9d
    • Yorick Peterse's avatar
      Refactor constants containing non-primitive values · bf5c6b62
      Yorick Peterse authored
      In preparation for #212, a
      variety of constants containing non-primitive data (e.g. arrays) have
      been refactored away. Some cases remain, such as
      std::test::DEFAULT_CLIENT. These requires further changes that we'll
      make in separate commits.
      bf5c6b62
  22. 20 Dec, 2020 2 commits
    • Yorick Peterse's avatar
      Add ByteArray.filled() · c7f4e904
      Yorick Peterse authored
      Similar to Array.filled(), this method is used to create a ByteArray
      filled with a given byte.
      c7f4e904
    • Yorick Peterse's avatar
      Don't desugar ?T at the parser level · 9ea8b1e5
      Yorick Peterse authored
      While semantically ?T and Option!(T) are the same, syntactically they
      are not. In addition, desugaring ?T at the parser level means a consumer
      of the AST can't determine if ?T or Option!(T) was used.
      
      This changes the self-hosting parser so it produces an OptionType AST
      node, instead of desugaring ?T into a Constant node.
      9ea8b1e5
  23. 19 Dec, 2020 2 commits
    • Yorick Peterse's avatar
      4e9322bf
    • Yorick Peterse's avatar
      Refactor how the prelude is set up · 68b50ea5
      Yorick Peterse authored
      The prelude used to be set up by importing std::prelude, and exposing
      some of its types to the modules that imported std::prelude. This
      approach only worked for exposing constants, not methods. In addition,
      it resulted in some standard library modules having access to the
      prelude, while others didn't, based on what order they were loaded in.
      
      This commit introduces a new setup. The module std::prelude is renamed
      to std::init, and is only used to load the core modules necessary for
      every Inko program. The prelude in turn is a small collection of imports
      that the compiler injects itself. This allows us to expose a few methods
      as part of the prelude, without having to extend the syntax.
      
      The prelude now exposes the following types and methods:
      
      * std::map::Map
      * std::option::Option
      * std::range::Range
      * std::loop.while()
      * std::loop.loop()
      
      In addition, the prelude isn't exposed to modules in the std namespace.
      This ensures that all standard library modules are treated the ...
      68b50ea5
  24. 18 Dec, 2020 1 commit
    • Yorick Peterse's avatar
      Make module entry points explicit · f96616bf
      Yorick Peterse authored
      The VM no longer depends on a module called "main", instead it starts
      with whatever module is marked as the entry point. The compiler in turn
      sets this entry point to the program to run at the start.
      
      In addition, the way module names are generated has changed a little.
      The main module is no longer always called "main". Instead, if it
      resides in the load path it's named according to that load path. If the
      module is outside of the load path, it's called "main". This ensures
      that for example running runtime/src/std/env.inko results in the module
      being called "std::env" and not "main", preventing the same code from
      running twice.
      f96616bf