DEV Community

James [Undefined]
James [Undefined]

Posted on • Updated on

Introducing The Calypso Chronicles

Edit: Apparently this got featured in TWIR! Thanks editors! :)
If you don't already know what Calypso is, I'd recommend reading the post I wrote about it a little bit after when I wrote this:

I recently found this site and actually have started to consider using it. I'm probably going to use this as a new version of my blog as I've been meaning to use something else for a little while.

Anyway, I'm going to start a new, (semi-)weekly series called "The Calypso Chronicles" about my status on Calypso, both how it's going and my plans for the future. This is mostly just for myself (though feel free to discuss anything here if you want to!).

It will be split into two sections: one for Calypso and one for Odysseus.

Onto the content!

Calypso

Notes and Thoughts

  • I finally have a stable part of my API! Unfortunately, this is currently just calypso_error and calypso_filety but it's progress! (Especially because calypso_filety was SUPER messy, so I ended up rewriting it completely. It's a lot better now.)
  • I wrote a blog post called "The Calypso Manifesto" about why I made Calypso, what I want it to become, and what it's not going to become. If you'd like to read that, it's available here.

TODOs

Since pretty much everything is a TODO, I'm just going to write immediate TODOs here for now. :P

  • Finish redesign of the language, including error handling.
  • Rewrite/fix lexer according to new design
    • see https://github.com/calypso-lang/calypso/tree/wip-lexer-rewrite (WIP lexer rewrite branch, blocked on language redesign)
    • see

      track: Lexer tracking issue #17

      This is a tracking issue for the lexer, contained within calypso_parsing::lexer. This is marked as blocking as it is impossible for Calypso to function without the lexer fully functioning.

      Updates

      • 12/12/20: The lexer has been split into multiple files (and clippy::pedantic has been enabled for calypso_parsing) as of d9d988c.

      Checklist

      • [x] Lexer debug interface (int lex)
      • [x] Basic tokens (e.g. <, +=, ...)
      • [x] Keywords
      • [x] Identifiers
      • [ ] Literals
        • [x] Character literals
        • [x] String literals
        • [ ] Number literals
          • [ ] Signed integer literals
          • [ ] Unsigned integer literals
          • [ ] Float literals

Odysseus

Notes and Thoughts

  • Oh goodness generics can be cursed. In order to reduce boilerplate, I created these traits for the Odysseus bytecode builder:
//! Traits used to make construction of bytecode builders much easier. These
//! may use complex or generally irritating generics, but it is much better
//! than repeating a lot of boilerplate. It is recommended that you read the
//! documentation before implementing any of these traits.

/// A trait used for "entries" of a bytecode [`Parent`], similar to a
/// [`std::collections::hash_map::Entry`].
pub trait Entry {
    /// The parent type of this entry.
    type Parent: Parent<<Self as Entry>::Element>;
    /// The element type of this entry.
    type Element: Element;

    /// Get the bytecode element behind this entry, if finished.
    ///
    /// # Implementors
    ///
    /// Do not re-implement this function. It has an implementation that is
    /// already sufficient for generalized functionality.
    fn get<'p>(&self, parent: &'p <Self as Entry>::Parent) -> Option<&'p <Self as Entry>::Element> {
        if !self.is_finished(parent) {
            panic!("cannot get an unfinished bytecode element");
        }
        parent.get(self.id())
    }

    /// Check if the bytecode element has been finished.
    ///
    /// # Implementors
    ///
    /// Do not re-implement this function. It has an implementation that is
    /// already sufficient for generalized functionality.
    fn is_finished(&self, parent: &<Self as Entry>::Parent) -> bool {
        parent.is_finished(self.id())
    }

    /// Enter the builder context and use the closure to build the bytecode
    /// element.
    ///
    /// # Implementors
    ///
    /// Do not re-implement this function. It has an implementation that is
    /// already sufficient for generalized functionality.
    fn enter(
        &mut self,
        parent: &mut <Self as Entry>::Parent,
        f: impl FnOnce(
            <<Self as Entry>::Element as Element>::Builder,
        ) -> <<Self as Entry>::Element as Element>::Builder,
    ) -> &mut Self {
        if self.is_finished(parent) {
            panic!("cannot enter a finished bytecode element");
        }
        let builder = f(parent.create_builder(self.id()));
        self.internal_build(parent, builder);
        self
    }

    /// Finish the bytecode element.
    ///
    /// # Implementors
    ///
    /// Do not re-implement this function. It has an implementation that is
    /// already sufficient for generalized functionality.
    fn finish<'p>(
        &mut self,
        parent: &'p mut <Self as Entry>::Parent,
    ) -> &'p <Self as Entry>::Element {
        if self.is_finished(parent) {
            panic!("cannot build a finished bytecode element")
        }
        parent.finish(self.id())
    }

    /// Get the ID of the bytecode element behind the entry.
    fn id(&self) -> <<Self as Entry>::Element as Element>::Id;

    /// An internal function to register changes caused by a builder. Do not
    /// call this function as it may cause errors.
    fn internal_build(
        &mut self,
        parent: &mut <Self as Entry>::Parent,
        builder: <<Self as Entry>::Element as Element>::Builder,
    );
}

/// A bytecode parent, i.e. a structure that contains one or more types of
/// bytecode elements.
pub trait Parent<Child>
where
    Child: Element,
{
    /// Check if a bytecode element is finished. This function should return
    /// `false` if the ID was not present within the parent. This function is
    /// called by [`Entry::is_finished`].
    fn is_finished(&self, id: Child::Id) -> bool;

    /// Get a reference to a bytecode element. This function is called by
    /// [`Entry::get`].
    fn get(&self, id: Child::Id) -> Option<&Child>;

    /// Create a builder from the specified bytecode element ID.
    ///
    /// # Panics
    ///
    /// This function may panic if the ID was not present within the parent,
    /// depending on the implementor.
    fn create_builder(&mut self, id: Child::Id) -> Child::Builder;

    /// Finish the bytecode element with the specified ID.
    ///
    /// # Panics
    ///
    /// This function may panic if the ID was not present within the parent,
    /// depending on the implementor.
    fn finish(&mut self, id: Child::Id) -> &Child;
}

/// A bytecode element, i.e. a single element in the bytecode, whether
/// high-level or low-level.
///
/// For example, all these are bytecode elements:
/// - Modules
/// - Functions
/// - Blocks
pub trait Element {
    /// The entry type of this bytecode element.
    type Entry: Entry;
    /// The builder type of this bytecode element, i.e. a structure that can
    /// construct a bytecode element. It should store changes within its own
    /// structure, then its corresponding [`Entry`] type should implement the
    /// [`Entry::internal_build`] function that writes these changes to the
    /// [`Parent`].
    type Builder;
    /// The ID type of this bytecode element. It is required to be [`Copy`] to
    /// ensure that IDs are small and also because of limitations with
    /// borrowing and traits.
    type Id: PartialEq + Eq + Copy;
}
Enter fullscreen mode Exit fullscreen mode

I might just be lucky but these are the most cursed generics I've ever had to use. I'm not sure why I have to explicitly disambiguate (e.g. <<Self as Entry>::Element as Element>::Builder) but rustc complains at me if I don't. :'(

TODOs

  • After the redesign of Calypso is done, solidify ABI and ISA for Odysseus.
  • After that's done, I can complete the bytecode builder and then eventually the runtime. (And at that point, Odysseus is basically done! The runtime is going to take a while though.)

Conclusion

Basically, it's going well! :)

If you find anything that I might've missed or you'd like more information on, feel free to comment or otherwise contact me!

Otherwise, have a good morning/afternoon/evening/night/etc.!
-James

Top comments (0)