Skip to content

Interfaz para el desarrollo

Rodrigo Alfonso edited this page Dec 19, 2017 · 4 revisions

Escribí una interfaz mínima de lo que necesitamos, que incluye cómo usaríamos el interactive y los snapshots. Acá va:

Al hacer require("gobstones-interpreter")...

index.js podría exportar un objeto con las siguientes funciones:

{
  config: {
    setLanguage,
    setInfiniteLoopTimeout,
    setXGobstonesEnabled
  },
  gbb: {
    read,
    write
  },
  getAst,
  parse
}

Detalle de qué sería cada una:

Clave Tipo retorno Descripción
setLanguage(code) - Setea el código de lenguaje code (por ej. "es" o "en"). Si no se invoca, todo debería usar "es" como default.
setInfiniteLoopTimeout(milliseconds) - Setea el tiempo de espera antes de que un loop infinito tire un error en tiempo de ejecución. Default: 3000.
setXGobstonesEnabled(isEnabled) - Setea un flag que activa o desactiva XGobstones. Default: false.
read(gbb) Board (*1) Recibe el gbb como string y retorna su tablero equivalente.
write(board) String Recibe el board (mismo formato que (*1)) y retorna un string con el GBB.
getAst(sourceCode) { tag: Symbol("Name"), contents: [...] } Recibe el sourceCode como string y retorna el AST.
parse(sourceCode) ParseResult (*2) Recibe el sourceCode como string y retorna un objeto con el resultado. Si falla, tira una excepción ParseError (*3). Inicialmente lo vamos a usar así y por eso preferí dejarlo simple, más adelante podemos extender esto para que reciba más sourceCodes (con argumentos variables) y que pueda recibir o strings u objetos { sourceCode, fileName } para incluir el nombre de archivo.

(*1) Board

{
  width: 2,
  height: 2,
  head: { x: 1, y: 2 },
  table: [ // mismo formato que en gs-weblang-cli
    [ { red: 1, blue: 4, green: 3, black: 2 }, {} ], // celdas con posiciones (0; 1) y (1; 1)
    [ { blue: 9 }, { red: 1, black: 8 } ] // celdas con posiciones (0; 0) y (1; 0)
  ]
}

(en los métodos que requieran un Board yo voy a mandar un objeto plano con esas keys)

(*2) ParseResult

{
  program: {
    alias: "interactiveProgram" // o "program" si no es interactivo,
    interpret: function(board) { ... } // recibe un Board (*1) y retorna un ExecutionResult (*4) o tira una excepción ExecutionError (*5)
  }, // o null si no hay programa, (*6)
  declarations: [
    // información de funciones y procedimientos  (*6)
  ]
}

(*3) ParseError

{
  message: "Token inesperado 'asdasf'", // el mensaje de error localizado al idioma actual
  reason: {
    code: "unexpected_token", // un código de error único en inglés
    detail: ["asdasf"] // (opcional) un objeto cualquiera que dé más detalles sobre el error
  },
  on: {
    range: {
      start: {
        row: 0,
        column: 1
      },
      end: {
        row: 0,
        column: 10
      }
    },
    region: "ASD" // idem como en las Snapshots (*7)
  }
}

(*4) ExecutionResult

Esto pueden ser dos cosas: NormalExecutionResult (*4.1) o InteractiveExecutionResult (*4.2)

(*4.1) NormalExecutionResult

{
  finalBoard: ..., // un Board (*1) normalito con el último resultado
                   // (esta key existe solo por comodidad), debería ser igual al (último snapshot).board
  snapshots: [
    ... // un array de varios Snapshot (*7)
  ],
  returnValue: { // valor de retorno del programa
    type: "Color", // o Number, o Direction, o Boolean o los que se me estén escapando
    value: "Rojo", // o 2 (como Number, sorry D=), o "Norte", o true (como booleano)
  } // o null si no retornó nada
}

(*4.2) InteractiveExecutionResult

{
  keys: ["K_ARROW_LEFT", "K_ENTER"], // las teclas usadas por el programa
  timeout: 400, // la cantidad de ms del timeout, o null si no hay timeout
  onInit: function() { },
  onKey: function(keyCode) { }, // recibe el keyCode como string, ej: "K_ENTER"
  onTimeout: function() { }
  // estas tres funciones...
  // devuelven un `Board` (*1) o tiran excepción ExecutionError (*5)
  // van a ser invocadas al principio, cuando el usuario presione una tecla, y en cada timeout
  // ... pero a diferencia de interpret(...) no reciben el tablero (este debe ser guardado en el estado interno del InteractiveExecutionResult)
}

(*5) ExecutionError

Misma idea que ParserError (*3) pero con snapshots...

{
  message: "El mensaje de error en tiempo de ejecución",
  reason: {
    code: "boom_called",
    detail: { message: "El mensaje de error en tiempo de ejecución" }
  },
  on: {
    range: {
      start: {
        row: 0,
        column: 1
      },
      end: {
        row: 0,
        column: 10
      }
    },
    region: "ASD" // idem como en las Snapshots (*7)
  },
  snapshots: []
}

(*6) Declaraciones

Es importante que .parse no rompa si no hay definición de programa, porque actualmente se usa para ver si cosas aisladas (como la biblioteca) tienen errores de compilación o no. Es decir, hacer: gobstonesInterpreter.parse("procedure Hola() { Poner(Rojo) } \n function devolverDos() { return(2) }") debería retornar:

{
  program: null,
  declarations: [
    {
      alias: "procedureDeclaration",
      name: "Hola"
    },
    {
      alias: "functionDeclaration",
      name: "devolverDos"
    }
  ]
}

(*7) Snapshots

Cada snapshot es un objeto:

{
  contextNames: [ 'program', 'Hey2-107430', 'Hey1-104697' ],
  board: ..., // el Board (*1) correspondiente a este snapshot
  region: "ASD", // esto lo agregué para que cada snapshot diga en qué región pasó cada snapshot (o null si no había ninguna región) para que podamos highlightear bloques
}

La idea es que:

  • Cada acción que modifica en algo al tablero final crea un snapshot luego de hacerla. Esto no aplica para funciones ya que su contexto es temporal.
  • El snapshot tiene el contextNames que representa el stack de llamadas hasta ese punto. A los procedimientos se les agrega un guión "-" y un id pseudo aleatorio, necesario para que desde la GUI se puedan ignorar algunos snapshots y ande bien con los repetidos.
  • El array de snapshots siempre contiene el tablero inicial (el que se le pasó a .interpret(...) al principio y el tablero final al final.

Ejemplo:

Al ejecutar procedure Hey1() { Mover(Norte) } \n procedure Hey2() { Hey1() \n Mover(Este) } \n program { Poner(Rojo) \n Hey2() \n Poner(Azul) } esto debería generar los siguientes snapshots:

[
  {
    contextNames: [],
    board: ... // el Board (*1) correspondiente al tablero inicial recibido
  },
  {
    contextNames: [ 'program' ],
    board: ... // el Board (*1) luego del primer Poner(Rojo)
  },
  {
    contextNames: [ 'program', 'Hey2-107430', 'Hey1-104697' ],
    board: ... // el Board (*1) luego del Mover(Norte)
  },
  {
    contextNames: [ 'program', 'Hey2-107430' ],
    board: .... // el Board (*1) luego del Mover(Este)
  },
  {
    contextNames: [ 'program' ],
    board: ... // el Board (*1) luego del Poner(Azul)
  }
]

-> Donde corresponda podrías agregar métodos para interactuar con los pragmas, como no vi todavía aún cómo está hecho eso te lo dejo a vos.

Fijate si tiene sentido y cualquier cosa me decís @foones 😃