From 795fb4290084bcdaea43879df3105358c9f8c506 Mon Sep 17 00:00:00 2001 From: rattatwinko Date: Sat, 8 Nov 2025 18:16:10 +0100 Subject: [PATCH] some new things, mostly not working or finished --- cube/fibonnacci.py | 1 - fib/fib.c | 46 + fib/fib.java | 22 + fib/fib.py | 41 + fib/sfib.c | 22 + functions/firstlast.py | 5 + functions/mark/main.py | 70 + functions/passwort.py | 52 + functions/sekunden.py | 57 + functions/summe.py | 18 + functions/umrechner.py | 31 + key/key.py | 143 ++ key/main.py | 847 ++++++++ math/LaunchProcess0279191077.debugTarget | 50 + math/complex.c | 98 + math/complex.h | 25 + math/main.cpp | 227 ++ math/mandelbrot.c | 61 + math/mandelbrot.h | 12 + math/mandelbrot.hlsl | 24 + niacin/compiler.py | 2403 ++++++++++++++++++++++ niacin/cpp/Makefile | 21 + niacin/cpp/main.c | 76 + niacin/cpp/sample.popclass | Bin 0 -> 51 bytes niacin/cpp/vm_core.c | 285 +++ niacin/cpp/vm_types.h | 120 ++ niacin/sample.popasm | 16 + niacin/sample.popclass | Bin 0 -> 51 bytes niacin/sample.src | 4 + simulations/donut.c/donutdbg.debugtarget | 13 + 30 files changed, 4789 insertions(+), 1 deletion(-) create mode 100644 fib/fib.c create mode 100644 fib/fib.java create mode 100644 fib/fib.py create mode 100644 fib/sfib.c create mode 100644 functions/firstlast.py create mode 100644 functions/mark/main.py create mode 100644 functions/passwort.py create mode 100644 functions/sekunden.py create mode 100644 functions/summe.py create mode 100644 functions/umrechner.py create mode 100644 key/key.py create mode 100644 key/main.py create mode 100644 math/LaunchProcess0279191077.debugTarget create mode 100644 math/complex.c create mode 100644 math/complex.h create mode 100644 math/main.cpp create mode 100644 math/mandelbrot.c create mode 100644 math/mandelbrot.h create mode 100644 math/mandelbrot.hlsl create mode 100644 niacin/compiler.py create mode 100644 niacin/cpp/Makefile create mode 100644 niacin/cpp/main.c create mode 100644 niacin/cpp/sample.popclass create mode 100644 niacin/cpp/vm_core.c create mode 100644 niacin/cpp/vm_types.h create mode 100644 niacin/sample.popasm create mode 100644 niacin/sample.popclass create mode 100644 niacin/sample.src create mode 100644 simulations/donut.c/donutdbg.debugtarget diff --git a/cube/fibonnacci.py b/cube/fibonnacci.py index 915e910..d25cbec 100644 --- a/cube/fibonnacci.py +++ b/cube/fibonnacci.py @@ -3,7 +3,6 @@ import tkinter as tk class Fibonacci: def s5(self, n, r): - """Generate Fibonacci spiral polar coordinates""" spirals = [] phi = (1 + 5 ** 0.5) / 2 # golden ratio for i in range(n + 1): diff --git a/fib/fib.c b/fib/fib.c new file mode 100644 index 0000000..20e1bdf --- /dev/null +++ b/fib/fib.c @@ -0,0 +1,46 @@ +#include +#include + +// tiny struct to hold a 2x2 matrix +typedef struct { + uint64_t m00, m01, m10, m11; +} Matrix2x2; + +// multiply two 2x2 matrices together +Matrix2x2 multiply(Matrix2x2 a, Matrix2x2 b) { + // standard 2x2 matrix multiplication + return (Matrix2x2) { + a.m00* b.m00 + a.m01 * b.m10, // top-left + a.m00* b.m01 + a.m01 * b.m11, // top-right + a.m10* b.m00 + a.m11 * b.m10, // bottom-left + a.m10* b.m01 + a.m11 * b.m11 // bottom-right + }; +} + +// raise a matrix to the power n using binary exponentiation +Matrix2x2 matrix_pow(Matrix2x2 base, int n) { + Matrix2x2 result = { 1, 0, 0, 1 }; // start with identity + while (n > 0) { + if (n % 2 == 1) result = multiply(result, base); // if odd, multiply once + base = multiply(base, base); // square the base + n /= 2; // integer divide n by 2 + } + return result; +} + +// get nth fibonacci number using matrix exponentiation +uint64_t fibonacci_matrix(int n) { + if (n == 0) return 0; // edge case + Matrix2x2 base = { 1, 1, 1, 0 }; // Fibonacci Q-matrix + Matrix2x2 result = matrix_pow(base, n - 1); // raise to (n-1) + return result.m00; // top-left is F(n) +} + +int main() { + int n = 50; // how many numbers to print + for (int i = 0; i < n; i++) { + printf("%llu ", fibonacci_matrix(i)); // print each fib number + } + printf("\n"); // newline at end + return 0; +} diff --git a/fib/fib.java b/fib/fib.java new file mode 100644 index 0000000..3be5220 --- /dev/null +++ b/fib/fib.java @@ -0,0 +1,22 @@ +public class recursive { + + public static int fibonacci(int max) { + + if (max <= 1) { + return max; + } + + else { + return fibonacci(max - 2) + fibonacci(max - 1); + } + + } + + public static void main(String[] args) { + + int max = 10; + for (int i = 0; i < max; i++) { + System.out.print(fibonacci(i) + " "); + } + } +} \ No newline at end of file diff --git a/fib/fib.py b/fib/fib.py new file mode 100644 index 0000000..ed1c653 --- /dev/null +++ b/fib/fib.py @@ -0,0 +1,41 @@ +# tiny class to hold a 2x2 matrix +class Matrix2x2: + def __init__(self, m00, m01, m10, m11): + self.m00 = m00 + self.m01 = m01 + self.m10 = m10 + self.m11 = m11 + +# multiply two 2x2 matrices +def multiply(a, b): + return Matrix2x2( + a.m00 * b.m00 + a.m01 * b.m10, # top-left + a.m00 * b.m01 + a.m01 * b.m11, # top-right + a.m10 * b.m00 + a.m11 * b.m10, # bottom-left + a.m10 * b.m01 + a.m11 * b.m11 # bottom-right + ) + +# raise a matrix to the power n using binary exponentiation +def matrix_pow(base, n): + result = Matrix2x2(1, 0, 0, 1) # start with identity matrix + while n > 0: + if n % 2 == 1: # if n is odd, multiply once + result = multiply(result, base) + base = multiply(base, base) # square the base + n //= 2 # integer division + return result + +# get nth fibonacci number using matrix exponentiation +def fibonacci_matrix(n): + if n == 0: + return 0 # edge case + base = Matrix2x2(1, 1, 1, 0) # Fibonacci Q-matrix + result = matrix_pow(base, n - 1) + return result.m00 # top-left is F(n) + +# print first n fibonacci numbers +if __name__ == "__main__": + n = 50 + for i in range(n): + print(fibonacci_matrix(i), end=" ") + print() diff --git a/fib/sfib.c b/fib/sfib.c new file mode 100644 index 0000000..28e9fed --- /dev/null +++ b/fib/sfib.c @@ -0,0 +1,22 @@ +#include +typedef unsigned long long U; + +U fib(int n) { + if (n < 2) return n; + U a = 1, b = 1, c = 1, d = 0, x = 1, y = 0, z = 0, w = 1, t; + while (n) { + if (n & 1) { + t = x * a + y * c; y = x * b + y * d; x = t; + t = z * a + w * c; w = z * b + w * d; z = t; + } + t = a * a + b * c; b = a * b + b * d; a = t; + t = c * a + d * c; d = c * b + d * d; c = t; + n >>= 1; + } + return x; +} + +int main(int argc, char **argv[]) { + for (int i = 0;i < int(argv);i++) printf("%llu ", fib(i)); +} + diff --git a/functions/firstlast.py b/functions/firstlast.py new file mode 100644 index 0000000..64380f5 --- /dev/null +++ b/functions/firstlast.py @@ -0,0 +1,5 @@ +def wort_funktion(s : str) -> str: + last = len(s) + print(s[0] + s[(len(s)) - 1]) + +wort_funktion("das ist ein string!") \ No newline at end of file diff --git a/functions/mark/main.py b/functions/mark/main.py new file mode 100644 index 0000000..db59601 --- /dev/null +++ b/functions/mark/main.py @@ -0,0 +1,70 @@ +# Datei ffnen + +f = open("login.txt", "a") + + +#Login Prozess + +def login(): + benutzername = input("Geben sie ihre Benutzername ein:") + passwort = input("Geben Sie Ihren Passwort ein:") + + global Registrierung + Registrierung = False #Sagt dem Programm ob der Nutzer in Zukunft registrieren mchte + falscherPasswort = False + try: + # Auslesen der Datei + with open("login.txt", "r") as f: + accounts = f.readlines() + + angemeldet = False + + for account in accounts: + name, pw = account.strip().split(",") + if benutzername == name and passwort == pw: + angemeldet = True + falscherPasswort = False + break + elif benutzername == name and not passwort == pw: + falscherPasswort = True + + if angemeldet and not falscherPasswort: + print("Anmeldung erfolgreich!") + elif falscherPasswort: + print("Benutzername oder Passwort falsch (pw)") + + else: + print("Benutzername oder Passwort falsch") + + newaccount = input("Wollen Sie einen neuen Account erstellen?") + if newaccount == "Ja": + Registrierung = True + else: + Registrierung = False + + + + except FileNotFoundError: + print("Datei wurde nicht gefunden") + +# Funktion zur Registrierung +def signin(): + newname = input("Geben Sie ihr neuer Benutzername ein:") + newpw = input("Geben Sie ihr neues Passwort ein:") + with open("login.txt", "a") as f: + f.write("\n"+newname+",") + f.write(newpw+"\n") + + print("Dein Account wurde erfolgreich registriert!") + + + + + + +login() + +# Aufruf der Funktion falls der Nutzer registrieren will +if Registrierung: + signin() + diff --git a/functions/passwort.py b/functions/passwort.py new file mode 100644 index 0000000..e5c3dc5 --- /dev/null +++ b/functions/passwort.py @@ -0,0 +1,52 @@ +import random + +def passwort_zahlen(laenge): + p = "" + for _ in range(laenge): + zufallszahl = random.randint(0, 9) + p = str(p) + str(zufallszahl) + return p + +def passwort_buchstaben(laenge): + p = "" + for _ in range(laenge): + buchstabe = random.choice("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + p = str(p) + str(buchstabe) + return p + +def passwort_kombi(laenge): + p = "" + for _ in range(laenge): + wahl = random.choice(["zahl", "buchstabe"]) + if wahl == "zahl": + zufallszahl = random.randint(0, 9) + p = str(p) + str(zufallszahl) + else: + buchstabe = random.choice("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + p = str(p) + str(buchstabe) + return p + +# Hauptprogramm +def main(): + print("Passwortgenerator") + print("1: Nur Zahlen") + print("2: Nur Buchstaben") + print("3: Zahlen und Buchstaben") + + auswahl = input("Deine Wahl (1/2/3): ") + laenge = int(input("Wie viele Zeichen soll das Passwort haben? ")) + + if auswahl == "1": + passwort = passwort_zahlen(laenge) + elif auswahl == "2": + passwort = passwort_buchstaben(laenge) + elif auswahl == "3": + passwort = passwort_kombi(laenge) + else: + return + + print("Passwort:", passwort) + +# Programm starten +if __name__ == "__main__": + main() diff --git a/functions/sekunden.py b/functions/sekunden.py new file mode 100644 index 0000000..3776355 --- /dev/null +++ b/functions/sekunden.py @@ -0,0 +1,57 @@ +# -*- coding: ansi -*- + +def timetosec(time_str): + parts = time_str.split(':') + parts = [int(p) for p in parts] + + if len(parts) == 3: # HH:MM:SS + hours, minutes, seconds = parts + elif len(parts) == 2: # MM:SS + hours = 0 + minutes, seconds = parts + elif len(parts) == 1: # SS + hours = 0 + minutes = 0 + seconds = parts[0] + else: + raise ValueError("Zeitformat") + + total_seconds = hours * 3600 + minutes * 60 + seconds + return total_seconds + +print(timetosec("02:15:30")) # 8130 +print(timetosec("15:30")) # 930 +print(timetosec("45")) # 45 + + +def format_seconds(seconds: int) -> str: + """ + Meine Interpretation von dem Moodle eintrag. Der leer ist. + Wandelt Sekunden in das nchst beste Format um + :param seconds -> int + :returns str + """ + if seconds < 0: + return "falsche eingable" + + intervals = ( + ('Tag', 86400), # 60*60*24 + ('Stunde', 3600), # 60*60 + ('Minute', 60), + ('Sekunde', 1), + ) + + result = [] + for name, count in intervals: + value = seconds // count + if value: + seconds -= value * count + if value == 1: + result.append(f"{value} {name}") + else: + result.append(f"{value} {name}en") + return ', '.join(result) if result else "0 Sekunden" + +print(format_seconds(3661)) # 1std 1m 1s +print(format_seconds(86465)) # 1t 1m 5s +print(format_seconds(59)) # 59s diff --git a/functions/summe.py b/functions/summe.py new file mode 100644 index 0000000..da2acc1 --- /dev/null +++ b/functions/summe.py @@ -0,0 +1,18 @@ +def summe(n) -> float: + lst = [] + for i in range(1,n): + lst.append(i) + formatted = " + ".join(str(x) for x in lst) + print(f"summe = {formatted} ; sum = {total(lst)}") + +def total(lst: list) -> float: + total = None + for i in lst: + i += total + if total: + return total + else: + return None + + +summe(10) \ No newline at end of file diff --git a/functions/umrechner.py b/functions/umrechner.py new file mode 100644 index 0000000..f63fa0b --- /dev/null +++ b/functions/umrechner.py @@ -0,0 +1,31 @@ +def euroToDollar(euro_amount, kurs=1.15): + return euro_amount * kurs + +def dollarToEuro(dollar_amount, kurs=1.15): + return dollar_amount / kurs + +def main(): + print("Währungs‐Umrechner ") + print("Aktueller Kurs: 1€ = {:.2f} USD".format(1.15)) + richtung = input("1) € auf $ \n 2) $ auf € \nIhre Wahl: ") + if richtung == "1": + euro_str = input("€: ") + try: + euro = float(euro_str.replace(",",".")) + dollar = euroToDollar(euro, kurs=1.15) + print(f"{euro:.2f}€ entsprechen {dollar:.2f}$") + except ValueError as e: + print(e) + elif richtung == "2": + usd_str = input("$: ") + try: + usd = float(usd_str.replace(",",".")) + euro = dollarToEuro(usd, kurs=1.15) + print(f"{usd:.2f} $ entsprechen {euro:.2f}€") + except ValueError as e: + print(e) + else: + print("wähle richtig") + +if __name__ == "__main__": + main() diff --git a/key/key.py b/key/key.py new file mode 100644 index 0000000..ae1ba5d --- /dev/null +++ b/key/key.py @@ -0,0 +1,143 @@ +import cv2 +import numpy as np +import trimesh +from shapely.geometry import Polygon +import tkinter as tk +from tkinter import filedialog, messagebox +from PIL import Image, ImageTk +import os + +class KeyForge3DApp: + def __init__(self, root): + self.root = root + self.root.title("KeyForge3D - Key Shape Extractor and 3D Model Generator") + self.root.geometry("600x400") + + # Variables + self.image_path = None + self.scale_factor = 0.1 # Default scale: 1 pixel = 0.1 mm (adjust with a reference object if needed) + self.key_thickness = 2.0 # Thickness of the key in mm + self.num_cuts = 5 # Number of bitting cuts (adjust based on key type) + self.cut_depth_increment = 0.33 # Depth increment per bitting value in mm (e.g., Schlage standard) + + # GUI Elements + self.label = tk.Label(root, text="KeyForge3D: Extract and 3D Print Keys", font=("Arial", 16)) + self.label.pack(pady=10) + + self.upload_button = tk.Button(root, text="Upload Key Image", command=self.upload_image) + self.upload_button.pack(pady=5) + + self.image_label = tk.Label(root) + self.image_label.pack(pady=5) + + self.process_button = tk.Button(root, text="Process Key and Generate 3D Model", command=self.process_key, state=tk.DISABLED) + self.process_button.pack(pady=5) + + self.result_label = tk.Label(root, text="", font=("Arial", 12)) + self.result_label.pack(pady=5) + + def upload_image(self): + """Allow the user to upload an image of a key.""" + self.image_path = filedialog.askopenfilename(filetypes=[("Image Files", "*.jpg *.jpeg *.png")]) + if self.image_path: + # Display the uploaded image + img = Image.open(self.image_path) + img = img.resize((300, 150), Image.Resampling.LANCZOS) # Resize for display + img_tk = ImageTk.PhotoImage(img) + self.image_label.config(image=img_tk) + self.image_label.image = img_tk # Keep a reference to avoid garbage collection + self.process_button.config(state=tk.NORMAL) + self.result_label.config(text="Image uploaded. Click 'Process Key' to generate the 3D model.") + + def process_key(self): + """Process the key image, extract the shape, and generate a 3D model.""" + if not self.image_path: + messagebox.showerror("Error", "Please upload an image first.") + return + + try: + # Load the image + image = cv2.imread(self.image_path) + if image is None: + raise ValueError("Could not load the image.") + + gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) + + # Apply Gaussian blur and edge detection + blurred = cv2.GaussianBlur(gray, (5, 5), 0) + edges = cv2.Canny(blurred, 50, 150) + + # Find contours + contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + + # Filter contours to find the key (long, thin shape) + key_contour = None + for contour in contours: + perimeter = cv2.arcLength(contour, True) + approx = cv2.approxPolyDP(contour, 0.02 * perimeter, True) + x, y, w, h = cv2.boundingRect(contour) + aspect_ratio = w / float(h) + if 2 < aspect_ratio < 5 and w > 100: # Adjust these values based on your image + key_contour = contour + break + + if key_contour is None: + raise ValueError("Could not detect a key in the image.") + + # Extract the key region + x, y, w, h = cv2.boundingRect(key_contour) + key_region = gray[y:y+h, x:x+w] + + # Convert contour to a 2D polygon + points = key_contour.reshape(-1, 2) * self.scale_factor + key_polygon = Polygon(points) + + # Extrude the polygon to create a 3D model + key_mesh = trimesh.creation.extrude_polygon(key_polygon, height=self.key_thickness) + + # Analyze the bitting + blade = key_region[h//2:h, :] # Focus on the lower half (blade) + height, width = blade.shape + segment_width = width // self.num_cuts + bitting = [] + + for i in range(self.num_cuts): + segment = blade[:, i * segment_width:(i + 1) * segment_width] + # Find the highest point (shallowest cut) in the segment + cut_depth = np.argmax(segment, axis=0).mean() + # Scale the depth to real-world dimensions + depth_value = (cut_depth / height) * self.cut_depth_increment * 9 + bitting.append(depth_value) + + # Apply bitting cuts to the 3D model + for i, depth in enumerate(bitting): + cut_x = (i * segment_width * self.scale_factor) + (segment_width * self.scale_factor / 2) + cut_y = 0 # Adjust based on blade position + cut_width = segment_width * self.scale_factor + cut_height = depth + # Create a box for the cut and subtract it from the key mesh + cut_box = trimesh.creation.box( + extents=[cut_width, cut_height, self.key_thickness + 1], + transform=trimesh.transformations.translation_matrix([cut_x, cut_y, 0]) + ) + key_mesh = key_mesh.difference(cut_box) + + # Export the 3D model as STL + output_path = "key_model.stl" + key_mesh.export(output_path) + + # Display the results + bitting_code = [int(d / self.cut_depth_increment) for d in bitting] + self.result_label.config( + text=f"Success! Bitting Code: {bitting_code}\n3D Model saved as '{output_path}'" + ) + messagebox.showinfo("Success", f"3D model generated and saved as '{output_path}'.") + + except Exception as e: + messagebox.showerror("Error", f"Failed to process the key: {str(e)}") + self.result_label.config(text="Error processing the key. See error message.") + +if __name__ == "__main__": + root = tk.Tk() + app = KeyForge3DApp(root) + root.mainloop() \ No newline at end of file diff --git a/key/main.py b/key/main.py new file mode 100644 index 0000000..01c3991 --- /dev/null +++ b/key/main.py @@ -0,0 +1,847 @@ +""" +Key to STL Generator - Educational Tool +Analyzes key images and creates 3D STL models of the key teeth profile +Enhanced version with manual selection tools +""" + +from PIL import Image, ImageDraw, ImageFilter, ImageTk, ImageEnhance +import numpy as np +from scipy import ndimage, signal +from scipy.ndimage import median_filter, gaussian_filter +import tkinter as tk +from tkinter import filedialog, messagebox, ttk +import struct + +class KeyToSTL: + def __init__(self): + self.original_image = None + self.image = None + self.processed_image = None + self.key_profile = [] + self.key_bounds = None + self.manual_bounds = None + self.groove_regions = [] + + def load_image(self, filepath): + """Load and preprocess the key image""" + self.original_image = Image.open(filepath) + self.original_image = self.original_image.convert('RGB') + self.image = self.original_image.convert('L') + print(f"Image loaded: {self.image.size}") + return self.image + + def set_manual_bounds(self, x1, y1, x2, y2): + """Set manual key bounds from user selection""" + self.manual_bounds = { + 'left': min(x1, x2), + 'right': max(x1, x2), + 'top': min(y1, y2), + 'bottom': max(y1, y2) + } + print(f"Manual bounds set: {self.manual_bounds}") + + def add_groove_region(self, x1, y1, x2, y2): + """Add a groove region that should be emphasized""" + region = { + 'left': min(x1, x2), + 'right': max(x1, x2), + 'top': min(y1, y2), + 'bottom': max(y1, y2) + } + self.groove_regions.append(region) + print(f"Groove region added: {region}") + + def clear_groove_regions(self): + """Clear all groove regions""" + self.groove_regions = [] + print("Groove regions cleared") + + def denoise_image(self, median_size=3, gaussian_sigma=1.5): + """Apply strong denoising to handle static/noise in images""" + if self.image is None: + raise ValueError("No image loaded") + + img_array = np.array(self.image) + denoised = median_filter(img_array, size=median_size) + denoised = gaussian_filter(denoised, sigma=gaussian_sigma) + + return denoised + + def auto_detect_key(self, noise_handling=True): + """Automatically detect the key region with noise handling""" + if self.image is None: + raise ValueError("No image loaded") + + if noise_handling: + img_array = self.denoise_image(median_size=5, gaussian_sigma=2.0) + else: + img_array = np.array(self.image) + + img_pil = Image.fromarray(img_array.astype(np.uint8)) + enhancer = ImageEnhance.Contrast(img_pil) + enhanced = enhancer.enhance(2.5) + + img_array = np.array(enhanced) + img_array = gaussian_filter(img_array, sigma=1.0) + + sobel_x = ndimage.sobel(img_array, axis=1) + sobel_y = ndimage.sobel(img_array, axis=0) + edge_magnitude = np.hypot(sobel_x, sobel_y) + + edge_magnitude = (edge_magnitude / edge_magnitude.max() * 255).astype(np.uint8) + + threshold = np.percentile(edge_magnitude[edge_magnitude > 0], 90) + binary_edges = edge_magnitude > threshold + + from scipy.ndimage import binary_closing, binary_opening, binary_dilation, binary_erosion + + binary_edges = binary_opening(binary_edges, structure=np.ones((2, 2))) + binary_edges = binary_closing(binary_edges, structure=np.ones((3, 3))) + binary_edges = binary_erosion(binary_edges, structure=np.ones((2, 2))) + binary_edges = binary_dilation(binary_edges, structure=np.ones((2, 2))) + + self.processed_image = Image.fromarray((binary_edges * 255).astype(np.uint8)) + return self.processed_image + + def find_key_bounds(self, margin_percent=0.1): + """Find the bounding box of the key with noise-resistant algorithm""" + if self.processed_image is None: + raise ValueError("Process image first") + + # Use manual bounds if available + if self.manual_bounds: + self.key_bounds = self.manual_bounds.copy() + print(f"Using manual bounds: {self.key_bounds}") + return self.key_bounds + + edges_array = np.array(self.processed_image) + height, width = edges_array.shape + + row_content = np.sum(edges_array, axis=1) + col_content = np.sum(edges_array, axis=0) + + row_smooth = gaussian_filter(row_content.astype(float), sigma=height * 0.02) + col_smooth = gaussian_filter(col_content.astype(float), sigma=width * 0.01) + + row_threshold = np.percentile(row_smooth[row_smooth > 0], 25) + col_threshold = np.percentile(col_smooth[col_smooth > 0], 15) + + rows_with_content = np.where(row_smooth > row_threshold)[0] + cols_with_content = np.where(col_smooth > col_threshold)[0] + + if len(rows_with_content) > 0 and len(cols_with_content) > 0: + row_margin = int(height * margin_percent) + col_margin = int(width * margin_percent * 0.5) + + self.key_bounds = { + 'top': max(0, rows_with_content[0] - row_margin), + 'bottom': min(height - 1, rows_with_content[-1] + row_margin), + 'left': max(0, cols_with_content[0] - col_margin), + 'right': min(width - 1, cols_with_content[-1] + col_margin) + } + print(f"Key bounds detected: {self.key_bounds}") + else: + self.key_bounds = { + 'top': height // 4, + 'bottom': 3 * height // 4, + 'left': width // 10, + 'right': 9 * width // 10 + } + print("Using fallback bounds") + + return self.key_bounds + + def extract_key_profile(self, use_top=True, consensus_window=5): + """Extract key profile with noise-resistant consensus approach""" + if self.processed_image is None or self.key_bounds is None: + raise ValueError("Process image and find bounds first") + + edges_array = np.array(self.processed_image) + bounds = self.key_bounds + + roi = edges_array[bounds['top']:bounds['bottom'], + bounds['left']:bounds['right']] + + height, width = roi.shape + profile = [] + + for x in range(width): + start_col = max(0, x - consensus_window // 2) + end_col = min(width, x + consensus_window // 2 + 1) + + window = roi[:, start_col:end_col] + + edge_positions = [] + for col in range(window.shape[1]): + column = window[:, col] + edge_pixels = np.where(column > 128)[0] + + if len(edge_pixels) > 0: + if use_top: + edge_positions.append(edge_pixels[0]) + else: + edge_positions.append(edge_pixels[-1]) + + if len(edge_positions) > 0: + y_pos = int(np.median(edge_positions)) + actual_y = y_pos + bounds['top'] + actual_x = x + bounds['left'] + profile.append((actual_x, actual_y)) + else: + if len(profile) > 0: + profile.append((x + bounds['left'], profile[-1][1])) + else: + profile.append((x + bounds['left'], bounds['top'] + height // 2)) + + profile = self._fill_profile_gaps(profile) + + # Apply groove emphasis + if self.groove_regions: + profile = self._emphasize_grooves(profile) + + self.key_profile = profile + return profile + + def _emphasize_grooves(self, profile, emphasis_factor=1.5): + """Emphasize groove regions in the profile""" + emphasized = [] + + for x, y in profile: + # Check if point is in any groove region + in_groove = False + for groove in self.groove_regions: + if groove['left'] <= x <= groove['right']: + in_groove = True + break + + if in_groove: + # Find average Y in this region + region_ys = [py for px, py in profile if groove['left'] <= px <= groove['right']] + if region_ys: + avg_y = np.mean(region_ys) + # Emphasize deviation from average + deviation = y - avg_y + new_y = avg_y + deviation * emphasis_factor + emphasized.append((x, int(new_y))) + else: + emphasized.append((x, y)) + else: + emphasized.append((x, y)) + + return emphasized + + def _fill_profile_gaps(self, profile, max_gap=10): + """Fill gaps in profile with interpolation""" + if len(profile) < 2: + return profile + + filled = [profile[0]] + + for i in range(1, len(profile)): + prev_x, prev_y = filled[-1] + curr_x, curr_y = profile[i] + + if abs(curr_y - prev_y) > max_gap: + steps = int(abs(curr_x - prev_x)) + if steps > 1: + for j in range(1, steps): + interp_x = prev_x + j + interp_y = int(prev_y + (curr_y - prev_y) * j / steps) + filled.append((interp_x, interp_y)) + + filled.append((curr_x, curr_y)) + + return filled + + def smooth_profile_advanced(self, window_size=11, poly_order=3): + """Apply Savitzky-Golay filter with noise handling""" + if not self.key_profile: + raise ValueError("Extract profile first") + + profile_array = np.array(self.key_profile) + x_coords = profile_array[:, 0] + y_coords = profile_array[:, 1] + + y_median = median_filter(y_coords, size=5) + + window_size = min(window_size, len(y_median)) + if window_size % 2 == 0: + window_size -= 1 + window_size = max(window_size, poly_order + 2) + if window_size % 2 == 0: + window_size += 1 + + try: + smoothed_y = signal.savgol_filter(y_median, window_size, poly_order) + except: + smoothed_y = gaussian_filter(y_median, sigma=window_size / 3.0) + + self.key_profile = [(int(x), int(y)) for x, y in zip(x_coords, smoothed_y)] + return self.key_profile + + def remove_outliers(self, threshold=3.0, window_size=10): + """Remove outlier points using local statistics""" + if not self.key_profile: + return + + profile_array = np.array(self.key_profile) + y_coords = profile_array[:, 1].astype(float) + + cleaned_y = y_coords.copy() + + for i in range(len(y_coords)): + start = max(0, i - window_size // 2) + end = min(len(y_coords), i + window_size // 2 + 1) + window = y_coords[start:end] + + local_median = np.median(window) + mad = np.median(np.abs(window - local_median)) + + if mad > 0: + z_score = abs(y_coords[i] - local_median) / (1.4826 * mad) + if z_score > threshold: + cleaned_y[i] = local_median + + self.key_profile = [(int(x), int(y)) for x, y in zip(profile_array[:, 0], cleaned_y)] + return self.key_profile + + def normalize_profile(self): + """Normalize profile to 0-1 range for depth""" + if not self.key_profile: + raise ValueError("Extract profile first") + + profile_array = np.array(self.key_profile) + y_coords = profile_array[:, 1] + + y_min, y_max = y_coords.min(), y_coords.max() + + if y_max - y_min < 1: + return [(x, 0.5) for x, _ in self.key_profile] + + normalized = [] + for x, y in self.key_profile: + normalized_depth = (y - y_min) / (y_max - y_min) + normalized.append((x, normalized_depth)) + + return normalized + + def generate_stl(self, output_path, depth_scale=2.0, base_thickness=1.0): + """Generate STL file from the key profile""" + if not self.key_profile: + raise ValueError("Extract profile first") + + normalized_profile = self.normalize_profile() + + profile_array = np.array(self.key_profile) + x_coords = profile_array[:, 0] + x_min, x_max = x_coords.min(), x_coords.max() + + key_length = float(x_max - x_min) + key_width = 10.0 + + scaled_profile = [] + for (x, _), (_, normalized_depth) in zip(self.key_profile, normalized_profile): + scaled_x = float(x - x_min) + actual_depth = normalized_depth * depth_scale + scaled_profile.append((scaled_x, actual_depth)) + + vertices, faces = self._create_mesh(scaled_profile, key_width, base_thickness) + self._write_stl(output_path, vertices, faces) + + print(f"STL file saved: {output_path}") + print(f"Dimensions: {key_length:.1f} x {key_width:.1f} x {base_thickness + depth_scale:.1f} units") + + def _create_mesh(self, profile, width, base_thickness): + """Create 3D mesh from 2D profile""" + vertices = [] + faces = [] + + n = len(profile) + + for x, depth in profile: + z_top = base_thickness + depth + vertices.append([x, 0.0, z_top]) + vertices.append([x, width, z_top]) + vertices.append([x, 0.0, 0.0]) + vertices.append([x, width, 0.0]) + + for i in range(n - 1): + base = i * 4 + next_base = (i + 1) * 4 + + v1, v2 = base, base + 1 + v3, v4 = next_base, next_base + 1 + faces.append([v1, v3, v2]) + faces.append([v2, v3, v4]) + + v1, v2 = base + 2, base + 3 + v3, v4 = next_base + 2, next_base + 3 + faces.append([v1, v2, v3]) + faces.append([v2, v4, v3]) + + v1, v2 = base, base + 2 + v3, v4 = next_base, next_base + 2 + faces.append([v1, v3, v2]) + faces.append([v2, v3, v4]) + + v1, v2 = base + 1, base + 3 + v3, v4 = next_base + 1, next_base + 3 + faces.append([v1, v2, v3]) + faces.append([v2, v4, v3]) + + faces.append([0, 2, 1]) + faces.append([1, 2, 3]) + + last = (n - 1) * 4 + faces.append([last, last + 1, last + 2]) + faces.append([last + 1, last + 3, last + 2]) + + return vertices, faces + + def _write_stl(self, filepath, vertices, faces): + """Write vertices and faces to binary STL file""" + with open(filepath, 'wb') as f: + header = b'Key STL - Python Generated' + b' ' * (80 - 26) + f.write(header) + f.write(struct.pack(' 0: + normal = normal / length + else: + normal = np.array([0.0, 0.0, 1.0]) + + return normal.tolist() + + +class KeySTLGUI: + def __init__(self, root): + self.root = root + self.root.title("Key to STL Generator - Manual Selection Tools") + self.root.geometry("1200x800") + + self.key_processor = KeyToSTL() + self.canvas_image = None + self.photo_image = None + + # Selection state + self.selection_mode = None # 'key_bounds' or 'groove' + self.selection_start = None + self.current_rect = None + self.drawn_rectangles = [] + + self._create_widgets() + self._bind_canvas_events() + + def _create_widgets(self): + main_container = ttk.PanedWindow(self.root, orient=tk.HORIZONTAL) + main_container.pack(fill=tk.BOTH, expand=True) + + # Control panel + control_frame = ttk.Frame(main_container, padding="10") + main_container.add(control_frame, weight=0) + + ttk.Label(control_frame, text="Key to STL Generator", + font=('Arial', 14, 'bold')).pack(pady=10) + + # Step 1 + ttk.Label(control_frame, text="Step 1: Load Image", + font=('Arial', 10, 'bold')).pack(pady=(10, 5)) + ttk.Button(control_frame, text="📁 Load Image", + command=self.load_image, width=22).pack(pady=5) + + ttk.Separator(control_frame, orient='horizontal').pack(fill='x', pady=10) + + # Step 2 - Manual Selection Tools + ttk.Label(control_frame, text="Step 2: Manual Selection (Optional)", + font=('Arial', 10, 'bold')).pack(pady=(5, 5)) + + tool_frame = ttk.LabelFrame(control_frame, text="Selection Tools", padding="5") + tool_frame.pack(fill='x', pady=5) + + ttk.Button(tool_frame, text="🔲 Select Key Area", + command=self.start_key_selection, width=20).pack(pady=2) + ttk.Label(tool_frame, text="Drag rectangle around key", + font=('Arial', 8), foreground='gray').pack() + + ttk.Separator(tool_frame, orient='horizontal').pack(fill='x', pady=5) + + ttk.Button(tool_frame, text="🎯 Mark Groove Region", + command=self.start_groove_selection, width=20).pack(pady=2) + ttk.Label(tool_frame, text="Mark areas with teeth cuts", + font=('Arial', 8), foreground='gray').pack() + + ttk.Button(tool_frame, text="🗑️ Clear Grooves", + command=self.clear_grooves, width=20).pack(pady=2) + + ttk.Button(tool_frame, text="↩️ Cancel Selection", + command=self.cancel_selection, width=20).pack(pady=2) + + self.selection_status = ttk.Label(tool_frame, text="No active tool", + font=('Arial', 8, 'italic'), foreground='blue') + self.selection_status.pack(pady=5) + + ttk.Separator(control_frame, orient='horizontal').pack(fill='x', pady=10) + + # Step 3 - Auto Process + ttk.Label(control_frame, text="Step 3: Auto Process", + font=('Arial', 10, 'bold')).pack(pady=(5, 5)) + + self.noise_handling_var = tk.BooleanVar(value=True) + ttk.Checkbutton(control_frame, text="Enable noise reduction", + variable=self.noise_handling_var).pack(pady=2) + + ttk.Button(control_frame, text="🔍 Auto Detect Key", + command=self.auto_process, width=22).pack(pady=5) + + self.edge_top_var = tk.BooleanVar(value=True) + ttk.Checkbutton(control_frame, text="Use top edge", + variable=self.edge_top_var).pack(pady=2) + + ttk.Separator(control_frame, orient='horizontal').pack(fill='x', pady=10) + + # Step 4 - Refine + ttk.Label(control_frame, text="Step 4: Refine Profile", + font=('Arial', 10, 'bold')).pack(pady=(5, 5)) + + ttk.Label(control_frame, text="Smoothing:").pack() + self.smooth_var = tk.IntVar(value=11) + ttk.Scale(control_frame, from_=5, to=21, variable=self.smooth_var, + orient=tk.HORIZONTAL).pack() + + ttk.Button(control_frame, text="✨ Smooth", + command=self.smooth_profile, width=22).pack(pady=2) + ttk.Button(control_frame, text="🧹 Remove Outliers", + command=self.remove_outliers, width=22).pack(pady=2) + + ttk.Separator(control_frame, orient='horizontal').pack(fill='x', pady=10) + + # Step 5 - Export + ttk.Label(control_frame, text="Step 5: Export STL", + font=('Arial', 10, 'bold')).pack(pady=(5, 5)) + + ttk.Label(control_frame, text="Teeth Depth (mm):").pack() + self.depth_var = tk.DoubleVar(value=2.0) + ttk.Entry(control_frame, textvariable=self.depth_var, width=15).pack() + + ttk.Label(control_frame, text="Base (mm):").pack() + self.base_var = tk.DoubleVar(value=1.0) + ttk.Entry(control_frame, textvariable=self.base_var, width=15).pack() + + ttk.Button(control_frame, text="💾 Generate STL", + command=self.generate_stl, width=22).pack(pady=10) + + # Canvas + canvas_frame = ttk.Frame(main_container) + main_container.add(canvas_frame, weight=1) + + self.canvas = tk.Canvas(canvas_frame, bg='#f0f0f0', highlightthickness=0, cursor='crosshair') + + h_scrollbar = ttk.Scrollbar(canvas_frame, orient=tk.HORIZONTAL, command=self.canvas.xview) + v_scrollbar = ttk.Scrollbar(canvas_frame, orient=tk.VERTICAL, command=self.canvas.yview) + + self.canvas.configure(xscrollcommand=h_scrollbar.set, yscrollcommand=v_scrollbar.set) + + h_scrollbar.pack(side=tk.BOTTOM, fill=tk.X) + v_scrollbar.pack(side=tk.RIGHT, fill=tk.Y) + self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) + + # Status + self.status_label = ttk.Label(self.root, text="Ready - Load a key image to start", + relief=tk.SUNKEN, padding=(5, 2)) + self.status_label.pack(side=tk.BOTTOM, fill=tk.X) + + def _bind_canvas_events(self): + """Bind mouse events for manual selection""" + self.canvas.bind('', self.on_canvas_click) + self.canvas.bind('', self.on_canvas_drag) + self.canvas.bind('', self.on_canvas_release) + + def start_key_selection(self): + """Start key bounds selection mode""" + if self.photo_image is None: + messagebox.showwarning("Warning", "Please load an image first") + return + + self.selection_mode = 'key_bounds' + self.selection_status.config(text="✓ Drag rectangle around key area") + self.canvas.config(cursor='crosshair') + self.status_label.config(text="SELECTION MODE: Draw rectangle around the key") + + def start_groove_selection(self): + """Start groove region selection mode""" + if self.photo_image is None: + messagebox.showwarning("Warning", "Please load an image first") + return + + self.selection_mode = 'groove' + self.selection_status.config(text="✓ Drag rectangle over groove area") + self.canvas.config(cursor='crosshair') + self.status_label.config(text="GROOVE MODE: Draw rectangles over areas with teeth cuts") + + def cancel_selection(self): + """Cancel current selection mode""" + self.selection_mode = None + self.selection_start = None + if self.current_rect: + self.canvas.delete(self.current_rect) + self.current_rect = None + self.selection_status.config(text="No active tool") + self.canvas.config(cursor='') + self.status_label.config(text="Selection cancelled") + + def clear_grooves(self): + """Clear all groove regions""" + self.key_processor.clear_groove_regions() + # Redraw without groove rectangles + self._redraw_with_annotations() + self.status_label.config(text="✓ Groove regions cleared") + + def on_canvas_click(self, event): + """Handle canvas click for selection start""" + if self.selection_mode: + self.selection_start = (self.canvas.canvasx(event.x), + self.canvas.canvasy(event.y)) + if self.current_rect: + self.canvas.delete(self.current_rect) + + def on_canvas_drag(self, event): + """Handle canvas drag for drawing selection rectangle""" + if self.selection_mode and self.selection_start: + x1, y1 = self.selection_start + x2 = self.canvas.canvasx(event.x) + y2 = self.canvas.canvasy(event.y) + + if self.current_rect: + self.canvas.delete(self.current_rect) + + # Draw rectangle with different colors for different modes + color = 'green' if self.selection_mode == 'key_bounds' else 'orange' + self.current_rect = self.canvas.create_rectangle( + x1, y1, x2, y2, + outline=color, width=3, dash=(5, 5) + ) + + def on_canvas_release(self, event): + """Handle canvas release to finalize selection""" + if self.selection_mode and self.selection_start: + x1, y1 = self.selection_start + x2 = self.canvas.canvasx(event.x) + y2 = self.canvas.canvasy(event.y) + + # Ensure we have a valid rectangle + if abs(x2 - x1) > 5 and abs(y2 - y1) > 5: + if self.selection_mode == 'key_bounds': + self.key_processor.set_manual_bounds(int(x1), int(y1), int(x2), int(y2)) + self.status_label.config(text="✓ Key area selected") + # Redraw with permanent rectangle + if self.current_rect: + self.canvas.delete(self.current_rect) + self.current_rect = self.canvas.create_rectangle( + x1, y1, x2, y2, + outline='green', width=2 + ) + self.selection_mode = None + self.selection_status.config(text="Key area set") + + elif self.selection_mode == 'groove': + self.key_processor.add_groove_region(int(x1), int(y1), int(x2), int(y2)) + self.status_label.config(text=f"✓ Groove region added (Total: {len(self.key_processor.groove_regions)})") + # Draw permanent groove rectangle + if self.current_rect: + self.canvas.delete(self.current_rect) + groove_rect = self.canvas.create_rectangle( + x1, y1, x2, y2, + outline='orange', width=2, dash=(3, 3) + ) + self.drawn_rectangles.append(groove_rect) + # Stay in groove mode for multiple selections + + self.selection_start = None + self.current_rect = None + + def _redraw_with_annotations(self): + """Redraw image with all annotations""" + if self.key_processor.original_image: + self._display_image(self.key_processor.original_image.convert('L')) + + # Redraw key bounds if set + if self.key_processor.manual_bounds: + bounds = self.key_processor.manual_bounds + self.canvas.create_rectangle( + bounds['left'], bounds['top'], + bounds['right'], bounds['bottom'], + outline='green', width=2 + ) + + # Redraw groove regions + self.drawn_rectangles = [] + for groove in self.key_processor.groove_regions: + rect = self.canvas.create_rectangle( + groove['left'], groove['top'], + groove['right'], groove['bottom'], + outline='orange', width=2, dash=(3, 3) + ) + self.drawn_rectangles.append(rect) + + def load_image(self): + filepath = filedialog.askopenfilename( + title="Select Key Image", + filetypes=[("Image files", "*.jpg *.jpeg *.png *.bmp *.gif"), ("All files", "*.*")] + ) + + if filepath: + try: + self.key_processor.load_image(filepath) + self._display_image(self.key_processor.image) + self.status_label.config(text=f"✓ Loaded: {filepath.split('/')[-1]}") + except Exception as e: + messagebox.showerror("Error", f"Failed to load image: {str(e)}") + + def auto_process(self): + try: + noise_handling = self.noise_handling_var.get() + + self.status_label.config(text="Processing... applying noise reduction" if noise_handling else "Processing... detecting edges") + self.root.update() + + self.key_processor.auto_detect_key(noise_handling=noise_handling) + + self.status_label.config(text="Processing... finding key bounds") + self.root.update() + + self.key_processor.find_key_bounds() + + self.status_label.config(text="Processing... extracting profile") + self.root.update() + + use_top = self.edge_top_var.get() + profile = self.key_processor.extract_key_profile(use_top=use_top, consensus_window=7) + + self._display_image_with_profile() + + groove_info = f" (with {len(self.key_processor.groove_regions)} groove regions)" if self.key_processor.groove_regions else "" + self.status_label.config(text=f"✓ Profile extracted: {len(profile)} points{groove_info}") + + except Exception as e: + messagebox.showerror("Error", f"Auto processing failed: {str(e)}") + import traceback + traceback.print_exc() + + def smooth_profile(self): + try: + window = self.smooth_var.get() + if window % 2 == 0: + window += 1 + self.key_processor.smooth_profile_advanced(window_size=window) + self._display_image_with_profile() + self.status_label.config(text="✓ Profile smoothed") + except Exception as e: + messagebox.showerror("Error", f"Failed to smooth: {str(e)}") + + def remove_outliers(self): + try: + self.key_processor.remove_outliers() + self._display_image_with_profile() + self.status_label.config(text="✓ Outliers removed") + except Exception as e: + messagebox.showerror("Error", f"Failed to remove outliers: {str(e)}") + + def generate_stl(self): + if not self.key_processor.key_profile: + messagebox.showwarning("Warning", "Please extract key profile first (Step 3)") + return + + filepath = filedialog.asksaveasfilename( + title="Save STL File", + defaultextension=".stl", + filetypes=[("STL files", "*.stl"), ("All files", "*.*")] + ) + + if filepath: + try: + self.key_processor.generate_stl( + filepath, + depth_scale=self.depth_var.get(), + base_thickness=self.base_var.get() + ) + messagebox.showinfo("Success", + f"✓ STL file generated!\n\n{filepath}\n\nYou can now scale it in your 3D slicer.") + self.status_label.config(text=f"✓ STL saved: {filepath.split('/')[-1]}") + except Exception as e: + messagebox.showerror("Error", f"Failed to generate STL: {str(e)}") + import traceback + traceback.print_exc() + + def _display_image(self, image): + if image is None: + return + + self.photo_image = ImageTk.PhotoImage(image) + self.canvas.delete("all") + self.canvas.create_image(0, 0, image=self.photo_image, anchor=tk.NW) + self.canvas.configure(scrollregion=self.canvas.bbox("all")) + + def _display_image_with_profile(self): + if self.key_processor.processed_image and self.key_processor.key_profile: + display_img = self.key_processor.processed_image.convert('RGB') + draw = ImageDraw.Draw(display_img) + + # Draw key bounds + if self.key_processor.key_bounds: + bounds = self.key_processor.key_bounds + draw.rectangle( + [bounds['left'], bounds['top'], bounds['right'], bounds['bottom']], + outline='green', width=3 + ) + + # Draw groove regions + for groove in self.key_processor.groove_regions: + draw.rectangle( + [groove['left'], groove['top'], groove['right'], groove['bottom']], + outline='orange', width=2 + ) + + # Draw profile + if len(self.key_processor.key_profile) > 1: + draw.line(self.key_processor.key_profile, fill='red', width=3) + + for x, y in self.key_processor.key_profile[::10]: + draw.ellipse([x-2, y-2, x+2, y+2], fill='yellow', outline='red') + + self._display_image(display_img) + + +if __name__ == "__main__": + print("Key to STL Generator - Manual Selection Tools") + print("Requirements: pip install scipy pillow numpy") + print("\nHow to use manual selection:") + print("1. Load your key image") + print("2. Click 'Select Key Area' and drag rectangle around key") + print("3. Click 'Mark Groove Region' and drag rectangles over teeth cuts") + print("4. Click 'Auto Detect Key' to process with your selections") + print("5. Refine and export STL") + + root = tk.Tk() + app = KeySTLGUI(root) + root.mainloop() \ No newline at end of file diff --git a/math/LaunchProcess0279191077.debugTarget b/math/LaunchProcess0279191077.debugTarget new file mode 100644 index 0000000..917922b --- /dev/null +++ b/math/LaunchProcess0279191077.debugTarget @@ -0,0 +1,50 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/math/complex.c b/math/complex.c new file mode 100644 index 0000000..8bfa5c6 --- /dev/null +++ b/math/complex.c @@ -0,0 +1,98 @@ +#include +#include +#include "complex.h" + +// Constructors +Complex complex_new(double re, double im) { + Complex z; + z.re = re; + z.im = im; + return z; +} + +// Add +Complex complex_add(Complex a, Complex b) { + return complex_new(a.re + b.re, a.im + b.im); +} + +// Subtract +Complex complex_sub(Complex a, Complex b) { + return complex_new(a.re - b.re, a.im - b.im); +} + +// Multiply +Complex complex_mul(Complex a, Complex b) { + return complex_new(a.re * b.re - a.im * b.im, + a.re * b.im + a.im * b.re); +} + +// Divide +Complex complex_div(Complex a, Complex b) { + double denom = b.re * b.re + b.im * b.im; + return complex_new((a.re * b.re + a.im * b.im) / denom, + (a.im * b.re - a.re * b.im) / denom); +} + +// Scalar multiply +Complex complex_scale(Complex a, double alpha) { + return complex_new(a.re * alpha, a.im * alpha); +} + +// Conjugate +Complex complex_conjugate(Complex a) { + return complex_new(a.re, -a.im); +} + +// Reciprocal +Complex complex_reciprocal(Complex a) { + double scale = a.re * a.re + a.im * a.im; + return complex_new(a.re / scale, -a.im / scale); +} + +// Magnitude (faster than hypot) +double complex_abs(Complex a) { + return sqrt(a.re * a.re + a.im * a.im); +} + +// Phase +double complex_phase(Complex a) { + return atan2(a.im, a.re); +} + +// Exponential +Complex complex_exp(Complex a) { + double exp_re = exp(a.re); + return complex_new(exp_re * cos(a.im), + exp_re * sin(a.im)); +} + +// Sine +Complex complex_sin(Complex a) { + return complex_new(sin(a.re) * cosh(a.im), + cos(a.re) * sinh(a.im)); +} + +// Cosine +Complex complex_cos(Complex a) { + return complex_new(cos(a.re) * cosh(a.im), + -sin(a.re) * sinh(a.im)); +} + +// Tangent +Complex complex_tan(Complex a) { + Complex s = complex_sin(a); + Complex c = complex_cos(a); + return complex_div(s, c); +} + +// Print function +void complex_print(Complex a) { + if (a.im == 0) + printf("%g", a.re); + else if (a.re == 0) + printf("%gi", a.im); + else if (a.im < 0) + printf("%g - %gi", a.re, -a.im); + else + printf("%g + %gi", a.re, a.im); +} diff --git a/math/complex.h b/math/complex.h new file mode 100644 index 0000000..13baeca --- /dev/null +++ b/math/complex.h @@ -0,0 +1,25 @@ +#ifndef COMPLEX_H +#define COMPLEX_H + +typedef struct { + double re; + double im; +} Complex; + +Complex complex_new(double re, double im); +Complex complex_add(Complex a, Complex b); +Complex complex_sub(Complex a, Complex b); +Complex complex_mul(Complex a, Complex b); +Complex complex_div(Complex a, Complex b); +Complex complex_scale(Complex a, double alpha); +Complex complex_conjugate(Complex a); +Complex complex_reciprocal(Complex a); +double complex_abs(Complex a); +double complex_phase(Complex a); +Complex complex_exp(Complex a); +Complex complex_sin(Complex a); +Complex complex_cos(Complex a); +Complex complex_tan(Complex a); +void complex_print(Complex a); + +#endif diff --git a/math/main.cpp b/math/main.cpp new file mode 100644 index 0000000..b2b7ca1 --- /dev/null +++ b/math/main.cpp @@ -0,0 +1,227 @@ +#include +#include +#include +#include + +#pragma comment(lib, "d3d11.lib") +#pragma comment(lib, "D3DCompiler.lib") + +HWND hwnd = nullptr; +ID3D11Device* device = nullptr; +ID3D11DeviceContext* context = nullptr; +IDXGISwapChain* swapChain = nullptr; +ID3D11RenderTargetView* rtv = nullptr; +ID3D11VertexShader* vs = nullptr; +ID3D11PixelShader* ps = nullptr; +ID3D11InputLayout* inputLayout = nullptr; +ID3D11Buffer* vertexBuffer = nullptr; + +// Vertex struct +struct Vertex { float x, y; }; + +// Vertex shader +const char* vsSrc = R"( +struct VS_INPUT { float2 pos : POSITION; }; +struct PS_INPUT { float4 pos : SV_POSITION; float2 uv : TEXCOORD; }; +PS_INPUT main(VS_INPUT input) { + PS_INPUT output; + output.pos = float4(input.pos,0,1); + output.uv = input.pos*0.5+0.5; + return output; +} +)"; + +char psSrc[2048]; // Pixel shader + +// Mandelbrot view presets +struct View { float xMin, xMax, yMin, yMax; }; +View views[] = { + {-2.5f, 1.0f, -1.2f, 1.2f}, // Full set + {-0.748f, -0.743f, 0.1f, 0.105f}, // Seahorse Valley + {-0.74877f, -0.74872f, 0.06505f, 0.06510f}, // Mini zoom +}; +int currentView = 0; + +// Forward declarations +LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); +bool InitWindow(HINSTANCE, int); +bool InitD3D(); +void CleanUp(); +bool CompileShaders(); +void Render(); + +// Main +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow) { + if (!InitWindow(hInstance, nCmdShow)) return 0; + if (!InitD3D()) return 0; + + CompileShaders(); + + MSG msg = {}; + while (msg.message != WM_QUIT) { + if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + else { + Render(); // non-blocking rendering + } + } + + CleanUp(); + return 0; +} + +// --- Window --- +bool InitWindow(HINSTANCE hInstance, int nCmdShow) { + const char CLASS_NAME[] = "MandelbrotDX11"; + WNDCLASS wc = {}; + wc.lpfnWndProc = WndProc; + wc.hInstance = hInstance; + wc.lpszClassName = CLASS_NAME; + RegisterClass(&wc); + + hwnd = CreateWindowEx(0, CLASS_NAME, "DX11 Mandelbrot", WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, CW_USEDEFAULT, 800, 600, nullptr, nullptr, hInstance, nullptr); + if (!hwnd) return false; + ShowWindow(hwnd, nCmdShow); + return true; +} + +// --- WinProc --- +LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { + switch (msg) { + case WM_DESTROY: PostQuitMessage(0); return 0; + case WM_KEYDOWN: + if (wParam == VK_ESCAPE) PostQuitMessage(0); + if (wParam == VK_LEFT) currentView = (currentView + 2) % 3; + if (wParam == VK_RIGHT) currentView = (currentView + 1) % 3; + CompileShaders(); + return 0; + } + return DefWindowProc(hwnd, msg, wParam, lParam); +} + +// --- D3D11 Init --- +bool InitD3D() { + DXGI_SWAP_CHAIN_DESC scd = {}; + scd.BufferCount = 1; + scd.BufferDesc.Width = 800; + scd.BufferDesc.Height = 600; + scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + scd.BufferDesc.RefreshRate.Numerator = 60; + scd.BufferDesc.RefreshRate.Denominator = 1; + scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + scd.OutputWindow = hwnd; + scd.SampleDesc.Count = 1; + scd.Windowed = TRUE; + + D3D_FEATURE_LEVEL featureLevel; + if (FAILED(D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, + 0, nullptr, 0, D3D11_SDK_VERSION, &scd, + &swapChain, &device, &featureLevel, &context))) return false; + + ID3D11Texture2D* backBuffer = nullptr; + swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)&backBuffer); + device->CreateRenderTargetView(backBuffer, nullptr, &rtv); + backBuffer->Release(); + context->OMSetRenderTargets(1, &rtv, nullptr); + + D3D11_VIEWPORT vp = {}; + vp.Width = 800; vp.Height = 600; vp.MinDepth = 0; vp.MaxDepth = 1; + context->RSSetViewports(1, &vp); + + Vertex verts[] = { {-1,-1},{-1,1},{1,-1},{1,1} }; + D3D11_BUFFER_DESC bd = {}; + bd.Usage = D3D11_USAGE_DEFAULT; + bd.ByteWidth = sizeof(verts); + bd.BindFlags = D3D11_BIND_VERTEX_BUFFER; + D3D11_SUBRESOURCE_DATA initData = {}; + initData.pSysMem = verts; + device->CreateBuffer(&bd, &initData, &vertexBuffer); + + return true; +} + +// --- Compile shaders --- +bool CompileShaders() { + // Pixel shader template with smooth coloring + snprintf(psSrc, sizeof(psSrc), + R"(struct PS_INPUT { float4 pos:SV_POSITION; float2 uv: TEXCOORD; }; +float mandelbrot(float2 c){ + float2 z=0; + float iter=0; + const int MAX_ITER=256; + for(int i=0;i4){iter=i; break;} + } + return iter; +} +float4 main(PS_INPUT input):SV_TARGET{ + float iter=mandelbrot(input.uv); + const float MAX_ITER=256.0; + // Original color mapping (smooth) + float t = iter / MAX_ITER; + float r = clamp(9.0*(1.0-t)*t*t*t,0,1); + float g = clamp(15.0*(1.0-t)*(1.0-t)*t*t,0,1); + float b = clamp(8.5*(1.0-t)*(1.0-t)*(1.0-t)*t,0,1); + return float4(r,g,b,1.0); +})", +(views[currentView].xMax - views[currentView].xMin), views[currentView].xMin, +(views[currentView].yMax - views[currentView].yMin), views[currentView].yMin +); + + ID3DBlob* vsBlob = nullptr, * psBlob = nullptr, * err = nullptr; + + if (FAILED(D3DCompile(vsSrc, strlen(vsSrc), nullptr, nullptr, nullptr, "main", "vs_5_0", 0, 0, &vsBlob, &err))) { + if (err) { OutputDebugStringA((char*)err->GetBufferPointer()); err->Release(); } return false; + } + if (FAILED(D3DCompile(psSrc, strlen(psSrc), nullptr, nullptr, nullptr, "main", "ps_5_0", 0, 0, &psBlob, &err))) { + if (err) { OutputDebugStringA((char*)err->GetBufferPointer()); err->Release(); } return false; + } + + if (vs) vs->Release(); + if (ps) ps->Release(); + if (inputLayout) inputLayout->Release(); + + device->CreateVertexShader(vsBlob->GetBufferPointer(), vsBlob->GetBufferSize(), nullptr, &vs); + device->CreatePixelShader(psBlob->GetBufferPointer(), psBlob->GetBufferSize(), nullptr, &ps); + + D3D11_INPUT_ELEMENT_DESC layout[] = { {"POSITION",0,DXGI_FORMAT_R32G32_FLOAT,0,0,D3D11_INPUT_PER_VERTEX_DATA,0} }; + device->CreateInputLayout(layout, 1, vsBlob->GetBufferPointer(), vsBlob->GetBufferSize(), &inputLayout); + + vsBlob->Release(); psBlob->Release(); + return true; +} + +// --- Render --- +void Render() { + float clear[4] = { 0,0,0,1 }; + context->ClearRenderTargetView(rtv, clear); + + UINT stride = sizeof(Vertex), offset = 0; + context->IASetVertexBuffers(0, 1, &vertexBuffer, &stride, &offset); + context->IASetInputLayout(inputLayout); + context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); + + context->VSSetShader(vs, nullptr, 0); + context->PSSetShader(ps, nullptr, 0); + + context->Draw(4, 0); + swapChain->Present(0, 0); // non-blocking present +} + +// --- Cleanup --- +void CleanUp() { + if (vertexBuffer) vertexBuffer->Release(); + if (inputLayout) inputLayout->Release(); + if (vs) vs->Release(); + if (ps) ps->Release(); + if (rtv) rtv->Release(); + if (swapChain) swapChain->Release(); + if (context) context->Release(); + if (device) device->Release(); +} diff --git a/math/mandelbrot.c b/math/mandelbrot.c new file mode 100644 index 0000000..7d22ea5 --- /dev/null +++ b/math/mandelbrot.c @@ -0,0 +1,61 @@ +#include "mandelbrot.h" +#include + +// Clamp helper +static int clamp(int val) { + if (val < 0) return 0; + if (val > 255) return 255; + return val; +} + +// Mandelbrot iterations +int mandelbrot(Complex z0, int max_iter) { + Complex z = z0; + for (int i = 0; i < max_iter; i++) { + if (complex_abs(z) > 2.0) return i; + z = complex_add(complex_mul(z, z), z0); + } + return max_iter; +} + +// Simple HSV -> RGB helper +static Color hsv_to_rgb(double h, double s, double v) { + double r = 0, g = 0, b = 0; + + int i = (int)(h * 6); + double f = h * 6 - i; + double p = v * (1 - s); + double q = v * (1 - f * s); + double t = v * (1 - (1 - f) * s); + + switch (i % 6) { + case 0: r = v; g = t; b = p; break; + case 1: r = q; g = v; b = p; break; + case 2: r = p; g = v; b = t; break; + case 3: r = p; g = q; b = v; break; + case 4: r = t; g = p; b = v; break; + case 5: r = v; g = p; b = q; break; + } + + Color c = { (unsigned char)clamp((int)(r * 255)), + (unsigned char)clamp((int)(g * 255)), + (unsigned char)clamp((int)(b * 255)) }; + return c; +} + +// Color Mandelbrot +Color color_mandelbrot(Complex z0, int max_iter) { + int iter = mandelbrot(z0, max_iter); + + if (iter == max_iter) + return (Color) { 0, 0, 0 }; // black inside the set + + // Map iteration to color hue + double t = (double)iter / max_iter; + double hue = 0.95 + 10 * t; // arbitrary scaling for more variation + hue = fmod(hue, 1.0); + double sat = 0.6; // saturation + double val = 1.0; // brightness + + return hsv_to_rgb(hue, sat, val); +} diff --git a/math/mandelbrot.h b/math/mandelbrot.h new file mode 100644 index 0000000..e7bdab2 --- /dev/null +++ b/math/mandelbrot.h @@ -0,0 +1,12 @@ +#ifndef MANDELBROT_H +#define MANDELBROT_H + +#include "complex.h" + +typedef struct { unsigned char r, g, b; } Color; + +int mandelbrot(Complex z0, int max_iter); +Color grey_mandelbrot(Complex z0, int max_iter); // optional, for backward compatibility +Color color_mandelbrot(Complex z0, int max_iter); + +#endif diff --git a/math/mandelbrot.hlsl b/math/mandelbrot.hlsl new file mode 100644 index 0000000..5f2479d --- /dev/null +++ b/math/mandelbrot.hlsl @@ -0,0 +1,24 @@ +cbuffer ViewportBuffer : register(b0) +{ + float xmin; + float xmax; + float ymin; + float ymax; + int max_iter; +}; + +struct PS_IN { float4 pos : SV_POSITION; }; + +float4 PSMain(PS_IN input) : SV_TARGET +{ + float2 uv = input.pos.xy * 0.5 + 0.5; // [-1,1] -> [0,1] + float2 c = float2(xmin,ymin) + uv * float2(xmax-xmin, ymax-ymin); + float2 z = c; + int i; + for(i=0;i4) break; + z = float2(z.x*z.x - z.y*z.y, 2*z.x*z.y)+c; + } + float t = i/(float)max_iter; + return float4(t,0.5*t,1.0-t,1); +} diff --git a/niacin/compiler.py b/niacin/compiler.py new file mode 100644 index 0000000..5d14f12 --- /dev/null +++ b/niacin/compiler.py @@ -0,0 +1,2403 @@ +#!/usr/bin/env python3 +""" +Bytecode VM with Java/C++-like syntax +Includes disassembler and debugger with improved type safety and error handling +""" + +import struct +import sys +import os +from enum import IntEnum +from dataclasses import dataclass +from typing import List, Optional, Any, Dict, Tuple, Union +import re +import traceback +from collections import defaultdict + +# ============================================================================ +# TOKEN DEFINITIONS +# ============================================================================ + +class TokenType(IntEnum): + # Keywords + FUN = 0 + IF = 1 + ELSE = 2 + WHILE = 3 + FOR = 4 + RETURN = 5 + UINT = 6 + INT = 7 + FLOAT = 8 + BOOL = 9 + CHAR = 10 + STR = 11 + TRUE = 12 + FALSE = 13 + U8 = 14 + U16 = 15 + U32 = 16 + I8 = 17 + I16 = 18 + I32 = 19 + + # Identifiers and literals + IDENTIFIER = 20 + INT_LITERAL = 21 + FLOAT_LITERAL = 22 + STRING_LITERAL = 23 + BOOL_LITERAL = 24 + + # Operators + PLUS = 30 + MINUS = 31 + STAR = 32 + SLASH = 33 + PERCENT = 34 + ASSIGN = 35 + PLUS_ASSIGN = 36 + MINUS_ASSIGN = 37 + STAR_ASSIGN = 38 + SLASH_ASSIGN = 39 + EQ = 40 + NEQ = 41 + LT = 42 + GT = 43 + LE = 44 + GE = 45 + AND = 46 + OR = 47 + NOT = 48 + INCREMENT = 49 + DECREMENT = 50 + + # Delimiters + LPAREN = 60 + RPAREN = 61 + LBRACE = 62 + RBRACE = 63 + SEMICOLON = 64 + COMMA = 65 + + # End of file + EOF = 99 + +@dataclass +class Token: + type: TokenType + value: Any + line: int + col: int + filename: str = "" + + def __str__(self): + return f"Token({self.type.name}, {repr(self.value)}, line {self.line}, col {self.col})" + +# ============================================================================ +# LEXER +# ============================================================================ + +class Lexer: + def __init__(self, source: str, filename: str = ""): + self.source = source + self.filename = filename + self.position = 0 + self.line = 1 + self.col = 1 + self.current_char = self.source[0] if source else None + + # Keyword mapping + self.keywords = { + 'fun': TokenType.FUN, + 'if': TokenType.IF, + 'else': TokenType.ELSE, + 'while': TokenType.WHILE, + 'for': TokenType.FOR, + 'return': TokenType.RETURN, + 'uint': TokenType.UINT, + 'int': TokenType.INT, + 'float': TokenType.FLOAT, + 'bool': TokenType.BOOL, + 'char': TokenType.CHAR, + 'str': TokenType.STR, + 'true': TokenType.TRUE, + 'false': TokenType.FALSE, + 'u8': TokenType.U8, + 'u16': TokenType.U16, + 'u32': TokenType.U32, + 'i8': TokenType.I8, + 'i16': TokenType.I16, + 'i32': TokenType.I32, + } + + def error(self, message: str): + """Raise a lexer error with position information""" + raise SyntaxError(f"{self.filename}:{self.line}:{self.col}: {message}") + + def advance(self): + """Advance to the next character""" + if self.position >= len(self.source) - 1: + self.current_char = None + return + + self.position += 1 + if self.current_char == '\n': + self.line += 1 + self.col = 1 + else: + self.col += 1 + self.current_char = self.source[self.position] + + def skip_whitespace(self): + """Skip whitespace and comments""" + while self.current_char is not None: + if self.current_char in ' \t\r': + self.advance() + elif self.current_char == '\n': + self.advance() + elif self.current_char == '/': + if self.position + 1 < len(self.source) and self.source[self.position + 1] == '/': + # Single line comment + while self.current_char is not None and self.current_char != '\n': + self.advance() + elif self.position + 1 < len(self.source) and self.source[self.position + 1] == '*': + # Multi-line comment + self.advance() # Skip / + self.advance() # Skip * + while (self.current_char is not None and + not (self.current_char == '*' and + self.position + 1 < len(self.source) and + self.source[self.position + 1] == '/')): + self.advance() + if self.current_char is None: + self.error("Unterminated multi-line comment") + self.advance() # Skip * + self.advance() # Skip / + else: + break + else: + break + + def number(self): + """Parse a number (integer or float)""" + start_line, start_col = self.line, self.col + result = '' + + while self.current_char is not None and self.current_char.isdigit(): + result += self.current_char + self.advance() + + if self.current_char == '.': + result += self.current_char + self.advance() + while self.current_char is not None and self.current_char.isdigit(): + result += self.current_char + self.advance() + return Token(TokenType.FLOAT_LITERAL, float(result), start_line, start_col, self.filename) + else: + return Token(TokenType.INT_LITERAL, int(result), start_line, start_col, self.filename) + + def string(self): + """Parse a string literal""" + start_line, start_col = self.line, self.col + self.advance() # Skip opening quote + result = '' + + while self.current_char is not None and self.current_char != '"': + if self.current_char == '\\': + self.advance() + if self.current_char == 'n': + result += '\n' + elif self.current_char == 't': + result += '\t' + elif self.current_char == 'r': + result += '\r' + elif self.current_char == '0': + result += '\0' + elif self.current_char == '\\': + result += '\\' + elif self.current_char == '"': + result += '"' + else: + result += '\\' + self.current_char + else: + result += self.current_char + self.advance() + + if self.current_char != '"': + self.error("Unterminated string literal") + + self.advance() # Skip closing quote + return Token(TokenType.STRING_LITERAL, result, start_line, start_col, self.filename) + + def identifier(self): + """Parse an identifier or keyword""" + start_line, start_col = self.line, self.col + result = '' + + while (self.current_char is not None and + (self.current_char.isalnum() or self.current_char == '_')): + result += self.current_char + self.advance() + + # Check if it's a keyword + token_type = self.keywords.get(result, TokenType.IDENTIFIER) + + # Handle boolean literals + if token_type == TokenType.TRUE: + return Token(TokenType.BOOL_LITERAL, True, start_line, start_col, self.filename) + elif token_type == TokenType.FALSE: + return Token(TokenType.BOOL_LITERAL, False, start_line, start_col, self.filename) + + return Token(token_type, result, start_line, start_col, self.filename) + + def next_token(self): + """Get the next token from the source""" + self.skip_whitespace() + + if self.current_char is None: + return Token(TokenType.EOF, None, self.line, self.col, self.filename) + + start_line, start_col = self.line, self.col + + # Single character tokens + if self.current_char == '(': + self.advance() + return Token(TokenType.LPAREN, '(', start_line, start_col, self.filename) + elif self.current_char == ')': + self.advance() + return Token(TokenType.RPAREN, ')', start_line, start_col, self.filename) + elif self.current_char == '{': + self.advance() + return Token(TokenType.LBRACE, '{', start_line, start_col, self.filename) + elif self.current_char == '}': + self.advance() + return Token(TokenType.RBRACE, '}', start_line, start_col, self.filename) + elif self.current_char == ';': + self.advance() + return Token(TokenType.SEMICOLON, ';', start_line, start_col, self.filename) + elif self.current_char == ',': + self.advance() + return Token(TokenType.COMMA, ',', start_line, start_col, self.filename) + + # Multi-character operators + elif self.current_char == '+': + self.advance() + if self.current_char == '+': + self.advance() + return Token(TokenType.INCREMENT, '++', start_line, start_col, self.filename) + elif self.current_char == '=': + self.advance() + return Token(TokenType.PLUS_ASSIGN, '+=', start_line, start_col, self.filename) + else: + return Token(TokenType.PLUS, '+', start_line, start_col, self.filename) + + elif self.current_char == '-': + self.advance() + if self.current_char == '-': + self.advance() + return Token(TokenType.DECREMENT, '--', start_line, start_col, self.filename) + elif self.current_char == '=': + self.advance() + return Token(TokenType.MINUS_ASSIGN, '-=', start_line, start_col, self.filename) + else: + return Token(TokenType.MINUS, '-', start_line, start_col, self.filename) + + elif self.current_char == '*': + self.advance() + if self.current_char == '=': + self.advance() + return Token(TokenType.STAR_ASSIGN, '*=', start_line, start_col, self.filename) + else: + return Token(TokenType.STAR, '*', start_line, start_col, self.filename) + + elif self.current_char == '/': + self.advance() + if self.current_char == '=': + self.advance() + return Token(TokenType.SLASH_ASSIGN, '/=', start_line, start_col, self.filename) + else: + return Token(TokenType.SLASH, '/', start_line, start_col, self.filename) + + elif self.current_char == '%': + self.advance() + return Token(TokenType.PERCENT, '%', start_line, start_col, self.filename) + + elif self.current_char == '=': + self.advance() + if self.current_char == '=': + self.advance() + return Token(TokenType.EQ, '==', start_line, start_col, self.filename) + else: + return Token(TokenType.ASSIGN, '=', start_line, start_col, self.filename) + + elif self.current_char == '!': + self.advance() + if self.current_char == '=': + self.advance() + return Token(TokenType.NEQ, '!=', start_line, start_col, self.filename) + else: + return Token(TokenType.NOT, '!', start_line, start_col, self.filename) + + elif self.current_char == '<': + self.advance() + if self.current_char == '=': + self.advance() + return Token(TokenType.LE, '<=', start_line, start_col, self.filename) + else: + return Token(TokenType.LT, '<', start_line, start_col, self.filename) + + elif self.current_char == '>': + self.advance() + if self.current_char == '=': + self.advance() + return Token(TokenType.GE, '>=', start_line, start_col, self.filename) + else: + return Token(TokenType.GT, '>', start_line, start_col, self.filename) + + elif self.current_char == '&': + self.advance() + if self.current_char == '&': + self.advance() + return Token(TokenType.AND, '&&', start_line, start_col, self.filename) + else: + self.error("Single '&' not supported, use '&&' for logical AND") + + elif self.current_char == '|': + self.advance() + if self.current_char == '|': + self.advance() + return Token(TokenType.OR, '||', start_line, start_col, self.filename) + else: + self.error("Single '|' not supported, use '||' for logical OR") + + # Numbers + elif self.current_char.isdigit(): + return self.number() + + # Strings + elif self.current_char == '"': + return self.string() + + # Identifiers + elif self.current_char.isalpha() or self.current_char == '_': + return self.identifier() + + else: + self.error(f"Unexpected character: '{self.current_char}'") + +# ============================================================================ +# OPCODES +# ============================================================================ + +class Opcode(IntEnum): + PUSH_CONST = 0x01 + PUSH_INT = 0x02 + PUSH_FLOAT = 0x03 + PUSH_STR = 0x04 + + LOAD_LOCAL = 0x10 + STORE_LOCAL = 0x11 + + ADD = 0x20 + SUB = 0x21 + MUL = 0x22 + DIV = 0x23 + MOD = 0x24 + NEG = 0x25 + BIT_AND = 0x26 + BIT_OR = 0x27 + BIT_XOR = 0x28 + SHL = 0x29 + SHR = 0x2A + + FADD = 0x30 + FSUB = 0x31 + FMUL = 0x32 + FDIV = 0x33 + FNEG = 0x34 + + CMP_EQ = 0x40 + CMP_NEQ = 0x41 + CMP_LT = 0x42 + CMP_GT = 0x43 + CMP_LE = 0x44 + CMP_GE = 0x45 + + JMP = 0x50 + JMP_IF = 0x51 + JMP_IF_NOT = 0x52 + + CALL = 0x60 + RET = 0x61 + + CONST_CAST = 0x70 + TRUNC = 0x71 + TO_FLOAT = 0x72 + TO_INT = 0x73 + + DUP = 0x80 + POP = 0x81 + + PRINT = 0x90 + HALT = 0xA0 + +# Type codes +class TypeCode(IntEnum): + I8 = 0x01 + U8 = 0x02 + I16 = 0x03 + U16 = 0x04 + I32 = 0x05 + U32 = 0x06 + F32 = 0x07 + BOOL = 0x08 + CHAR = 0x09 + STR = 0x0A + +# ============================================================================ +# VALUE REPRESENTATION +# ============================================================================ + +@dataclass +class Value: + """Runtime value container with type safety""" + type_code: TypeCode + data: Any + + def __post_init__(self): + """Validate value data matches type""" + self._validate_type() + + def _validate_type(self): + """Validate that data matches the type code""" + type_validators = { + TypeCode.I8: lambda x: isinstance(x, int) and -128 <= x <= 127, + TypeCode.U8: lambda x: isinstance(x, int) and 0 <= x <= 255, + TypeCode.I16: lambda x: isinstance(x, int) and -32768 <= x <= 32767, + TypeCode.U16: lambda x: isinstance(x, int) and 0 <= x <= 65535, + TypeCode.I32: lambda x: isinstance(x, int), + TypeCode.U32: lambda x: isinstance(x, int) and x >= 0, + TypeCode.F32: lambda x: isinstance(x, float), + TypeCode.BOOL: lambda x: isinstance(x, bool), + TypeCode.CHAR: lambda x: isinstance(x, str) and len(x) == 1, + TypeCode.STR: lambda x: isinstance(x, str), + } + + validator = type_validators.get(self.type_code) + if validator and not validator(self.data): + raise TypeError(f"Value {self.data} is not valid for type {self.type_code.name}") + + def to_bool(self) -> bool: + """Convert to boolean""" + if self.type_code == TypeCode.BOOL: + return self.data + elif self.type_code in [TypeCode.I8, TypeCode.U8, TypeCode.I16, TypeCode.U16, TypeCode.I32, TypeCode.U32]: + return bool(self.data) + elif self.type_code == TypeCode.F32: + return bool(self.data) + elif self.type_code == TypeCode.CHAR: + return bool(ord(self.data)) + elif self.type_code == TypeCode.STR: + return bool(self.data) + return False + + def to_int(self) -> int: + """Convert to integer""" + if self.type_code in [TypeCode.I8, TypeCode.U8, TypeCode.I16, TypeCode.U16, TypeCode.I32, TypeCode.U32]: + return int(self.data) + elif self.type_code == TypeCode.F32: + return int(self.data) + elif self.type_code == TypeCode.BOOL: + return 1 if self.data else 0 + elif self.type_code == TypeCode.CHAR: + return ord(self.data) + return 0 + + def to_float(self) -> float: + """Convert to float""" + return float(self.data) + + def get_type_name(self) -> str: + """Get human-readable type name""" + return self.type_code.name + + def __repr__(self): + return f"Value({self.type_code.name}, {repr(self.data)})" + +# ============================================================================ +# DISASSEMBLER +# ============================================================================ + +class Disassembler: + def __init__(self, bytecode: bytes, filename: str = ""): + self.bytecode = bytecode + self.filename = filename + self.ip = 0 + self.constants = [] + self.functions = [] + + def error(self, message: str): + """Raise a disassembler error with position information""" + raise ValueError(f"{self.filename}:0x{self.ip:08x}: {message}") + + def disassemble(self) -> str: + """Disassemble entire .popclass file""" + output = [] + + # Parse header + magic = self.bytecode[self.ip:self.ip+4] + if magic != b'POPC': + self.error(f"Invalid .popclass file magic: {magic}") + self.ip += 4 + + version_major = struct.unpack(' List[str]: + """Disassemble a single function""" + output = [] + + ip = func['code_offset'] + end_ip = ip + func['code_size'] + + while ip < end_ip: + # Save current IP for this instruction + current_ip = ip + + try: + opcode = self.bytecode[ip] + ip += 1 + + # Get mnemonic + mnemonic = self.get_opcode_mnemonic(opcode) + + # Handle operands based on opcode + operands = [] + + if opcode == Opcode.PUSH_CONST: + const_idx = struct.unpack('= len(self.constants): + self.error(f"Constant index {const_idx} out of range") + const_type, value, type_str = self.constants[const_idx] + operands.append(f"const[{const_idx}] ; {type_str} {value}") + + elif opcode == Opcode.PUSH_INT: + width = self.bytecode[ip] + ip += 1 + value = struct.unpack('= len(self.constants): + self.error(f"Constant index {const_idx} out of range") + const_type, value, type_str = self.constants[const_idx] + operands.append(f"const[{const_idx}] ; \"{value}\"") + + elif opcode in [Opcode.LOAD_LOCAL, Opcode.STORE_LOCAL]: + local_idx = struct.unpack('= len(self.functions): + self.error(f"Function index {func_idx} out of range") + operands.append(f"func[{func_idx}], {arg_count}") + + elif opcode == Opcode.RET: + has_value = self.bytecode[ip] + ip += 1 + operands.append(f"{has_value}") + + else: + # No operands for this opcode + pass + + # Format the instruction + operands_str = ', '.join(operands) if operands else '' + output.append(f"0x{current_ip:08x}: {mnemonic:15} {operands_str}") + + except Exception as e: + output.append(f"0x{current_ip:08x}: ERROR: {e}") + break + + return output + + def get_opcode_mnemonic(self, opcode: int) -> str: + """Convert opcode to mnemonic string""" + try: + return Opcode(opcode).name + except ValueError: + return f"UNKNOWN(0x{opcode:02x})" + +# ============================================================================ +# DEBUGGER +# ============================================================================ + +class Debugger: + def __init__(self, vm): + self.vm = vm + self.breakpoints = set() + self.step_mode = False + self.last_command = None + + def print_status(self): + """Print current VM status""" + print(f"\nIP: 0x{self.vm.ip:08x} | Stack: {len(self.vm.current_frame.stack)} | " + f"Locals: {len(self.vm.current_frame.locals)} | Frame: {len(self.vm.call_stack)}") + + # Show current instruction + if self.vm.ip < len(self.vm.bytecode): + opcode = self.vm.bytecode[self.vm.ip] + try: + print(f"Next: {Opcode(opcode).name}") + except: + print(f"Next: UNKNOWN(0x{opcode:02x})") + + def print_stack(self): + """Print current stack contents""" + if not self.vm.current_frame.stack: + print("Stack: empty") + return + + print("Stack (top to bottom):") + for i, value in enumerate(reversed(self.vm.current_frame.stack)): + print(f" [{len(self.vm.current_frame.stack)-i-1}]: {value}") + + def print_locals(self): + """Print current local variables""" + if not self.vm.current_frame.locals: + print("Locals: none") + return + + print("Local variables:") + for i, value in enumerate(self.vm.current_frame.locals): + print(f" [{i}]: {value}") + + def print_breakpoints(self): + """Print all breakpoints""" + if not self.breakpoints: + print("No breakpoints set") + return + + print("Breakpoints:") + for bp in sorted(self.breakpoints): + print(f" 0x{bp:08x}") + + def disassemble_around(self, ip: int, lines_before: int = 2, lines_after: int = 5): + """Disassemble code around current IP""" + dis = Disassembler(self.vm.bytecode) + + # Find current function + current_func = None + for func in dis.functions: + if func['code_offset'] <= ip < func['code_offset'] + func['code_size']: + current_func = func + break + + if not current_func: + print("Not in any function") + return + + # Disassemble the function and find current instruction + func_code = dis.disassemble_function(current_func) + + # Find current instruction in disassembly + current_line = -1 + for i, line in enumerate(func_code): + if f"0x{ip:08x}:" in line: + current_line = i + break + + if current_line == -1: + print("Could not find current instruction") + return + + # Print surrounding lines + start = max(0, current_line - lines_before) + end = min(len(func_code), current_line + lines_after + 1) + + print(f"Disassembly around 0x{ip:08x}:") + for i in range(start, end): + marker = ">>> " if i == current_line else " " + print(f"{marker}{func_code[i]}") + + def handle_command(self, command: str) -> bool: + """Handle debugger command""" + cmd_parts = command.strip().split() + if not cmd_parts: + return True + + cmd = cmd_parts[0].lower() + + if cmd in ['c', 'continue']: + self.step_mode = False + return False + + elif cmd in ['s', 'step']: + self.step_mode = True + return False + + elif cmd in ['n', 'next']: + # Step over calls + current_ip = self.vm.ip + self.step_mode = True + return False + + elif cmd in ['si', 'stepi']: + # Single instruction + self.execute_single_instruction() + return True + + elif cmd in ['b', 'break']: + if len(cmd_parts) > 1: + try: + if cmd_parts[1].startswith('0x'): + bp = int(cmd_parts[1], 16) + else: + bp = int(cmd_parts[1]) + self.breakpoints.add(bp) + print(f"Breakpoint set at 0x{bp:08x}") + except ValueError: + print("Invalid breakpoint address") + else: + print("Usage: break
") + + elif cmd in ['db', 'delbreak']: + if len(cmd_parts) > 1: + try: + if cmd_parts[1].startswith('0x'): + bp = int(cmd_parts[1], 16) + else: + bp = int(cmd_parts[1]) + if bp in self.breakpoints: + self.breakpoints.remove(bp) + print(f"Breakpoint removed at 0x{bp:08x}") + else: + print("Breakpoint not found") + except ValueError: + print("Invalid breakpoint address") + else: + self.breakpoints.clear() + print("All breakpoints cleared") + + elif cmd in ['bl', 'breaklist']: + self.print_breakpoints() + + elif cmd in ['st', 'stack']: + self.print_stack() + + elif cmd in ['l', 'locals']: + self.print_locals() + + elif cmd in ['d', 'disasm']: + lines = 10 + if len(cmd_parts) > 1: + try: + lines = int(cmd_parts[1]) + except ValueError: + pass + self.disassemble_around(self.vm.ip, lines_after=lines) + + elif cmd in ['p', 'print']: + if len(cmd_parts) > 1: + # Try to evaluate expression (simple for now) + expr = ' '.join(cmd_parts[1:]) + if expr == 'stack': + self.print_stack() + elif expr == 'locals': + self.print_locals() + elif expr == 'ip': + print(f"IP: 0x{self.vm.ip:08x}") + else: + print(f"Unknown expression: {expr}") + else: + self.print_status() + + elif cmd in ['h', 'help', '?']: + self.print_help() + + elif cmd in ['q', 'quit']: + print("Debugger exited") + sys.exit(0) + + else: + print(f"Unknown command: {cmd}") + self.print_help() + + return True + + def execute_single_instruction(self): + """Execute a single instruction""" + if self.vm.halted: + print("VM is halted") + return + + old_ip = self.vm.ip + try: + self.vm.execute_instruction() + print(f"Executed: 0x{old_ip:08x} -> 0x{self.vm.ip:08x}") + self.disassemble_around(self.vm.ip, lines_before=0, lines_after=1) + except Exception as e: + print(f"Error executing instruction at 0x{old_ip:08x}: {e}") + + def print_help(self): + """Print debugger help""" + print("Debugger commands:") + print(" c, continue - Continue execution") + print(" s, step - Step into functions") + print(" n, next - Step over functions") + print(" si, stepi - Single instruction step") + print(" b, break - Set breakpoint") + print(" db, delbreak - Delete breakpoint (or all)") + print(" bl, breaklist - List breakpoints") + print(" st, stack - Show stack") + print(" l, locals - Show local variables") + print(" d, disasm [n] - Disassemble around IP") + print(" p, print [expr]- Print status or expression") + print(" h, help, ? - This help") + print(" q, quit - Quit debugger") + + def run(self): + """Run debugger main loop""" + print("POP VM Debugger started") + print("Type 'help' for commands") + + while not self.vm.halted: + # Check breakpoints + if self.vm.ip in self.breakpoints: + print(f"\nBreakpoint hit at 0x{self.vm.ip:08x}") + self.step_mode = True + + if self.step_mode: + self.print_status() + try: + command = input("\ndbg> ") + if self.handle_command(command): + continue # Stay in step mode + else: + # Command said to continue + pass + except EOFError: + print("\nExiting debugger") + break + except KeyboardInterrupt: + print("\nInterrupted") + self.step_mode = True + continue + + # Execute instruction + try: + self.vm.execute_instruction() + except Exception as e: + print(f"Runtime error at IP 0x{self.vm.ip:08x}: {e}") + break + +# ============================================================================ +# VM IMPLEMENTATION +# ============================================================================ + +class Frame: + """Call frame with type safety""" + def __init__(self, func_idx: int, return_ip: int, arg_values: List[Value]): + self.func_idx = func_idx + self.return_ip = return_ip + self.locals = arg_values[:] # Copy arguments + self.stack: List[Value] = [] + + def __repr__(self): + return f"Frame(func={self.func_idx}, return_ip=0x{self.return_ip:08x}, locals={len(self.locals)}, stack={len(self.stack)})" + +class VM: + def __init__(self, bytecode: bytes, filename: str = ""): + self.bytecode = bytecode + self.filename = filename + self.ip = 0 + self.constants: List[Value] = [] + self.functions: List[Dict] = [] + self.call_stack: List[Frame] = [] + self.current_frame: Optional[Frame] = None + self.halted = False + + self.load_bytecode() + + def error(self, message: str): + """Raise a VM error with position information""" + raise RuntimeError(f"{self.filename}:0x{self.ip:08x}: {message}") + + def load_bytecode(self): + """Load bytecode into VM with validation""" + self.ip = 0 + + # Check magic + magic = self.bytecode[self.ip:self.ip+4] + if magic != b'POPC': + self.error(f"Invalid .popclass file magic: {magic}") + self.ip += 4 + + # Version + version_major = struct.unpack(' int: + """Fetch one byte and advance IP""" + if self.ip >= len(self.bytecode): + self.error("Unexpected end of bytecode") + b = self.bytecode[self.ip] + self.ip += 1 + return b + + def fetch_u16(self) -> int: + """Fetch u16 and advance IP""" + if self.ip + 2 > len(self.bytecode): + self.error("Unexpected end of bytecode while reading u16") + value = struct.unpack(' int: + """Fetch u32 and advance IP""" + if self.ip + 4 > len(self.bytecode): + self.error("Unexpected end of bytecode while reading u32") + value = struct.unpack(' int: + """Fetch i32 and advance IP""" + if self.ip + 4 > len(self.bytecode): + self.error("Unexpected end of bytecode while reading i32") + value = struct.unpack(' float: + """Fetch f32 and advance IP""" + if self.ip + 4 > len(self.bytecode): + self.error("Unexpected end of bytecode while reading f32") + value = struct.unpack(' Value: + """Pop value from current frame's stack""" + if not self.current_frame.stack: + self.error("Stack underflow") + return self.current_frame.stack.pop() + + def type_check_binary_op(self, a: Value, b: Value, op: str) -> Tuple[TypeCode, TypeCode]: + """Check if types are compatible for binary operation""" + # Allow numeric types to mix with some restrictions + numeric_types = {TypeCode.I8, TypeCode.U8, TypeCode.I16, TypeCode.U16, TypeCode.I32, TypeCode.U32, TypeCode.F32} + + if a.type_code in numeric_types and b.type_code in numeric_types: + # For mixed operations, promote to the wider type + if a.type_code == TypeCode.F32 or b.type_code == TypeCode.F32: + return (TypeCode.F32, TypeCode.F32) + else: + # Both are integers, use I32 as common type + return (TypeCode.I32, TypeCode.I32) + + # For boolean operations + if op in ['==', '!=', '<', '>', '<=', '>=']: + if a.type_code == b.type_code: + return (a.type_code, b.type_code) + + self.error(f"Type mismatch in {op}: {a.get_type_name()} and {b.get_type_name()}") + + def run(self, entry_func: int = 0, debug: bool = False): + """Run VM starting from entry function""" + if entry_func >= len(self.functions): + self.error(f"Invalid entry function index: {entry_func}") + + # Set up initial frame + func = self.functions[entry_func] + self.current_frame = Frame(entry_func, -1, []) + + # Initialize locals + for _ in range(func['local_count']): + self.current_frame.locals.append(Value(TypeCode.I32, 0)) + + self.ip = func['code_offset'] + self.call_stack = [self.current_frame] + + if debug: + debugger = Debugger(self) + debugger.run() + else: + while not self.halted and self.ip < len(self.bytecode): + self.execute_instruction() + + def execute_instruction(self): + """Execute one instruction with type safety""" + opcode = self.fetch_byte() + + if opcode == Opcode.PUSH_CONST: + idx = self.fetch_u32() + if idx >= len(self.constants): + self.error(f"Constant index {idx} out of range") + self.push(self.constants[idx]) + + elif opcode == Opcode.PUSH_INT: + width = self.fetch_byte() + value = self.fetch_i32() + + if width == 8: + type_code = TypeCode.I8 + elif width == 16: + type_code = TypeCode.I16 + else: + type_code = TypeCode.I32 + + self.push(Value(type_code, value)) + + elif opcode == Opcode.PUSH_FLOAT: + value = self.fetch_f32() + self.push(Value(TypeCode.F32, value)) + + elif opcode == Opcode.PUSH_STR: + idx = self.fetch_u32() + if idx >= len(self.constants): + self.error(f"Constant index {idx} out of range") + self.push(self.constants[idx]) + + elif opcode == Opcode.LOAD_LOCAL: + idx = self.fetch_u16() + if idx >= len(self.current_frame.locals): + self.error(f"Local variable index {idx} out of range") + self.push(self.current_frame.locals[idx]) + + elif opcode == Opcode.STORE_LOCAL: + idx = self.fetch_u16() + value = self.pop() + if idx >= len(self.current_frame.locals): + # Extend locals if needed + self.current_frame.locals.extend([Value(TypeCode.I32, 0)] * (idx - len(self.current_frame.locals) + 1)) + self.current_frame.locals[idx] = value + + elif opcode == Opcode.ADD: + a = self.pop() + b = self.pop() + a_type, b_type = self.type_check_binary_op(a, b, '+') + + if a_type == TypeCode.F32: + result = b.to_float() + a.to_float() + self.push(Value(TypeCode.F32, result)) + else: + result = b.to_int() + a.to_int() + self.push(Value(TypeCode.I32, result)) + + elif opcode == Opcode.SUB: + a = self.pop() + b = self.pop() + a_type, b_type = self.type_check_binary_op(a, b, '-') + + if a_type == TypeCode.F32: + result = b.to_float() - a.to_float() + self.push(Value(TypeCode.F32, result)) + else: + result = b.to_int() - a.to_int() + self.push(Value(TypeCode.I32, result)) + + elif opcode == Opcode.MUL: + a = self.pop() + b = self.pop() + a_type, b_type = self.type_check_binary_op(a, b, '*') + + if a_type == TypeCode.F32: + result = b.to_float() * a.to_float() + self.push(Value(TypeCode.F32, result)) + else: + result = b.to_int() * a.to_int() + self.push(Value(TypeCode.I32, result)) + + elif opcode == Opcode.DIV: + a = self.pop() + b = self.pop() + a_type, b_type = self.type_check_binary_op(a, b, '/') + + if a.to_int() == 0 or a.to_float() == 0: + self.error("Division by zero") + + if a_type == TypeCode.F32: + result = b.to_float() / a.to_float() + self.push(Value(TypeCode.F32, result)) + else: + result = b.to_int() // a.to_int() + self.push(Value(TypeCode.I32, result)) + + elif opcode == Opcode.MOD: + a = self.pop() + b = self.pop() + a_type, b_type = self.type_check_binary_op(a, b, '%') + + if a.to_int() == 0: + self.error("Modulo by zero") + + result = b.to_int() % a.to_int() + self.push(Value(TypeCode.I32, result)) + + elif opcode == Opcode.NEG: + a = self.pop() + if a.type_code == TypeCode.F32: + result = -a.to_float() + self.push(Value(TypeCode.F32, result)) + else: + result = -a.to_int() + self.push(Value(TypeCode.I32, result)) + + elif opcode == Opcode.BIT_AND: + a = self.pop() + b = self.pop() + result = b.to_int() & a.to_int() + self.push(Value(TypeCode.I32, result)) + + elif opcode == Opcode.BIT_OR: + a = self.pop() + b = self.pop() + result = b.to_int() | a.to_int() + self.push(Value(TypeCode.I32, result)) + + elif opcode == Opcode.BIT_XOR: + a = self.pop() + b = self.pop() + result = b.to_int() ^ a.to_int() + self.push(Value(TypeCode.I32, result)) + + elif opcode == Opcode.FADD: + a = self.pop() + b = self.pop() + result = b.to_float() + a.to_float() + self.push(Value(TypeCode.F32, result)) + + elif opcode == Opcode.FSUB: + a = self.pop() + b = self.pop() + result = b.to_float() - a.to_float() + self.push(Value(TypeCode.F32, result)) + + elif opcode == Opcode.FMUL: + a = self.pop() + b = self.pop() + result = b.to_float() * a.to_float() + self.push(Value(TypeCode.F32, result)) + + elif opcode == Opcode.FDIV: + a = self.pop() + b = self.pop() + if a.to_float() == 0: + self.error("Division by zero") + result = b.to_float() / a.to_float() + self.push(Value(TypeCode.F32, result)) + + elif opcode == Opcode.FNEG: + a = self.pop() + result = -a.to_float() + self.push(Value(TypeCode.F32, result)) + + elif opcode == Opcode.CMP_EQ: + a = self.pop() + b = self.pop() + result = b.data == a.data + self.push(Value(TypeCode.BOOL, result)) + + elif opcode == Opcode.CMP_NEQ: + a = self.pop() + b = self.pop() + result = b.data != a.data + self.push(Value(TypeCode.BOOL, result)) + + elif opcode == Opcode.CMP_LT: + a = self.pop() + b = self.pop() + a_type, b_type = self.type_check_binary_op(a, b, '<') + + if a_type == TypeCode.F32: + result = b.to_float() < a.to_float() + else: + result = b.to_int() < a.to_int() + self.push(Value(TypeCode.BOOL, result)) + + elif opcode == Opcode.CMP_GT: + a = self.pop() + b = self.pop() + a_type, b_type = self.type_check_binary_op(a, b, '>') + + if a_type == TypeCode.F32: + result = b.to_float() > a.to_float() + else: + result = b.to_int() > a.to_int() + self.push(Value(TypeCode.BOOL, result)) + + elif opcode == Opcode.CMP_LE: + a = self.pop() + b = self.pop() + a_type, b_type = self.type_check_binary_op(a, b, '<=') + + if a_type == TypeCode.F32: + result = b.to_float() <= a.to_float() + else: + result = b.to_int() <= a.to_int() + self.push(Value(TypeCode.BOOL, result)) + + elif opcode == Opcode.CMP_GE: + a = self.pop() + b = self.pop() + a_type, b_type = self.type_check_binary_op(a, b, '>=') + + if a_type == TypeCode.F32: + result = b.to_float() >= a.to_float() + else: + result = b.to_int() >= a.to_int() + self.push(Value(TypeCode.BOOL, result)) + + elif opcode == Opcode.JMP: + offset = self.fetch_i32() + self.ip += offset + + elif opcode == Opcode.JMP_IF: + offset = self.fetch_i32() + cond = self.pop() + if cond.to_bool(): + self.ip += offset + + elif opcode == Opcode.JMP_IF_NOT: + offset = self.fetch_i32() + cond = self.pop() + if not cond.to_bool(): + self.ip += offset + + elif opcode == Opcode.CALL: + func_idx = self.fetch_u16() + arg_count = self.fetch_byte() + + if func_idx >= len(self.functions): + self.error(f"Function index {func_idx} out of range") + + # Pop arguments + args = [] + for _ in range(arg_count): + args.insert(0, self.pop()) + + # Validate argument count + func = self.functions[func_idx] + if len(args) != func['arg_count']: + self.error(f"Function expects {func['arg_count']} arguments, got {len(args)}") + + # Save return address + return_ip = self.ip + + # Create new frame + new_frame = Frame(func_idx, return_ip, args) + + # Initialize remaining locals + for _ in range(func['local_count'] - arg_count): + new_frame.locals.append(Value(TypeCode.I32, 0)) + + self.call_stack.append(new_frame) + self.current_frame = new_frame + self.ip = func['code_offset'] + + elif opcode == Opcode.RET: + has_value = self.fetch_byte() + + return_value = None + if has_value: + return_value = self.pop() + + # Pop frame + self.call_stack.pop() + + if not self.call_stack: + # Returned from main + self.halted = True + if return_value: + print(f"Program returned: {return_value.data}") + return + + # Restore previous frame + self.current_frame = self.call_stack[-1] + self.ip = self.current_frame.return_ip + + # Push return value if any + if return_value: + self.push(return_value) + + elif opcode == Opcode.DUP: + if not self.current_frame.stack: + self.error("Cannot DUP from empty stack") + value = self.current_frame.stack[-1] + self.push(Value(value.type_code, value.data)) + + elif opcode == Opcode.POP: + self.pop() + + elif opcode == Opcode.PRINT: + value = self.pop() + print(value.data) + + elif opcode == Opcode.HALT: + self.halted = True + + else: + self.error(f"Unknown opcode: 0x{opcode:02X}") + +# ============================================================================ +# COMPILER WITH PROPER FOR LOOP SUPPORT +# ============================================================================ + +class Compiler: + def __init__(self, source: str, filename: str = ""): + self.lexer = Lexer(source, filename) + self.filename = filename + self.current_token = self.lexer.next_token() + self.constants: List[Tuple[TypeCode, Any]] = [] + self.functions: List[Dict] = [] + self.current_function: Optional[Dict] = None + self.local_vars: Dict[str, int] = {} + self.local_count = 0 + self.loop_stack: List[Dict] = [] # For break/continue in loops + + def error(self, msg: str): + """Raise a compilation error with position information""" + raise SyntaxError(f"{self.filename}:{self.current_token.line}:{self.current_token.col}: {msg}") + + def expect(self, token_type: TokenType, context: str = ""): + """Expect a specific token type, provide context for better error messages""" + if self.current_token.type != token_type: + context_str = f" in {context}" if context else "" + self.error(f"Expected {token_type.name}{context_str}, but got {self.current_token.type.name}") + self.current_token = self.lexer.next_token() + + def eat(self, token_type: TokenType): + """Compatibility alias for expect""" + self.expect(token_type) + + def add_constant(self, type_code: TypeCode, value: Any) -> int: + """Add constant to pool and return index""" + for i, (tc, v) in enumerate(self.constants): + if tc == type_code and v == value: + return i + self.constants.append((type_code, value)) + return len(self.constants) - 1 + + def emit(self, *bytes_data): + """Emit bytes to current function's code""" + for b in bytes_data: + if isinstance(b, int): + self.current_function['code'].append(b) + elif isinstance(b, bytes): + self.current_function['code'].extend(b) + + def compile(self) -> bytes: + """Main compilation entry point""" + try: + while self.current_token.type != TokenType.EOF: + if self.current_token.type == TokenType.FUN: + self.compile_function() + else: + self.error("Expected function definition") + + return self.generate_bytecode() + except Exception as e: + # Add context to compilation errors + if not isinstance(e, SyntaxError): + self.error(str(e)) + else: + raise + + def compile_function(self): + """Compile function definition""" + self.expect(TokenType.FUN, "function definition") + + func_name = self.current_token.value + self.expect(TokenType.IDENTIFIER, "function name") + + self.expect(TokenType.LPAREN, "function parameter list") + + # Parse parameters + params = [] + while self.current_token.type != TokenType.RPAREN: + param_type = self.parse_type() + param_name = self.current_token.value + self.expect(TokenType.IDENTIFIER, "parameter name") + params.append((param_type, param_name)) + + if self.current_token.type == TokenType.COMMA: + self.eat(TokenType.COMMA) + + self.expect(TokenType.RPAREN, "function parameter list") + + # Set up function + self.current_function = { + 'name': func_name, + 'arg_count': len(params), + 'code': [], + 'labels': {}, # For break/continue labels + } + + # Set up locals + self.local_vars = {} + self.local_count = 0 + + # Parameters become first locals + for _, param_name in params: + self.local_vars[param_name] = self.local_count + self.local_count += 1 + + # Parse body + self.expect(TokenType.LBRACE, "function body") + while self.current_token.type != TokenType.RBRACE: + self.compile_statement() + self.expect(TokenType.RBRACE, "function body") + + # Add implicit return if not present + if not self.current_function['code'] or self.current_function['code'][-1] != Opcode.RET: + self.emit(Opcode.RET, 0) + + self.current_function['local_count'] = self.local_count + self.functions.append(self.current_function) + self.current_function = None + + def parse_type(self) -> TypeCode: + """Parse type specification with better error reporting""" + type_map = { + TokenType.U8: TypeCode.U8, + TokenType.U16: TypeCode.U16, + TokenType.U32: TypeCode.U32, + TokenType.I8: TypeCode.I8, + TokenType.I16: TypeCode.I16, + TokenType.I32: TypeCode.I32, + TokenType.FLOAT: TypeCode.F32, + TokenType.BOOL: TypeCode.BOOL, + TokenType.CHAR: TypeCode.CHAR, + TokenType.STR: TypeCode.STR, + TokenType.UINT: TypeCode.U32, + TokenType.INT: TypeCode.I32, + } + + if self.current_token.type in type_map: + type_code = type_map[self.current_token.type] + self.current_token = self.lexer.next_token() + return type_code + else: + self.error(f"Expected type specification, got {self.current_token.type.name}") + + def compile_statement(self): + """Compile a statement with better error context""" + try: + if self.current_token.type in [TokenType.U8, TokenType.U16, TokenType.U32, + TokenType.I8, TokenType.I16, TokenType.I32, + TokenType.FLOAT, TokenType.BOOL, TokenType.CHAR, + TokenType.STR, TokenType.UINT, TokenType.INT]: + self.compile_var_declaration() + elif self.current_token.type == TokenType.IF: + self.compile_if_statement() + elif self.current_token.type == TokenType.WHILE: + self.compile_while_statement() + elif self.current_token.type == TokenType.FOR: + self.compile_for_statement() + elif self.current_token.type == TokenType.RETURN: + self.compile_return_statement() + elif self.current_token.type == TokenType.LBRACE: + self.compile_block() + elif self.current_token.type == TokenType.IDENTIFIER: + self.compile_assignment_or_call() + else: + self.error(f"Unexpected statement starting with {self.current_token.type.name}") + except Exception as e: + # Add context to statement compilation errors + if not isinstance(e, SyntaxError): + self.error(f"Error in statement: {e}") + else: + raise + + def compile_block(self): + """Compile a block of statements""" + self.expect(TokenType.LBRACE, "block start") + + # Save current locals to restore after block + old_locals = self.local_vars.copy() + old_local_count = self.local_count + + while self.current_token.type != TokenType.RBRACE: + self.compile_statement() + + self.expect(TokenType.RBRACE, "block end") + + # Restore locals (block scoping) + self.local_vars = old_locals + self.local_count = old_local_count + + def compile_var_declaration(self): + """Compile variable declaration with type checking""" + var_type = self.parse_type() + var_name = self.current_token.value + self.expect(TokenType.IDENTIFIER, "variable name") + + # Check for redeclaration + if var_name in self.local_vars: + self.error(f"Redeclaration of variable '{var_name}'") + + # Add to locals + self.local_vars[var_name] = self.local_count + self.local_count += 1 + + local_idx = self.local_vars[var_name] + + if self.current_token.type == TokenType.ASSIGN: + self.eat(TokenType.ASSIGN) + self.compile_expression() + # TODO: Add type checking for assignment + self.emit(Opcode.STORE_LOCAL, *struct.pack(' body_start: # Only if there's an increment + offset = increment_start - (body_end + 5) + self.emit(Opcode.JMP, *struct.pack(' bytes: + """Generate final bytecode""" + bytecode = bytearray() + + # Header + bytecode.extend(b'POPC') # Magic for .popclass files + bytecode.extend(struct.pack(' bool: + """Compile source file to .popclass file with improved error reporting""" + if not os.path.exists(source_file): + print(f"Error: Source file '{source_file}' not found") + return False + + if not output_file: + output_file = os.path.splitext(source_file)[0] + '.popclass' + + try: + with open(source_file, 'r', encoding='utf-8') as f: + source = f.read() + + compiler = Compiler(source, source_file) + bytecode = compiler.compile() + + with open(output_file, 'wb') as f: + f.write(bytecode) + + print(f"Successfully compiled {source_file} to {output_file}") + return True + except SyntaxError as e: + print(f"Compilation error in {e.filename}:{e.lineno}: {e.msg}") + return False + except Exception as e: + print(f"Compilation error: {e}") + traceback.print_exc() + return False + +def disassemble_file(popclass_file: str) -> bool: + """Disassemble .popclass file""" + if not os.path.exists(popclass_file): + print(f"Error: .popclass file '{popclass_file}' not found") + return False + + try: + with open(popclass_file, 'rb') as f: + bytecode = f.read() + + disassembler = Disassembler(bytecode, popclass_file) + disassembly = disassembler.disassemble() + + base_name = os.path.splitext(popclass_file)[0] + disasm_file = base_name + '.popasm' + + # Save disassembly result + with open(disasm_file, 'w', encoding='utf-8') as f: + f.write(disassembly) + + print(disassembly) + return True + except Exception as e: + print(f"Disassembly error: {e}") + traceback.print_exc() + return False + +def execute_popclass(popclass_file: str, debug: bool = False) -> bool: + """Execute .popclass file""" + if not os.path.exists(popclass_file): + print(f"Error: .popclass file '{popclass_file}' not found") + return False + + try: + with open(popclass_file, 'rb') as f: + bytecode = f.read() + + vm = VM(bytecode, popclass_file) + vm.run(debug=debug) + return True + except Exception as e: + print(f"Execution error: {e}") + traceback.print_exc() + return False + +def main(): + if len(sys.argv) < 2: + print("POP VM Tools - Enhanced with Type Safety and Better Error Messages") + print("Usage:") + print(" python interpreter.py compile [output_file]") + print(" python interpreter.py disasm ") + print(" python interpreter.py run ") + print(" python interpreter.py debug ") + print(" python interpreter.py (compiles and runs)") + print("\nFeatures:") + print(" - Improved type safety and error messages") + print(" - C-style for loops with ++/-- operators") + print(" - Better debugging information") + return + + command = sys.argv[1] + + if command == 'compile': + if len(sys.argv) < 3: + print("Error: No source file specified") + return + + source_file = sys.argv[2] + output_file = sys.argv[3] if len(sys.argv) > 3 else None + + compile_source(source_file, output_file) + + elif command == 'disasm': + if len(sys.argv) < 3: + print("Error: No .popclass file specified") + return + + popclass_file = sys.argv[2] + disassemble_file(popclass_file) + + elif command == 'run': + if len(sys.argv) < 3: + print("Error: No .popclass file specified") + return + + popclass_file = sys.argv[2] + execute_popclass(popclass_file) + + elif command == 'debug': + if len(sys.argv) < 3: + print("Error: No .popclass file specified") + return + + popclass_file = sys.argv[2] + execute_popclass(popclass_file, debug=True) + + else: + # Assume it's a source file - compile and run + source_file = sys.argv[1] + + if not os.path.exists(source_file): + print(f"Error: Source file '{source_file}' not found") + return + + # Compile to temporary file + temp_file = 'temp.popclass' + if compile_source(source_file, temp_file): + # Run the compiled file + execute_popclass(temp_file) + + # Clean up temporary file + try: + os.remove(temp_file) + except: + pass + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/niacin/cpp/Makefile b/niacin/cpp/Makefile new file mode 100644 index 0000000..6a1dfdc --- /dev/null +++ b/niacin/cpp/Makefile @@ -0,0 +1,21 @@ +CC = gcc +CFLAGS = -Wall -Wextra -Werror -O2 -std=c99 -D_DEFAULT_SOURCE +CFLAGS += -fstack-protector-strong -D_FORTIFY_SOURCE=2 +LDFLAGS = -z relro -z now + +SOURCES = main.c vm_core.c +OBJECTS = $(SOURCES:.c=.o) +TARGET = popvm + +all: $(TARGET) + +$(TARGET): $(OBJECTS) + $(CC) $(LDFLAGS) -o $@ $^ + +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + rm -f $(OBJECTS) $(TARGET) + +.PHONY: all clean \ No newline at end of file diff --git a/niacin/cpp/main.c b/niacin/cpp/main.c new file mode 100644 index 0000000..97ecfcc --- /dev/null +++ b/niacin/cpp/main.c @@ -0,0 +1,76 @@ +#include "vm_types.h" +#include +#include +#include + +// Safe file reading +static uint8_t* read_file(const char* filename, size_t* size) { + FILE* file = fopen(filename, "rb"); + if (!file) { + fprintf(stderr, "Cannot open file: %s\n", filename); + return NULL; + } + + fseek(file, 0, SEEK_END); + long file_size = ftell(file); + fseek(file, 0, SEEK_SET); + + if (file_size <= 0 || file_size > 10 * 1024 * 1024) { // 10MB limit + fclose(file); + fprintf(stderr, "Invalid file size\n"); + return NULL; + } + + uint8_t* buffer = malloc(file_size); + if (!buffer) { + fclose(file); + fprintf(stderr, "Memory allocation failed\n"); + return NULL; + } + + if (fread(buffer, 1, file_size, file) != (size_t)file_size) { + fclose(file); + free(buffer); + fprintf(stderr, "File read error\n"); + return NULL; + } + + fclose(file); + *size = file_size; + return buffer; +} + +int main(int argc, char* argv[]) { + if (argc != 2) { + printf("Usage: %s \n", argv[0]); + return 1; + } + + size_t file_size; + uint8_t* bytecode = read_file(argv[1], &file_size); + if (!bytecode) { + return 1; + } + + vm_t* vm = vm_create(bytecode, file_size); + if (!vm) { + fprintf(stderr, "Failed to create VM\n"); + free(bytecode); + return 1; + } + + if (!vm_validate_bytecode(vm)) { + fprintf(stderr, "Invalid bytecode file\n"); + vm_destroy(vm); + free(bytecode); + return 1; + } + + printf("Executing program...\n"); + vm_execute(vm); + printf("Program finished\n"); + + vm_destroy(vm); + free(bytecode); + return 0; +} \ No newline at end of file diff --git a/niacin/cpp/sample.popclass b/niacin/cpp/sample.popclass new file mode 100644 index 0000000000000000000000000000000000000000..a98c0240b5e7acf6fa552aaedeb5fa19a2fb774d GIT binary patch literal 51 ncmWIW4{&B=KmZW?KM*hg8T>%D3J^0XfOvuo3<3-c6A~E!y1@pT literal 0 HcmV?d00001 diff --git a/niacin/cpp/vm_core.c b/niacin/cpp/vm_core.c new file mode 100644 index 0000000..e05a861 --- /dev/null +++ b/niacin/cpp/vm_core.c @@ -0,0 +1,285 @@ +#include "vm_types.h" +#include +#include +#include +#include + +// Safe memory allocation with overflow checking +static void* safe_malloc(size_t size) { + if (size == 0 || size > SIZE_MAX / 2) return NULL; + void* ptr = malloc(size); + if (!ptr) { + fprintf(stderr, "Memory allocation failed\n"); + exit(EXIT_FAILURE); + } + return ptr; +} + +static void* safe_realloc(void* ptr, size_t size) { + if (size == 0 || size > SIZE_MAX / 2) return NULL; + void* new_ptr = realloc(ptr, size); + if (!new_ptr) { + fprintf(stderr, "Memory reallocation failed\n"); + exit(EXIT_FAILURE); + } + return new_ptr; +} + +// Initialize VM with security limits +vm_t* vm_create(uint8_t* bytecode, size_t size) { + vm_t* vm = safe_malloc(sizeof(vm_t)); + memset(vm, 0, sizeof(vm_t)); + + vm->bytecode = bytecode; + vm->bytecode_size = size; + vm->ip = 0; + vm->halted = false; + + // Security limits + vm->max_stack_size = 65536; + vm->max_call_depth = 256; + vm->current_call_depth = 0; + + return vm; +} + +// Safe bytecode reading with bounds checking +static bool read_u8(vm_t* vm, uint8_t* result) { + if (vm->ip >= vm->bytecode_size) return false; + *result = vm->bytecode[vm->ip++]; + return true; +} + +static bool read_u16(vm_t* vm, uint16_t* result) { + if (vm->ip + 2 > vm->bytecode_size) return false; + memcpy(result, &vm->bytecode[vm->ip], 2); + vm->ip += 2; + return true; +} + +static bool read_u32(vm_t* vm, uint32_t* result) { + if (vm->ip + 4 > vm->bytecode_size) return false; + memcpy(result, &vm->bytecode[vm->ip], 4); + vm->ip += 4; + return true; +} + +static bool read_i32(vm_t* vm, int32_t* result) { + if (vm->ip + 4 > vm->bytecode_size) return false; + memcpy(result, &vm->bytecode[vm->ip], 4); + vm->ip += 4; + return true; +} + +static bool read_f32(vm_t* vm, float* result) { + if (vm->ip + 4 > vm->bytecode_size) return false; + memcpy(result, &vm->bytecode[vm->ip], 4); + vm->ip += 4; + return true; +} + +// Stack operations with bounds checking +static bool stack_push(frame_t* frame, value_t value) { + if (frame->stack_size >= frame->stack_capacity) { + size_t new_capacity = frame->stack_capacity * 2; + if (new_capacity == 0) new_capacity = 16; + + value_t* new_stack = safe_realloc(frame->stack, new_capacity * sizeof(value_t)); + if (!new_stack) return false; + + frame->stack = new_stack; + frame->stack_capacity = new_capacity; + } + + frame->stack[frame->stack_size++] = value; + return true; +} + +static bool stack_pop(frame_t* frame, value_t* result) { + if (frame->stack_size == 0) return false; + *result = frame->stack[--frame->stack_size]; + return true; +} + +// Type checking and conversion +static bool type_check_binary(value_t a, value_t b, type_code_t* result_type) { + // Simple type checking - in real implementation, add proper type promotion + if (a.type == b.type) { + *result_type = a.type; + return true; + } + return false; +} + +static int32_t value_to_int(value_t val) { + switch (val.type) { + case TYPE_I8: return val.data.i8; + case TYPE_U8: return val.data.u8; + case TYPE_I16: return val.data.i16; + case TYPE_U16: return val.data.u16; + case TYPE_I32: return val.data.i32; + case TYPE_U32: return (int32_t)val.data.u32; + case TYPE_BOOL: return val.data.boolean ? 1 : 0; + case TYPE_CHAR: return (int32_t)val.data.character; + default: return 0; + } +} + +static float value_to_float(value_t val) { + switch (val.type) { + case TYPE_F32: return val.data.f32; + case TYPE_I32: return (float)val.data.i32; + case TYPE_U32: return (float)val.data.u32; + default: return 0.0f; + } +} + +// Bytecode validation +bool vm_validate_bytecode(vm_t* vm) { + // Check magic number + if (vm->bytecode_size < 4 || memcmp(vm->bytecode, "POPC", 4) != 0) { + return false; + } + + // Add more validation as needed + return true; +} + +// Execute single instruction with full error checking +bool vm_execute_instruction(vm_t* vm) { + if (vm->halted || vm->ip >= vm->bytecode_size) return false; + + uint8_t opcode; + if (!read_u8(vm, &opcode)) return false; + + frame_t* frame = vm->current_frame; + if (!frame) return false; + + switch (opcode) { + case OP_PUSH_CONST: { + uint32_t const_idx; + if (!read_u32(vm, &const_idx) || const_idx >= vm->constants_count) { + return false; + } + if (!stack_push(frame, vm->constants[const_idx])) return false; + break; + } + + case OP_PUSH_INT: { + uint8_t width; + int32_t value; + if (!read_u8(vm, &width) || !read_i32(vm, &value)) return false; + + value_t val = { 0 }; + switch (width) { + case 8: val.type = TYPE_I8; val.data.i8 = (int8_t)value; break; + case 16: val.type = TYPE_I16; val.data.i16 = (int16_t)value; break; + case 32: val.type = TYPE_I32; val.data.i32 = value; break; + default: return false; + } + if (!stack_push(frame, val)) return false; + break; + } + + case OP_ADD: { + value_t a, b; + if (!stack_pop(frame, &a) || !stack_pop(frame, &b)) return false; + + type_code_t result_type; + if (!type_check_binary(a, b, &result_type)) return false; + + value_t result = { 0 }; + result.type = result_type; + + if (result_type == TYPE_F32) { + result.data.f32 = value_to_float(b) + value_to_float(a); + } + else { + result.data.i32 = value_to_int(b) + value_to_int(a); + } + + if (!stack_push(frame, result)) return false; + break; + } + + case OP_CMP_EQ: { + value_t a, b; + if (!stack_pop(frame, &a) || !stack_pop(frame, &b)) return false; + + value_t result = { 0 }; + result.type = TYPE_BOOL; + result.data.boolean = (value_to_int(a) == value_to_int(b)); + + if (!stack_push(frame, result)) return false; + break; + } + + case OP_JMP: { + int32_t offset; + if (!read_i32(vm, &offset)) return false; + + // Check for negative jumps (security) + if (offset < 0 && (size_t)(-offset) > vm->ip) return false; + if (vm->ip + offset >= vm->bytecode_size) return false; + + vm->ip += offset; + break; + } + + case OP_PRINT: { + value_t val; + if (!stack_pop(frame, &val)) return false; + + switch (val.type) { + case TYPE_I32: printf("%d\n", val.data.i32); break; + case TYPE_F32: printf("%f\n", val.data.f32); break; + case TYPE_BOOL: printf("%s\n", val.data.boolean ? "true" : "false"); break; + case TYPE_STR: printf("%s\n", val.data.string); break; + default: printf("\n"); break; + } + break; + } + + case OP_HALT: + vm->halted = true; + break; + + default: + fprintf(stderr, "Unknown opcode: 0x%02x\n", opcode); + return false; + } + + return true; +} + +// Main execution loop +void vm_execute(vm_t* vm) { + while (!vm->halted && vm_execute_instruction(vm)) { + // Continue execution + } +} + +// Cleanup +void vm_destroy(vm_t* vm) { + if (!vm) return; + + // Free constants + for (size_t i = 0; i < vm->constants_count; i++) { + if (vm->constants[i].type == TYPE_STR && vm->constants[i].data.string) { + free(vm->constants[i].data.string); + } + } + free(vm->constants); + + // Free functions + free(vm->functions); + + // Free frames (simplified - in real impl, walk call stack) + if (vm->current_frame) { + free(vm->current_frame->locals); + free(vm->current_frame->stack); + free(vm->current_frame); + } + + free(vm); +} \ No newline at end of file diff --git a/niacin/cpp/vm_types.h b/niacin/cpp/vm_types.h new file mode 100644 index 0000000..0f64e8c --- /dev/null +++ b/niacin/cpp/vm_types.h @@ -0,0 +1,120 @@ +#ifndef VM_TYPES_H +#define VM_TYPES_H + +#include +#include +#include + +// Type codes +typedef enum { + TYPE_I8 = 0x01, + TYPE_U8 = 0x02, + TYPE_I16 = 0x03, + TYPE_U16 = 0x04, + TYPE_I32 = 0x05, + TYPE_U32 = 0x06, + TYPE_F32 = 0x07, + TYPE_BOOL = 0x08, + TYPE_CHAR = 0x09, + TYPE_STR = 0x0A +} type_code_t; + +// Opcodes +typedef enum { + OP_PUSH_CONST = 0x01, + OP_PUSH_INT = 0x02, + OP_PUSH_FLOAT = 0x03, + OP_PUSH_STR = 0x04, + OP_LOAD_LOCAL = 0x10, + OP_STORE_LOCAL = 0x11, + OP_ADD = 0x20, + OP_SUB = 0x21, + OP_MUL = 0x22, + OP_DIV = 0x23, + OP_MOD = 0x24, + OP_CMP_EQ = 0x40, + OP_CMP_NEQ = 0x41, + OP_CMP_LT = 0x42, + OP_CMP_GT = 0x43, + OP_CMP_LE = 0x44, + OP_CMP_GE = 0x45, + OP_JMP = 0x50, + OP_JMP_IF = 0x51, + OP_JMP_IF_NOT = 0x52, + OP_CALL = 0x60, + OP_RET = 0x61, + OP_DUP = 0x80, + OP_POP = 0x81, + OP_PRINT = 0x90, + OP_HALT = 0xA0 +} opcode_t; + +// Value union with type safety +typedef union { + int8_t i8; + uint8_t u8; + int16_t i16; + uint16_t u16; + int32_t i32; + uint32_t u32; + float f32; + bool boolean; + char character; + char* string; +} value_data_t; + +typedef struct { + type_code_t type; + value_data_t data; +} value_t; + +// Function definition +typedef struct { + uint32_t name_index; + uint8_t arg_count; + uint8_t local_count; + uint16_t reserved; + uint32_t code_size; + uint32_t code_offset; +} function_t; + +// Call frame +typedef struct frame { + struct frame* prev; + uint32_t return_ip; + value_t* locals; + value_t* stack; + size_t stack_size; + size_t stack_capacity; + size_t locals_count; +} frame_t; + +// Virtual machine state +typedef struct { + uint8_t* bytecode; + size_t bytecode_size; + uint32_t ip; + + value_t* constants; + size_t constants_count; + + function_t* functions; + size_t functions_count; + + frame_t* current_frame; + bool halted; + + // Security settings + size_t max_stack_size; + size_t max_call_depth; + size_t current_call_depth; +} vm_t; + +// Function declarations +vm_t* vm_create(uint8_t* bytecode, size_t size); +bool vm_validate_bytecode(vm_t* vm); +bool vm_execute_instruction(vm_t* vm); +void vm_execute(vm_t* vm); +void vm_destroy(vm_t* vm); + +#endif \ No newline at end of file diff --git a/niacin/sample.popasm b/niacin/sample.popasm new file mode 100644 index 0000000..c26d935 --- /dev/null +++ b/niacin/sample.popasm @@ -0,0 +1,16 @@ +; POP Class File Version 1.0 +; Magic: b'POPC' + +; Constant Pool +; Count: 0 + +; Function Table +; Count: 1 +; func[0]: func_0, args=0, locals=1, code_size=15, offset=0x00000024 + +; Function 0 +0x00000024: PUSH_INT i32 12 +0x0000002a: STORE_LOCAL local[0] +0x0000002d: LOAD_LOCAL local[0] +0x00000030: PRINT +0x00000031: RET 0 diff --git a/niacin/sample.popclass b/niacin/sample.popclass new file mode 100644 index 0000000000000000000000000000000000000000..a98c0240b5e7acf6fa552aaedeb5fa19a2fb774d GIT binary patch literal 51 ncmWIW4{&B=KmZW?KM*hg8T>%D3J^0XfOvuo3<3-c6A~E!y1@pT literal 0 HcmV?d00001 diff --git a/niacin/sample.src b/niacin/sample.src new file mode 100644 index 0000000..dbc2bf3 --- /dev/null +++ b/niacin/sample.src @@ -0,0 +1,4 @@ +fun main() { + u8 testint = 255; + print(testint); +} \ No newline at end of file diff --git a/simulations/donut.c/donutdbg.debugtarget b/simulations/donut.c/donutdbg.debugtarget new file mode 100644 index 0000000..dcfcb37 --- /dev/null +++ b/simulations/donut.c/donutdbg.debugtarget @@ -0,0 +1,13 @@ + + + + + + + + + + \ No newline at end of file