PythonMonkey Documentation

repository·main·Indexed 21 days ago

https://github.com/distributive-network/pythonmonkey

PythonMonkey is a Mozilla SpiderMonkey JavaScript engine embedded into the Python runtime, enabling high-performance, zero-copy interoperability between JavaScript and Python. It allows developers to use JS libraries in Python and vice-versa through features like the eval() function, CommonJS module support via require(), and intelligent type coercion for strings, arrays, and objects. The project includes the pmjs shell and pmdb debugger.

Tokens
7.5K
Snippets
28
Records
36
Agent score
76%

What's inside pythonmonkey

  1. How data interchange works between Python and JavaScript

    main

    PythonMonkey provides seamless, high-performance data interchange between Python and JavaScript without serialization or pipes. Both engines run in the same process.

    Key Mapping Rules

    • Strings: Share immutable backing stores whenever possible to avoid memory-copy overhead for large strings.
    • Arrays and Objects:
      • JavaScript Array methods work on Python list objects via a JS API Proxy.
      • JavaScript Object methods work on Python dict objects.
      • Python list and dict are coerced to JS Array and Object respectively.
    • TypedArrays: Share mutable backing stores.
    • Dates: JavaScript Date objects are represented by Python datetime.datetime objects.
    • Functions: JS functions are automatically wrapped to behave like Python functions, and vice-versa.
    • Intrinsics: Booleans, numbers, null, and undefined are passed by value.
  2. Use CommonJS modules in PythonMonkey

    main

    PythonMonkey's pmjs starts a CommonJS subsystem with Node.js-like semantics (searching module.paths, understanding package.json, etc.).

    You can create CommonJS modules in either JavaScript or Python:

    JavaScript Modules

    Create a .js file and use the exports object to expose functionality.

    Python Modules

    Create a .py file and decorate a dictionary named exports with your module's members. This allows the module to be loaded via require() in either JavaScript or Python.

    // date-lib.js - require("./date-lib")
    const d = new Date();
    exports.today = `${d.getFullYear()}-${String(d.getMonth()).padStart(2,'0')}-${String(d.getDay()).padStart(2,'0')}`
    # date-lib.py - require("./date-lib")
    from datetime import date
    exports['today'] = date.today()
  3. Understand PythonMonkey type coercion and wrapping

    main

    PythonMonkey performs intelligent coercion or wrapping when moving variables between Python and JavaScript.

    Fast Transfers (Shared Backing Stores): PythonMonkey shares memory for ctypes, typed arrays, and strings, making these transfers extremely fast without copying.

    Automatic Wrapping: When shared memory isn't possible, PythonMonkey uses wrappers. Updates to an object in JavaScript will reflect in the corresponding Python Dict/List, as JavaScript methods are implemented on Python structures and vice-versa.

    Type Mapping Reference:

    Python TypeJavaScript TypeProxy Type (JS to Python)
    Stringstringpythonmonkey.JSStringProxy
    Integernumberpythonmonkey.bigint (for BigInt)
    Boolboolean-
    Functionfunctionpythonmonkey.JSFunctionProxy / JSMethodProxy
    Dictobjectpythonmonkey.JSObjectProxy
    ListArraypythonmonkey.JSArrayProxy
    datetimeDate-
    awaitablePromise-
    ErrorError-
    BufferArrayBuffer-
  4. Understand Program Modules in CommonJS

    main

    A Program Module (or main module) is a special module in the CommonJS subsystem with the following behaviors:

    • Variables defined in the outermost scope become properties of globalThis.
    • Returning from the outermost scope results in a syntax error.
    • The arguments variable is an Array containing the program's argument vector (command-line arguments).
    $ echo "console.log('hello world')" > my-program.js
    $ pmjs my-program.js
    hello world
    $ 
  5. Implement C++ functions and parameter handling

    main

    When writing functions, follow these patterns for parameters and return types:

    • Return Values: Prefer returning by value or by reference. Avoid pointers unless the return can be nullptr.
    • Input Parameters: Use values or const references for non-optional inputs. Use std::optional for optional by-value inputs.
    • Output/IO Parameters: Use non-const references for non-optional output/IO parameters. Use non-const pointers for optional output/IO parameters.
    • Parameter Ordering: Place all input-only parameters before any output parameters.
    • Attributes: Place function attributes like [[nodiscard]] on their own line.
  6. Follow C++ naming and scoping conventions

    main

    PythonMonkey uses a specific hierarchy for naming and scoping to maintain consistency across C and C++ code:

    • Root Namespace: All declarations must reside in a namespace, starting with the root namespace DCP.
    • Namespace Mapping: Each namespace must be represented by a corresponding subdirectory in both include and source directories.
    • C Macro Emulation: Since C lacks namespaces, emulate them using naming conventions for macros and constants:
      • Format: DCP_NamespaceName_StructName_macroName.
    • Internal Linkage: Use anonymous namespaces or static for definitions that do not need to be referenced outside the current source file (do not use these in header files).
    • Avoid: Do not use using directives to import entire namespaces, do not use namespace aliases at namespace scope in headers, and do not declare anything in the std namespace.
    #if !defined(DCP_NamespaceName_StructName_)
      #define DCP_NamespaceName_StructName_
    
      #define DCP_NamespaceName_StructName_macroName(argumentName) !(argumentName)
    
      #define DCP_NamespaceName_StructName_definitionName 42
    
      namespace DCP::NamespaceName {
        struct StructName {
          int functionName(int argumentName) {
            int variableName = argumentName;
            return variableName;
          }
          int memberName;
        };
      }
    #endif
  7. Use PythonMonkey to execute JavaScript

    main

    PythonMonkey allows you to run JavaScript code within a Python environment. The core mechanism is the eval function, which accepts JavaScript code and returns values coerced into Python types.

    Basic Usage

    Import pythonmonkey and use eval to run a JS string. The result is automatically converted to the appropriate Python type.

    import pythonmonkey as pm
    
    # Evaluate a JS arrow function and call it
    hello = pm.eval("() => {return 'Hello from Spidermonkey!'}")
    print(hello())
    # Output: 'Hello from Spidermonkey!'

    Quick Example (tl;dr)

    from pythonmonkey import eval as js_eval
    
    # Evaluate console.log and call it with a string
    js_eval("console.log")('hello, world')
    import pythonmonkey as pm
    hello = pm.eval("() => {return 'Hello from Spidermonkey!'}")
    hello()
  8. Use the pmjs REPL

    main

    The pmjs REPL allows for interactive Python and JavaScript execution.

    Key features:

    • .help: Displays a help menu.
    • Python Integration: You can run Python code directly using the .python prefix.
    • Variable History: The REPL stores evaluated Python expressions in special variables named $1, $2, etc., allowing you to access previous results.
    $ pmjs
    
    Welcome to PythonMonkey v1.0.0.
    Type ".help" for more information.
    > .python import sys
    > .python sys.path
    $1 = { '0': '/home/wes/git/pythonmonkey2', ... }
    > $1[3]
    '/usr/lib/python3.10/lib-dynload'
  9. Configure CMake for modern C++ standards

    main

    To ensure compliance with the project's requirement for modern C++ and to prevent the use of non-standard extensions, configure your CMakeLists.txt with the following settings. This enforces C++17 and disables compiler-specific extensions.

    set(CMAKE_CXX_EXTENSIONS OFF)
    set(CMAKE_CXX_STANDARD "17")
    set(CMAKE_CXX_STANDARD_REQUIRED ON)
  10. Format C++ code and statements

    main

    To maintain a consistent codebase, adhere to these formatting rules:

    • Indentation: Use 2 spaces; no tabs allowed.
    • Braces: Always enclose the body of if, for, do, while, and switch in braces {}.
    • Line Length: Aim for a limit of 80 characters.
    • Switch Statements: Do not indent case statements. Use [[fallthrough]]; to annotate intentional fall-throughs.
    • Multi-line splitting: Match indentation to the nesting level of braces/brackets/parentheses. Place the opening brace on the same line as the statement.
    if (
      condition && (
        anotherCondition || possiblyAnotherCondition
      )
    ) {
      do {
        ++index;
      } while (index < total);
    } else {
      return false;
    }
  11. Run an asynchronous Python event-loop for JS Promises

    main

    To use setTimeout or leverage Promise <=> awaitable coercion, you must have an event-loop running in Python. This is typically done using asyncio.

    import asyncio
    import pythonmonkey as pm
    
    async def async_fn():
        # Using setTimeout in JS
        await pm.eval("""
            new Promise((resolve) => setTimeout((...args) => {
                console.log(args);
                resolve();
              }, 1000, 42, "abc")
            )
        """)
        # Using Python awaitable in JS
        await pm.eval("async (x) => await x")(asyncio.sleep(0.5))
    
    asyncio.run(async_fn())