3dSynth

Tutorials / Custom G-code

Open in the interactive tutorials reader

---
title: Tutorial 29 Converting DSL → Pure G-code
description: Building a compiler for your language.
---

Tutorial 29 Converting DSL → Pure G-code

1. Lesson Header

2. Concept Introduction

The Compiler Pipeline.
In Lesson 28, we used Python functions. Now we want to read a text file written in our own language and convert it.
Input: my_shape.txt

START
SPEED 3000
SQUARE 10
CIRCLE 20
END

Output: my_shape.gcode

G28
G1 F3000
G1 X10...

This requires Parsing (reading text) and Code Generation (writing G-code).

3. Machine State Explanation

The Tokenizer.
We need to split the input string into "Tokens".
"SQUARE 10" -> ["SQUARE", "10"].
Then we switch based on the first token (Command) and use the second (Argument).

4. Command Breakdown

5. Minimal Working Example

The Interpreter Loop.

lines = ["SPEED 1000", "MOVE 10"]
for line in lines:
    parts = line.split()
    cmd = parts[0]
    arg = int(parts[1])
    
    if cmd == "SPEED":
        print(f"G1 F{arg}")
    elif cmd == "MOVE":
        print(f"G1 X{arg}")

6. Visual Representation

Interactive preview is available in the interactive reader.

7. Build Exercise

Task: Build a Parser for ShapeLang.
Supported Commands:
- HOME: G28.
- SPEED [F]: Set Feedrate.
- LINE [X] [Y]: Draw line to X,Y.
- RECT [W] [H]: Draw rectangle of size WxH at current position.

Implementation:
Read input.txt.
Parse line by line.
Call the GcodeTurtle methods from Lesson 28 to execute logic.

8. Deep Insight Section

Error Handling.
What if the user writes RECT (no args)?
Your parser must check len(parts) before accessing parts[1].
Good compilers give helpful error messages:
"Error on line 5: RECT command requires 2 arguments."

9. Common Failure Modes

  1. Type Conversion: Reading "10.5" as int() crashes. Use float().
  2. Case Sensitivity: rect vs RECT. Standardize with .upper().

10. Real-World Application

G-code Flavors.
Slicers often have "Post-Processing Scripts" that parse the G-code they just generated to modify it (e.g., "Find all M104 and add 5 degrees").
This is essentially parsing a DSL (G-code itself) and transpiling it.

11. Final Clean Version

The Compiler Script:

import sys

# Reuse our Turtle logic (simplified)
class Compiler:
    def __init__(self):
        self.output = []
        self.x = 0
        self.y = 0

    def emit(self, gcode):
        self.output.append(gcode)

    def parse(self, filename):
        with open(filename, 'r') as f:
            lines = f.readlines()
            
        for i, line in enumerate(lines):
            line = line.strip()
            if not line or line.startswith("#"): continue
            
            parts = line.split()
            cmd = parts[0].upper()
            
            try:
                if cmd == "HOME":
                    self.emit("G28")
                    self.x, self.y = 0, 0
                    
                elif cmd == "SPEED":
                    speed = int(parts[1])
                    self.emit(f"G1 F{speed}")
                    
                elif cmd == "LINE":
                    target_x = float(parts[1])
                    target_y = float(parts[2])
                    self.emit(f"G1 X{target_x} Y{target_y} E...") # Add extrusion logic
                    self.x, self.y = target_x, target_y
                    
                elif cmd == "RECT":
                    w = float(parts[1])
                    h = float(parts[2])
                    # Draw rect relative to current
                    self.emit(f"G1 X{self.x + w} Y{self.y}")
                    self.emit(f"G1 X{self.x + w} Y{self.y + h}")
                    self.emit(f"G1 X{self.x} Y{self.y + h}")
                    self.emit(f"G1 X{self.x} Y{self.y}") # Close loop
                    
                else:
                    print(f"Unknown command line {i}: {cmd}")
                    
            except IndexError:
                print(f"Syntax Error line {i}: Missing arguments")
            except ValueError:
                print(f"Syntax Error line {i}: Invalid number format")

    def save(self, output_file):
        with open(output_file, 'w') as f:
            f.write("G21\nG90\n")
            f.write("\n".join(self.output))
            
# Usage
c = Compiler()
c.parse("input.txt")
c.save("output.gcode")

12. Stretch Challenge

Challenge: Add Variables to your language.
SET width 10
RECT $width 20
You need a dictionary variables = {}.
When parsing args, check if it starts with $. If so, look it up.