/src/script/parser.ne
@preprocessor typescript
@{%
import * as moo from 'moo';
import { SayInstruction } from './say';
import { MenuInstruction } from './menu';
import { Label } from './label';
import { GotoInstruction } from './goto';
import { FinishInstruction } from './finish';
import { ClearScreenInstruction } from './clear';
import { SleepInstruction } from './sleep';
import { PauseInstruction } from './pause';
import { MoveInstruction } from './move';

import { Direction, directionFromToken } from '../direction';

const lexer = moo.compile({
  ws:      { match: /[ \t\r\n]+/, lineBreaks: true },
  number:  /[0-9]+(?:\.[0-9]+)?/,
  keyword: ['say', 'menu', 'item', 'end', 'exit', 'goto', 'if', 'finish', 'clear', 'buy', 'transact', 'setflag', 'move'],
  direction: /\+(?:n|e|s|w|north|east|south|west)/,
  label:  /[a-zA-Z_][a-zA-Z0-9_]*:/,
  word:   /[a-zA-Z_][a-zA-Z0-9_]*/,
  dqstring: { match: /"(?:\\["\\]|[^\n"\\])*"/, value: s => s.slice(1, -1) },
  sqstring: { match: /'(?:\\['\\]|[^\n'\\])*'/, value: s => s.slice(1, -1) },
  mlstring: { match: /\[\[\[[^]*?\]\]\]/, lineBreaks: true, value: s => s.slice(3, -3).trim() },
  bang:      '!',
  flag_mark: '$',
  obj_mark:  '@',
  comma:     ',',
});
%}

@lexer lexer

program -> __:? statement:* {% data => data[1] || data[0] %}

statement -> "say" __ string __             {% data => new SayInstruction(data[2]) %}
statement -> "menu" __ menuitem:* "end" __  {% data => new MenuInstruction(data[2]) %}
statement -> %label __                      {% data => new Label(data[0].value.slice(0, -1)) %}
statement -> "goto" __ %word __             {% data => new GotoInstruction(data[2].value) %}
statement -> "finish" __                    {% data => new FinishInstruction() %}
statement -> "clear" __                     {% data => new ClearScreenInstruction() %}
statement -> "sleep" __ number __           {% data => new SleepInstruction(data[2]) %}
statement -> "pause" __ string __           {% data => new PauseInstruction(data[2]) %}
statement -> "move" __ direction __         {% data => new MoveInstruction(data[2]) %}
statement -> "move" __ number _ %comma _ number __ {% data => new MoveInstruction(data[2], data[6]) %}

menuitem -> if:? "item" __ string __ program "end" __
menuitem -> if:? "buy" __ obj_identifier __ number __
menuitem -> if:? "exit" __ string __

if -> "if" __ %bang:? flag_identifier __
if -> "if" __ %bang:? obj_identifier __

flag_identifier -> %flag_mark %word
obj_identifier -> %obj_mark %word

number -> %number {% data => parseFloat(data[0]) %}

string -> %dqstring {% data => data[0].value %}
string -> %sqstring {% data => data[0].value %}
string -> %mlstring {% data => data[0].value %}

direction -> %direction {% data => directionFromToken(data[0]) %}

__ -> %ws {% _ => null %}
_ -> null | %ws {% _ => null %}