diff --git a/docs/tutorials/treetraversal.mdx b/docs/tutorials/treetraversal.mdx new file mode 100644 index 0000000..c5d704e --- /dev/null +++ b/docs/tutorials/treetraversal.mdx @@ -0,0 +1,361 @@ +import DocusaurusTreeVisualization from "@site/src/components/TreeTraversal/DocusaurusWrapper"; + +# Traversing a Polyglot Tree with MetaCall + +A comprehensive guide to implementing and visualizing tree traversal across multiple programming languages using MetaCall. + +## Table of Contents + +- [Overview](#overview) +- [Prerequisites](#prerequisites) +- [Project Setup](#project-setup) +- [Implementation Details](#implementation-details) +- [Tree Visualization](#tree-visualization) +- [Running the Project](#running-the-project) +- [Technical Deep Dive](#technical-deep-dive) + +## Overview + +This tutorial demonstrates how to create a binary tree where each node is implemented in a different programming language: + +- Root Node: Python +- Middle Node: JavaScript +- Leaf Node: C + +The implementation showcases MetaCall's ability to seamlessly integrate functions across different programming languages in a single process. + +## Prerequisites + +- Linux environment +- Git +- CMake +- Basic knowledge of: + - Python + - JavaScript + - C + - React/Next.js (for visualization) +- MetaCall installation + +## Project Setup + +### 1. System Dependencies + +```bash +sudo apt-get update +sudo apt-get install -y --no-install-recommends \ + ca-certificates \ + git \ + liburing-dev \ + cmake \ + curl +``` + +### 2. MetaCall Installation + +```bash +git clone --branch v0.8.7 https://github.com/metacall/core +cd core +./tools/metacall-environment.sh release base nodejs c python +sudo mkdir build && cd build +sudo cmake \ + -DOPTION_BUILD_LOADERS_C=On \ + -DOPTION_BUILD_LOADERS_NODE=On \ + -DOPTION_BUILD_LOADERS_PY=On \ + -DOPTION_BUILD_PORTS=On \ + -DOPTION_BUILD_PORTS_NODE=On \ + -DOPTION_BUILD_PORTS_PY=On \ + -DOPTION_BUILD_DETOURS=Off \ + -DOPTION_BUILD_SCRIPTS=Off \ + -DOPTION_BUILD_TESTS=Off \ + -DOPTION_BUILD_EXAMPLES=Off \ + .. +sudo cmake --build . --target install +sudo ldconfig /usr/local/lib +``` + +### 3. Environment Configuration + +```bash +export LOADER_LIBRARY_PATH="/usr/local/lib" +export LOADER_SCRIPT_PATH="$(pwd)" +``` + +## Implementation Details + +### Leaf Node (C) + +`leaf_Node.c`: + +```c +#include + +void leaf_node(int currentNode) { + printf("%d ", currentNode); +} +``` + +### Middle Node (JavaScript) + +`middleNode.js`: + +```javascript +const { metacall, metacall_load_from_file } = require("metacall"); +metacall_load_from_file("c", ["leaf_Node.c"]); + +function traverse_middleNode(currentNode) { + // Left child + metacall("leaf_node", 2 * currentNode); + + // Current node + process.stdout.write(currentNode.toString() + " "); + + // Right child + metacall("leaf_node", 2 * currentNode + 1); +} + +module.exports = traverse_middleNode; +``` + +### Root Node (Python) + +`rootNode.py`: + +```python +from metacall import metacall, metacall_load_from_file + +# Load JavaScript middle node +metacall_load_from_file('node', ['middleNode.js']) + +currentNode = 1 + +# Traverse tree +metacall('traverse_middleNode', 2 * currentNode) # Left subtree +print(currentNode, end=" ", flush=True) # Current node +metacall('traverse_middleNode', 2 * currentNode + 1) # Right subtree +``` + +## Tree Visualization + +The visualization component is built with Next.js and provides an interactive way to understand the tree traversal process. Let's dive into the implementation details. + + + +### Component Architecture + +```typescript +interface TreeNode { + id: number; + language: "python" | "javascript" | "c"; + left?: number; + right?: number; + x: number; + y: number; +} +``` + +### Key Features + +1. **Tree Generation** + +```typescript +const generateTree = (input: string): TreeNode[] => { + const values = input.split(",").map(Number); + const height = Math.floor(Math.log2(values.length)) + 1; + + return values.map((value, i) => { + const level = Math.floor(Math.log2(i + 1)); + // Calculate x,y coordinates for visual layout + const x = calculateXPosition(level, i); + const y = calculateYPosition(level, height); + + // Assign language based on node level + const language = + level === 0 ? "python" : level === height - 1 ? "c" : "javascript"; + + return { + id: value, + language, + left: 2 * i + 1 < values.length ? values[2 * i + 1] : undefined, + right: 2 * i + 2 < values.length ? values[2 * i + 2] : undefined, + x, + y, + }; + }); +}; +``` + +2. **Traversal Implementation** + +```typescript +const getTraversalOrder = (type: string, nodes: TreeNode[]): number[] => { + const result: number[] = []; + + const traverse = (nodeId: number) => { + const node = nodes.find((n) => n.id === nodeId); + if (!node) return; + + // Preorder: Root -> Left -> Right + if (type === "preorder") result.push(node.id); + + // Traverse left subtree + if (node.left !== undefined) traverse(node.left); + + // Inorder: Left -> Root -> Right + if (type === "inorder") result.push(node.id); + + // Traverse right subtree + if (node.right !== undefined) traverse(node.right); + + // Postorder: Left -> Right -> Root + if (type === "postorder") result.push(node.id); + }; + + if (nodes.length > 0) traverse(nodes[0].id); + return result; +}; +``` + +3. **Animation Control** + +```typescript +useEffect(() => { + if (isAnimating && currentStep < traversalSteps.length - 1) { + const timer = setTimeout(() => { + const nextStep = currentStep + 1; + setCurrentStep(nextStep); + setActiveNode(traversalSteps[nextStep]); + + // Highlight the path from parent to current node + const currentNode = tree.find((n) => n.id === traversalSteps[nextStep]); + if (currentNode) { + const parentNode = tree.find( + (n) => n.left === currentNode.id || n.right === currentNode.id + ); + if (parentNode) { + setActivePath(`${parentNode.id}-${currentNode.id}`); + } + } + }, 1000); + + return () => clearTimeout(timer); + } +}, [isAnimating, currentStep, traversalSteps, tree]); +``` + +### Visual Elements + +1. **Node Colors** + +```typescript +const getNodeClasses = (nodeId) => { + const node = tree.find((n) => n.id === nodeId); + if (!node) return styles.node; + const isActive = nodeId === activeNode; + return `${styles.node} ${styles[`node-${node.language}`]} ${ + isActive ? styles.active : "" + }`; +}; +``` + +2. **Arrow Connections** + +```typescript + +``` + +### Traversal Types + +The visualization supports three types of tree traversal: + +1. **Inorder (Left -> Root -> Right)** + + - Visit left subtree + - Visit root + - Visit right subtree + +2. **Preorder (Root -> Left -> Right)** + + - Visit root + - Visit left subtree + - Visit right subtree + +3. **Postorder (Left -> Right -> Root)** + - Visit left subtree + - Visit right subtree + - Visit root + +## Running the Project + +1. Start MetaCall CLI: + +```bash +metacallcli rootNode.py +``` + +2. Launch visualization (if using Next.js): + +```bash +npm run dev +``` + +## Technical Deep Dive + +### MetaCall Integration + +The project demonstrates three key MetaCall features: + +1. **Cross-Language Loading** + +```python +metacall_load_from_file('node', ['middleNode.js']) +``` + +2. **Function Execution** + +```python +metacall('traverse_middleNode', currentNode) +``` + +3. **Data Type Handling** + +- Numbers are automatically converted between languages +- Stdout is properly managed across language boundaries + +### Tree Traversal Logic + +The traversal follows these steps: + +1. Python root node initiates traversal +2. JavaScript middle node processes: + - Left child (C) + - Current node + - Right child (C) +3. C leaf nodes print their values + +### Performance Considerations + +- MetaCall overhead is minimal for this use case +- Memory usage is efficient due to: + - No redundant data copying between languages + - Direct function calls across language boundaries + +## Resources + +- [MetaCall Documentation](https://metacall.github.io/doc/docs/getting-started) +- [Project GitHub Repository](https://github.com/metacall/doc) +- [Next.js Documentation](https://nextjs.org/docs) + +## Contributing + +Contributions are welcome! Please: + +1. Fork the repository +2. Create a feature branch +3. Submit a pull request diff --git a/package-lock.json b/package-lock.json index 3cf8745..855949e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,8 @@ "prism-react-renderer": "^2.3.0", "react": "^19.0.0", "react-dom": "^19.0.0", - "react-icons": "^5.5.0" + "react-icons": "^5.5.0", + "react-xarrows": "^2.0.2" }, "devDependencies": { "@docusaurus/module-type-aliases": "3.7.0", @@ -15338,6 +15339,24 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/react-xarrows": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/react-xarrows/-/react-xarrows-2.0.2.tgz", + "integrity": "sha512-tDlAqaxHNmy0vegW/6NdhoWyXJq1LANX/WUAlHyzoHe9BwFVnJPPDghmDjYeVr7XWFmBrVTUrHsrW7GKYI6HtQ==", + "license": "MIT", + "dependencies": { + "@types/prop-types": "^15.7.3", + "lodash": "^4.17.21", + "prop-types": "^15.7.2" + }, + "funding": { + "type": "individual", + "url": "https://www.paypal.com/donate?hosted_button_id=CRQ343F9VTRS8" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", diff --git a/package.json b/package.json index 35767d5..31d869d 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,8 @@ "prism-react-renderer": "^2.3.0", "react": "^19.0.0", "react-dom": "^19.0.0", - "react-icons": "^5.5.0" + "react-icons": "^5.5.0", + "react-xarrows": "^2.0.2" }, "devDependencies": { "@docusaurus/module-type-aliases": "3.7.0", diff --git a/src/components/TreeTraversal/DocusaurusWrapper.jsx b/src/components/TreeTraversal/DocusaurusWrapper.jsx new file mode 100644 index 0000000..faaa557 --- /dev/null +++ b/src/components/TreeTraversal/DocusaurusWrapper.jsx @@ -0,0 +1,15 @@ +import React from 'react'; +import BrowserOnly from '@docusaurus/BrowserOnly'; +import TreeVisualization from '../TreeTraversal/index'; + +export default function DocusaurusTreeVisualization() { + return ( + + {() => ( +
+ +
+ )} +
+ ); +} diff --git a/src/components/TreeTraversal/index.js b/src/components/TreeTraversal/index.js new file mode 100644 index 0000000..4729abb --- /dev/null +++ b/src/components/TreeTraversal/index.js @@ -0,0 +1,238 @@ +import { useState, useEffect, Fragment } from 'react'; +import Xarrow, { Xwrapper } from 'react-xarrows'; +import styles from './styles.module.css'; + +export default function TreeVisualization() { + const [array, setArray] = useState("1,2,3,4,5,6,7"); + const [traversalType, setTraversalType] = useState("inorder"); + const [activeNode, setActiveNode] = useState(null); + const [activePath, setActivePath] = useState(null); + const [traversalSteps, setTraversalSteps] = useState([]); + const [currentStep, setCurrentStep] = useState(-1); + const [isAnimating, setIsAnimating] = useState(false); + const [tree, setTree] = useState([]); + + const generateTree = (input) => { + const values = input + .split(",") + .map((v) => v.trim()) + .filter((v) => v !== "") + .map(Number); + const n = values.length; + if (n === 0) return []; + const height = Math.floor(Math.log2(n)) + 1; + const nodes = values.map((value, i) => { + const level = Math.floor(Math.log2(i + 1)); + const posInLevel = i - (Math.pow(2, level) - 1); + const numNodesInLevel = Math.pow(2, level); + const x = ((posInLevel + 0.5) / numNodesInLevel) * 100; + const y = height > 1 ? (level / (height - 1)) * 90 + 5 : 50; + let language = "javascript"; + if (level === 0) language = "python"; + else if (level === height - 1) language = "c"; + let left = undefined; + let right = undefined; + if (2 * i + 1 < n) left = values[2 * i + 1]; + if (2 * i + 2 < n) right = values[2 * i + 2]; + return { id: value, language, left, right, x, y }; + }); + return nodes; + }; + + const getTraversalOrder = (type, nodes) => { + const result = []; + const traverse = (nodeId) => { + const node = nodes.find((n) => n.id === nodeId); + if (!node) return; + if (type === "preorder") result.push(node.id); + if (node.left !== undefined) traverse(node.left); + if (type === "inorder") result.push(node.id); + if (node.right !== undefined) traverse(node.right); + if (type === "postorder") result.push(node.id); + }; + if (nodes.length > 0) { + traverse(nodes[0].id); + } + return result; + }; + + const handleExecute = () => { + const newTree = generateTree(array); + setTree(newTree); + const steps = getTraversalOrder(traversalType, newTree); + setTraversalSteps(steps); + setCurrentStep(-1); + setIsAnimating(true); + setActiveNode(null); + setActivePath(null); + }; + + useEffect(() => { + if (isAnimating && currentStep < traversalSteps.length - 1) { + const timer = setTimeout(() => { + const nextStep = currentStep + 1; + setCurrentStep(nextStep); + setActiveNode(traversalSteps[nextStep]); + const currentNode = tree.find((n) => n.id === traversalSteps[nextStep]); + if (currentNode) { + const parentNode = tree.find( + (n) => n.left === currentNode.id || n.right === currentNode.id + ); + if (parentNode) { + setActivePath(`${parentNode.id}-${currentNode.id}`); + } + } + }, 1000); + return () => clearTimeout(timer); + } else if (currentStep === traversalSteps.length - 1) { + setIsAnimating(false); + } + }, [isAnimating, currentStep, traversalSteps, tree]); + + const getNodeClasses = (nodeId) => { + const node = tree.find((n) => n.id === nodeId); + if (!node) return styles.node; + const isActive = nodeId === activeNode; + return `${styles.node} ${styles[`node-${node.language}`]} ${ + isActive ? styles.active : '' + }`; + }; + + const getArrowColor = (fromId, toId) => { + const pathId = `${fromId}-${toId}`; + if (activePath === pathId) { + const childNode = tree.find((n) => n.id === toId); + if (childNode) { + switch (childNode.language) { + case "python": + return "blue"; + case "javascript": + return "yellow"; + case "c": + return "red"; + default: + return "black"; + } + } + } + return "black"; + }; + + return ( +
+
+

Binary Tree Traversal Visualizer

+

+ This interactive tool demonstrates how binary tree traversals work. + The blue node is the root + (Python), + yellow nodes are + intermediate (JavaScript), and the{" "} + red nodes are leaves (C). + Click "Execute Traversal" to see the animated traversal sequence. +

+
+ +
+ setArray(e.target.value)} + className={styles.input} + /> + + + + +
+ +
+ + {tree.map((node) => ( + + {node.left !== undefined && ( + + )} + {node.right !== undefined && ( + + )} + + ))} + + + {tree.map((node) => ( +
+ {node.id} +
+ ))} +
+ +
+
+ Traversal Sequence:{" "} + {traversalSteps.slice(0, currentStep + 1).join(" → ")} +
+
+
+
+ Python (Root) +
+
+
+ JavaScript (Middle) +
+
+
+ C (Leaf) +
+
+
+
+ ); +} diff --git a/src/components/TreeTraversal/styles.module.css b/src/components/TreeTraversal/styles.module.css new file mode 100644 index 0000000..d15587e --- /dev/null +++ b/src/components/TreeTraversal/styles.module.css @@ -0,0 +1,182 @@ +.container { + min-height: 100vh; + background-color: rgb(249, 250, 251); + padding: 2rem; +} + +.header { + text-align: center; + margin-bottom: 2rem; +} + +.header h1 { + font-size: 1.875rem; + font-weight: bold; + color: rgb(31, 41, 55); +} + +.header p { + margin-top: 0.5rem; + color: rgb(75, 85, 99); + max-width: 36rem; + margin-left: auto; + margin-right: auto; +} + +.blue-text { + color: rgb(59, 130, 246); + font-weight: bold; +} + +.yellow-text { + color: rgb(234, 179, 8); + font-weight: bold; +} + +.red-text { + color: rgb(239, 68, 68); + font-weight: bold; +} + +/* Form controls */ +.controls { + width: 100%; + max-width: 28rem; + margin: 0 auto 2rem auto; + display: flex; + flex-direction: column; + gap: 1rem; +} + +.input, +.select { + width: 100%; + padding: 0.5rem; + border: 1px solid rgb(209, 213, 219); + border-radius: 0.375rem; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); +} + +.input:focus, +.select:focus { + outline: none; + ring: 2px solid rgb(59, 130, 246); +} + +.button { + width: 100%; + padding: 0.5rem; + background-color: rgb(59, 130, 246); + color: white; + border: none; + border-radius: 0.375rem; + cursor: pointer; + font-weight: 500; +} + +.button:disabled { + opacity: 0.7; + cursor: not-allowed; +} + +/* Tree visualization */ +.tree-container { + position: relative; + width: 100%; + max-width: 56rem; + height: 500px; + margin: 0 auto; + border: 1px solid rgb(209, 213, 219); + border-radius: 0.5rem; + background-color: white; + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); +} + +.node { + position: absolute; + width: 4rem; + height: 4rem; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.125rem; + font-weight: bold; + border: 2px solid rgb(209, 213, 219); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + transition: background-color 0.5s; + z-index: 1; + transform: translate(-50%, -50%); +} + +.node-python { + background-color: rgb(191, 219, 254); +} + +.node-python.active { + background-color: rgb(59, 130, 246); +} + +.node-javascript { + background-color: rgb(254, 240, 138); +} + +.node-javascript.active { + background-color: rgb(234, 179, 8); +} + +.node-c { + background-color: rgb(254, 202, 202); +} + +.node-c.active { + background-color: rgb(239, 68, 68); +} + +/* Sequence and legend */ +.sequence { + max-width: 56rem; + margin: 2rem auto 0 auto; +} + +.sequence-text { + text-align: center; + font-size: 1.125rem; + color: rgb(55, 65, 81); + margin-bottom: 1rem; +} + +.sequence-text span { + font-weight: bold; +} + +.legend { + display: flex; + justify-content: center; + gap: 1.5rem; + font-size: 0.875rem; +} + +.legend-item { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.legend-dot { + width: 1rem; + height: 1rem; + border-radius: 50%; +} + +.legend-dot-python { + background-color: rgb(59, 130, 246); +} + +.legend-dot-javascript { + background-color: rgb(234, 179, 8); +} + +.legend-dot-c { + background-color: rgb(239, 68, 68); +}