Sona 0.50 Source

Sona 0.50/tools/mml2sona/mml_parse_at.c

#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;
}