JSFiddle - React, Tailwind, and code Playground

by Ben Gillbanks

HTML

<script src="https://unpkg.com/ohm-js@17/dist/ohm.min.js"></script>

JavaScript

// Create the grammar using Ohm
const grammar = ohm.grammar(`
  LogoScript {
    program = command*
    command = moveCommand | penCommand
    moveCommand = ("FORWARD" | "BACKWARD") value
    penCommand = ("PENUP" | "PENDOWN")
    value = number
    number = "-"? digit+
    space := " " | "\\t" | "\\n"
  }
`);

// Create the turtle object to store the state
const turtle = {
  x: 0,
  y: 0,
  penDown: true,
  direction: 0, // Angle in degrees, 0 points to the right
  output: [],
};

// Helper function to move the turtle
function move(distance) {
  const radians = (turtle.direction * Math.PI) / 180;
  const dx = distance * Math.cos(radians);
  const dy = distance * Math.sin(radians);
  turtle.x += dx;
  turtle.y += dy;
  if (turtle.penDown) {
    turtle.output.push(`DRAW ${dx} ${dy}`);
  }
}

// Helper function to turn the turtle
function turn(angle) {
  turtle.direction += angle;
  turtle.output.push(`TURN ${angle}`);
}

// Helper function to put the pen up
function penUp() {
  turtle.penDown = false;
  turtle.output.push('PENUP');
}

// Helper function to put the pen down
function penDown() {
  turtle.penDown = true;
  turtle.output.push('PENDOWN');
}

// Create the interpreter
const semantics = grammar.createSemantics().addOperation('run', {
  program(commands) {
    return commands.run();
  },
  moveCommand(direction, value) {
    const distance = Number(value.sourceString);
    if (direction.sourceString === 'FORWARD') {
      move(distance);
    } else {
      move(-distance);
    }
  },
  penCommand(cmd) {
    if (cmd.sourceString === 'PENUP') {
      penUp();
    } else {
      penDown();
    }
  },
});

// Define a helper function to execute Logo-like code
function runLogoCode(code) {
  const match = grammar.match(code);
  if (match.succeeded()) {
    turtle.output = [];
    semantics(match).run();
    return turtle.output;
  } else {
    throw new Error('Syntax Error: ' + match.message);
  }
}

// Example usage:
const logoCode = `
  FORWARD 50
  TURN 90
 ...