NQP (Not Quite Perl)

repository·main·Indexed 18 days ago

https://github.com/raku/nqp

A lightweight, Raku-like environment designed as a small runtime footprint for virtual machines, used for building compilers and libraries for platforms including MoarVM, the JVM, and JavaScript. It includes 6model, a framework for implementing object-oriented features through meta-objects and representations, and provides tools like nqp-m for executing scripts and a REPL.

Tokens
36.9K
Snippets
110
Records
179
Agent score
63%

What's inside nqp

  1. What is 6model?

    main
    6model is a framework for building implementations of object-oriented features (such as classes, interfaces, roles, or prototype objects). It does not provide a pre-built object system; instead, it provides the building blocks (meta-objects and representations) that allow you to define how your language's types behave and how they are stored in memory.
  2. Rubyish Syntax and Language Features

    main

    The rubyish implementation includes several Ruby-inspired features, but note that some behaviors (like string concatenation and truthiness) follow Rakuish rules.

    Supported Features

    • Strings: Simple strings '...', %q{...}, interpolating strings "...#{...}", and %Q{...}.
    • Quoted Words: %w[aa bb cc].
    • Control Flow: if...then...elsif...else...endif, unless...end, while, until, and for loops.
    • Loops:
      • Arrays: for val in [10, 20, 30] do ... end
      • Hashes: for kv in h do ... end (iterates by pairs).
    • Blocks/Closures: Supports lambda blocks and code block arguments (e.g., grep(arr) {|n| n % 2 == 0}).
    • Classes: Simple classes, objects with attributes, and method inheritance.
    • Named Parameters: Supports Ruby 2.x style named parameters: def foo(bar:42, baz:).
    • Heredocs: Literal <<EOF ... EOF and interpolating <<"END" ... END.
    • Constants: Package constants via Trig::PI = 3.1415926.

    Important Syntax Deviations (Rakuish behavior)

    • String Concatenation: Use the ~ operator. The + operator is strictly for arithmetic addition.
    • Comparisons:
      • Arithmetic: >, ==, <=.
      • String: gt, eq, le.
    • Truthiness: 0, '0', and '' are all considered false.
    • Hash Access: Use angle braces hash<key> or curlies hash{'key'}.
    • Hash Iteration: Iteration is done by pairs. Use the key and value built-ins to access elements during iteration.
    # Hash iteration example
    for item in {"apples" => 20, "bananas" => 35, "potatos" => 12}
        puts "#{key item} are #{value item} cents per Kg"
    end
  3. How meta-objects work in 6model

    main

    A meta-object is a standard object that defines the behavior of other objects. It contains methods that respond to specific events in a type's lifetime (like method dispatch or type creation).

    Key concepts:

    • Meta-objects are just objects: There is no special distinction in 6model between a 'normal' object and a 'meta-object'; a meta-object is simply an object serving a specific role.
    • Convention over enforcement: While 6model uses certain method names (like find_method) to drive internal logic, the specific names of your meta-object methods (like add_method) are conventions you define.
    • The .HOW macro: In NQP, use the .HOW macro to access the meta-object of an instance.
    • Prototype-friendly: Meta-object methods typically take the type object as the first parameter to support prototype-based OO systems.
    class SimpleMetaObject {
        has %!methods;
    
        method new_type() {
            my $meta-object := self.new();
            return nqp::newtype($meta-object, 'HashAttrStore');
        }
    
        method add_method($type, $name, $code) {
            %!methods{$name} := $code;
        }
    
        method find_method($type, $name) {
            %!methods{$name}
        }
    }
  4. How to implement an object-oriented type system with 6model

    main

    To implement an object system, follow this four-step workflow:

    1. Identify Types: Determine the OO types your language needs (e.g., classes, roles, or prototype objects).
    2. Pick a Representation: Choose how objects are stored in memory. For example, use a fixed-slot allocation for known attributes or a hash-like structure for dynamic attributes.
    3. Implement Meta-objects: Create meta-objects that respond to lifecycle events (e.g., type declaration, adding methods, inheritance, or dynamic type checks).
    4. Compile Declarations: Map your language's type declarations to calls on the meta-objects.

    Core Formula: meta-object (behavior) + representation (storage/allocation) = full implementation of an OO type.

  5. Create POD blocks

    main

    To define a multi-line documentation block, use the =begin and =end syntax. If you provide an identifier to the =begin tag, you must provide the exact same identifier to the =end tag to close the block.

    =begin my_block
    This is some multi-line
    documentation text.
    =end my_block
  6. Create single-line POD entries

    main

    You can create single-line documentation entries using the =identifier syntax. Any word can be used as an identifier except for the reserved word cut. These entries can be placed inside a POD block.

    =identifier This is a documentation entry
  7. Understand Parameterization Interning Data

    main

    To optimize memory and identity, the VM uses parameterization interning. When deserializing multiple compilation units (SCs) that contain identical parameterizations, the first unit's parameterization is reused ('wins').

    An entry in the Parameterization Interning Data section is included only if:

    1. The parametric type originates from a different SC.
    2. All parameters are objects from a different SC.

    Each entry follows this structure:

    • Base-1 index of the owning SC: 32-bit integer (0 is invalid; 1 is the current SC).
    • Index in owning SC: 32-bit integer locating the type object.
    • Object list index: 32-bit integer locating the type object to be interned.
    • STable list index: 32-bit integer locating the STable to be interned.
    • Parameter count: 32-bit integer.
    • Parameters: A sequence of object references for each parameter.
  8. How to use type objects for object instantiation

    main

    In 6model, a type object is the handle created when you pair a meta-class with a representation. It serves as the primary mechanism for creating new instances of an object.

    Depending on your language's paradigm, you should use type objects as follows:

    Class-based languages

    Use the type object as the handle for creating instances. You can:

    • Maintain a lookup table of type objects to manage instantiations.
    • Store the handle within a 'class object' that manages the instantiation process.

    Prototype-based languages

    • You may only need to create a single type object. Store it in a persistent location to use for future instantiations.
    • Alternatively, if instances are created via cloning, you can simply install the initial instance into the appropriate namespace and clone from there, bypassing the need to manually manage the type object handle.
  9. Handling languages without methods in 6model

    main

    If your target language supports objects but does not support methods, 6model is still a viable fit. You are not required to implement methods within the meta-object to handle add_method or find_method operations.

    Implications:

    • Your meta-object will not need to allocate storage for a method table.
    • The only overhead is a small amount of unused space in the s-table (specifically the method cache and v-table slots). This overhead is per-type, not per-instance, and is minimal (a few pointers per type).
  10. Understand the REPR Compose Protocol

    main

    In the 6model architecture, representations (REPRs) are responsible for memory layout, while meta-objects handle type-ness (dispatch, type checking, etc.).

    When a meta-object's type definition is complete, it must configure the REPR. This configuration is performed via the REPR composition protocol using the nqp::composetype primitive.

    To avoid complex object system dependencies, the protocol is defined entirely using standard arrays and hashes. A top-level hash is passed to the REPR, where each key represents a specific part of the protocol and its value provides the necessary configuration data.

  11. Understand Code Reference Types and Serialization

    main

    Code references are handled based on whether the code is static or dynamic:

    VM Static Code Reference

    Occurs when the VM has never invoked the code during compilation. The thunk for dynamic compilation is tagged with a STATIC_CODE_REF property and placed in the SC. Serialization includes the SC owning the code and the code ref index.

    Dynamic Compilation

    When dynamic compilation occurs, the SC is updated with the code ref to the compiled code, which is then tagged as a static code reference (including the owning SC).

    Closures

    • Tagged Closures: If a code object is not marked static but already has an assigned SC, it is serialized like a static code object.
    • Untagged Closures: If a code ref hasn't been tagged with an SC, the serializer:
      1. Traces back to the correct static code ref (via static lexical scope info).
      2. Creates an entry in the closures table indicating the static code ref to be cloned.
      3. Evaluates the outer scope context.
  12. Optimize meta-object performance with caches

    main

    Because meta-object method calls can be heavyweight, 6model allows meta-objects to publish "caches" that provide low-level, high-speed views of their data to the VM. The meta-object remains the authoritative source and is responsible for updating these caches.

    Supported cache types:

    • Name to method cache: A flat view (including inherited methods) used for fast dynamic method dispatch via hash table lookup.
    • v-table: An index to the method cache, mapping method calls to specific slots. Ideal for static or gradually typed languages to enable fast array-based lookups.
    • Type check cache: A low-level array used for fast is-a or does-a operations.