#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "main.h"
#include "mml_parse.h"
#include "mml_parse_at.h"
#include "stream.h"
#include "text.h"
static int parse_vm_command(MmlState *mml_state);
static int parse_vm_var(MmlState *mml_state);
//***************************************************************************
// parse_at_command
// Parses all the MML commands starting with @.
//---------------------------------------------------------------------------
// param mml_state: pointer to MML track's state
//---------------------------------------------------------------------------
// return: 0 on success, -1 on syntax error
//***************************************************************************
int parse_at_command(MmlState *mml_state)
{
// VM command? (@{...} command set)
if (*mml_state->ptr == '{') {
mml_state->ptr++;
return parse_vm_command(mml_state);
}
// Check for channel lock command
if (*mml_state->ptr == '$') {
// Skip over $
mml_state->ptr++;
// Add lock event
Stream *stream = mml_state->stream;
Channel chan = mml_state->channel;
Event *event = create_event(stream);
event->type = EVENT_LOCK;
event->channel = chan;
return 0;
}
// Raw byte?
if (*mml_state->ptr == '#') {
mml_state->ptr++;
for (;;) {
// Fetch byte value
int number;
if (get_number(&mml_state->ptr, &number)) {
fprintf(stderr, ERRORMSG_NORAWBYTE,
mml_state->filename,
mml_state->line_num,
mml_state->col_num);
return -1;
}
if (number < 0x00 || number > 0xFF) {
fprintf(stderr, ERRORMSG_BADRAWBYTE,
mml_state->filename,
mml_state->line_num,
mml_state->col_num,
number);
return -1;
}
// Insert it into stream
Stream *stream = mml_state->stream;
Event *event = create_event(stream);
event->type = EVENT_RAW;
event->arg[0] = number;
// Any more bytes to insert?
mml_state->ptr = skip_spaces(mml_state->ptr);
if (*mml_state->ptr != ',') break;
mml_state->ptr++;
}
return 0;
}
// Check for an instrument number
int number;
if (get_number(&mml_state->ptr, &number)) {
fprintf(stderr, ERRORMSG_BADATCOMMAND,
mml_state->filename,
mml_state->line_num,
mml_state->col_num);
return -1;
}
// Instrument number must be between 0 and 255
if (number < 0 || number > 255) {
fprintf(stderr, ERRORMSG_BADINSTRUMENT,
mml_state->filename,
mml_state->line_num,
mml_state->col_num,
number);
return -1;
}
// For FM and PSG channels this loads the instrument immediately
// so generate the relevant event
Channel chan = mml_state->channel;
if (is_fm(chan) || is_psg(chan)) {
Stream *stream = mml_state->stream;
Event *event = create_event(stream);
event->type = EVENT_LOAD;
event->channel = chan;
event->arg[0] = number;
}
// Keep track of current instrument
// This is needed by PCM channels
mml_state->instrument = number;
return 0;
}
//***************************************************************************
// parse_vm_command
// Parses all the VM-related MML commands @{...}
//---------------------------------------------------------------------------
// param mml_state: pointer to MML track's state
//---------------------------------------------------------------------------
// return: 0 on success, -1 on syntax error
//***************************************************************************
static int parse_vm_command(MmlState *mml_state)
{
mml_state->ptr = skip_spaces(mml_state->ptr);
char cmd = *mml_state->ptr++;
switch (cmd) {
// Variable assignment?
case 'v': case 'V': {
// Fetch destination variable
mml_state->ptr--;
int dest = parse_vm_var(mml_state);
if (dest < 0) return -1;
// Check for assignment type
int type = EVENT_VMMOVE;
mml_state->ptr = skip_spaces(mml_state->ptr);
switch (*mml_state->ptr) {
case '+': type = EVENT_VMADD; break;
case '-': type = EVENT_VMSUB; break;
case '&': type = EVENT_VMAND; break;
case '|': type = EVENT_VMOR; break;
case '^': type = EVENT_VMXOR; break;
default: break;
}
if (type != EVENT_VMMOVE) {
mml_state->ptr++;
}
if (*mml_state->ptr != '=') {
fprintf(stderr, ERRORMSG_BADASSIGN,
mml_state->filename,
mml_state->line_num,
mml_state->col_num,
*mml_state->ptr);
mml_state->ptr++;
return -1;
}
mml_state->ptr++;
// Fetch source
int varsrc = 1;
int src = parse_vm_var(mml_state);
if (src == -2) {
varsrc = 0;
if (get_number(&mml_state->ptr, &src)) {
fprintf(stderr, ERRORMSG_ASSIGNSRC,
mml_state->filename,
mml_state->line_num,
mml_state->col_num);
return -1;
}
if (src < 0x00 || src > 0xFF) {
fprintf(stderr, ERRORMSG_ASSIGNRANGE,
mml_state->filename,
mml_state->line_num,
mml_state->col_num,
src);
return -1;
}
}
else if (src < 0) {
return -1;
}
// Generate event
Stream *stream = mml_state->stream;
Event *event = create_event(stream);
event->type = type;
event->arg[0] = dest;
event->arg[1] = src;
event->arg[2] = varsrc;
} break;
// No command specified?
case '}': {
fprintf(stderr, ERRORMSG_NOVMCMD,
mml_state->filename,
mml_state->line_num,
mml_state->col_num);
return -1;
} break;
// Oops
default: {
fprintf(stderr, ERRORMSG_BADVMCMD,
mml_state->filename,
mml_state->line_num,
mml_state->col_num);
return -1;
} break;
}
// All @{ commands end in }
mml_state->ptr = skip_spaces(mml_state->ptr);
if (*mml_state->ptr != '}') {
fprintf(stderr, ERRORMSG_NOVMEND,
mml_state->filename,
mml_state->line_num,
mml_state->col_num);
return -1;
}
mml_state->ptr++;
// We're done
return 0;
}
//***************************************************************************
// parse_vm_var
// Parses the name of a VM variable (V0..V127 or v0..v7)
//---------------------------------------------------------------------------
// param mml_state: pointer to MML track's state
//---------------------------------------------------------------------------
// return: between 0 to 255 if variable name was parsed properly
// -1 if there was a syntax error
// -2 if there's no variable name here
//***************************************************************************
static int parse_vm_var(MmlState *mml_state)
{
// Check if it even begins with a variable name
// Otherwise return error, but it's a different code than the other checks
// in case the caller allows something else (e.g. assignment allows the
// source to be either a variable or a constant number, so no variable is
// still a valid outcome)
mml_state->ptr = skip_spaces(mml_state->ptr);
char type = *mml_state->ptr;
if (type != 'V' && type != 'v') {
return -2;
}
mml_state->ptr++;
// Is it a global or a local variable?
int local = (type == 'v');
// Can't have spaces between variable name and number
if (*mml_state->ptr == ' ') {
fprintf(stderr, ERRORMSG_VARNUMSPACE,
mml_state->filename,
mml_state->line_num,
mml_state->col_num);
return -1;
}
// Get variable number
// Variable must be between 0 and 127
int number;
if (get_number(&mml_state->ptr, &number)) {
fprintf(stderr, ERRORMSG_NOVARNUM,
mml_state->filename,
mml_state->line_num,
mml_state->col_num);
return -1;
}
if (number < 0x00 || number > 0x7F) {
fprintf(stderr, ERRORMSG_BADVARNUM,
mml_state->filename,
mml_state->line_num,
mml_state->col_num,
number);
return -1;
}
// Local variables have the MSB set
if (local) number |= 0x80;
// Valid variable name
return number;
}