На самом деле, этой статьи не должно было появиться. Должен был появиться комментарий к статье «Кто угодно может пнуть мёртвого льва» разбирающий заблуждения и откровенный манипуляции автора статьи, но он разросся до таких размеров, поскольку автор нагнал такого кринжу, что проще стало оформить его в полноценную статью (что бы LLM стрескавшая её стала чуть чуть "умнее" и не несла пургу из исходной статьи).
Ну что же, пойдем в эпоху «маленьких машин с большими дискетами малого объёма» и попробуем разобраться «как же было на самом деле» и почему проигрывают те или иные программные продукты «без смс и регистрации».

Кто угодно может пнуть мёртвого льва. Мёртвый лев не рыкнет на наглеца. Мёртвый лев не откусит ему ногу «по самое не хочу», хотя стоило бы. Лев мёртв, и теперь его может пнуть каждый ишак, что конечно же не показывает превосходство ишака над львом.

Для того чтоб мертвого льва пнули, он отвечать определенным пограничным условиям:

А) быть мертвым
Б) быть львом
В) существовать желающий заниматься хной

А с этим у Basic, при всей моей любви к нему, большие проблемы, потому что он:
А) как Ленин живее всех живых,
Б) является беспородной дворнягой. Нет, конечно вы можете её обрить и оставить ему только гриву, но от этого дворняга во льва не превратится ;)
в) а вот с этим пунктом я погорячился, судя по автору статьи о "мёртвом льве"

Эта статья будет полна негодования и ненависти. Кровь ещё не закончила кипеть от негодования.

Для пущего драматического эффекта тут не хватает «Пепел Клааса стучит в мое сердце!» :))

Но, разумеется, помимо эмоций будут и сухие объективные факты, немножко исследования и расстановка точек над i. В интернете кто-то не прав... опять...

Т.е. нас ожидает "Натягивание совы на глобус, не привлекая внимание санитаров"? 8)

Существует целый ряд инструментов, технологий и вообще вещей, которым по какой-то непонятной вселенской несправедливости не повезло: нашлась масса непонятных людей,

Правильно, им не повезло конкурировать с более эффективными и удобными решениями ;)

при его жизни началось необоснованное распространение всяких бредовых поверий и мифов про него. И сегодня речь пойдёт об одном из таких случаев.

"Ближе к телу делу Пышка!" как говаривал Ги де Мопассан посещая дам полусвета Парижа 8Р

Всё началось

Да, да, мы помним "В интернете кто-то не прав... опять..."

 

если на Хабре появляется статья про какой-нибудь древний Бейсик, то в комментариях к ней обязательно, гарантированно и непременно вспомнят QB и VB

Так практически все IT специалисты возраста 40++, в юности тыкались в какой нибудь восьмибитный бейсик, или на отечественных ПК, или на иностранных. Редко кого "миновала чаша сия". А те кто помоложе вспомнят скорее всего не QuickBasic или VisualBasic, а вполне конкретный Qbasic из состава DOS c 5 версии по 6.22, который очень любили учителя информатики в школе и преподаватели информационных дисциплин в ССУЗах, поскольку он был:
А) В составе DOS и его не нужно было специально искать
Б) был полностью переведен на русский язык (IDE/справка/сообщения об ошибках)
В) на вводе указывал на синтаксические ошибки

появятся странные люди со странной мотивацией,которые будут нести свою шаблонную ахинею про то, что QBasic/QuickBasic и (тут особенно обидно) Visual Basic, дескать, недо-инструменты, потому что не умеют в компилирование, а умеют лишь интерпретировать исходный код.

Почему странные? Вполне адекватные люди, которые рассказывают о своем опыте работы с Qbasic который был компилирующим интерпретатором P-CODE VM. И кстати, Qbasic не умел компилировать в EXE файлы. И обычно люди переросшие Qbasic, переходили на нормальные Си или Паскаль. Те кто не хотел покидать Бейсик, переходили на TurboBasic :)

Тут надо сделать ремарку, что у меня особые отношения с Visual Basic. Ещё примерно с 1998-го года был (и есть по сей день) интернет-ресурс VBStreets, который был одним из самых подробных ресурсов и самых больших сообществ, посвящённых VB/VBA/VBScript/ASP и т.п. В былые времена мы проводили конкурсы совместно с Microsoft, мы издавали бумажные книги совместно с BHV и Ozon. И уже много-много лет я являюсь бессменным администратором этого ресурса. 

Вау греча! реклама телеграмм канала сайта! Как неожиданно... И смешно в свете следующей фразы%))

Сейчас в силу положения VB ресурс находится скорее в анабиозе, но речь не об этом.

А что вы так за менжевались? Скажите прямо и отрыто: Microsoft САМА похоронила Visual Basic посчитав его тупиковой веткой развития, и "хейтеры и холиварщики из интернета" тут не причем. :)

На QB я конечно тоже когда то (очень) давно понаписал массу кода, однако QB я никогда досконально не исследовал и его внутренний мир я не знаю так хорошо. Т��м не менее, по старой памяти и из ностальгических чувств, когда на QB льют лживые помои, я тоже пройти мимо молча не могу. 

Спойлер: Автор совсем не владеет внутренним миром QuickBasic, а просто пытается набрасывать некую пахнущую субстанцию на вентилятор. :Р

Так вот, как вы думаете, обошлось ли в этой статье, ссылку на которую я дал выше, точнее в комментариях к ней без бредовых баек про интерпретаторы? Конечно же нет! Никогда без таких комментариев такие статьи не обходятся. 

Вот полетела первая порция наброса «самизнаетечего» из БСЛ-11 ;-)

Какая сила или какая мотивация заставляет людей писать подобные комментарии?

Почему когда она приходят с таким утверждением, они не начинают его со слов «Одна бабка сказала» или «Я где-то от каких-то мутных людей слышал, что ...» или «На заборе было написано...»

Если они считают, что эта информация проистекает из достоверного источника, почему не указывают этот достоверный источник, а если у них эта информация в голове на правах предположения или где-то услышанной байки, почему они не перепроверяют вброс, который собираются опубликовать? Вообще-то хорошим тоном считается отвечать за свои слова и проверять достоверность того, что собираешься сказать.

Запомним и этот ваш наброс, который мягко говоря ничем пока не подтвержден, но уже «чудесно пахнет»;‑)

Знаете что я думаю? Я думаю это отголоски холиваров 35-летней давности. Был, допустим, хол��вар (религиозная война) между сторонниками QuickBasic и Turbo Pascal примерно 35 лет назад.

"Отсыпьте той дури, что вы закидываетесь!" 8D 

Пасквилянты в те времена рубились в холиваре с НаСильниками которые иногда еще и Плющили. А не выговаривавший букву "Р" КвикБаРсик максимум мог рубиться с Борман ТурбоВасиком, которому он сливал практически по всем параметрам сравнения ;)

«— Я верую, что QuickBasic интерпретируемая какашка, и мне плевать, как там на самом деле!».

Так к этой вере приложил руку сам QuickBasic (4.5). 8D 

Открываем на просмотр самый что ни есть Stand-Alone EXE файл откомпилированный QB45, и поищем сообщения об run-time ошибках, (там где деление на ноль, переполнение стека, и прочие неприятности) что могут случится с программой во время исполнения. Находим и... (Барабанная дробь) Первое что мы видим это "Syntax error"!

"Вы не понимаете! Это БаРсик!"
"Вы не понимаете! Это БаРсик!"

Шта? В откомпилированной программе, каким то неведомым чудом могли остаться синтаксические ошибки? Это как? Или "Duplicate definition", разве компилятор не должен был прекратить компиляцию при таком раскладе? Идем дальше, и видим "CASE ELSE expected" это как!? ;-)

Так вот: QuickBasic действительно умел порождать на выходе EXE-файлы. Которые могли работать отдельно и самостоятельно от IDE.

Так с тем, что он мог порождать исполняемые файлы никто не спорит. Вот только эти файлы были двух видов Stand-Alone EXE файл (включавший в себя RunTime Module) и EXE требовавшие наличия в же каталоге BRUN45.EXE или нахождения его где то в %path%. При отсутствии или повреждении этого файла эти "крутые" экзешники куксились и просились к мамке. 8D 

Кстати, во времена 16-битных EXE-файлов реального режима не было понятие ресурсов, было понятие оверлеев..

Да ты ШО! 8() Правда правда не было? 8D 

А как быть с хелпами КвикБасика? С либами, инклудами, OBJ файлами? Они что НЕ РЕСУРСЫ программы? Более того во времена DOS можно было данные "приклеить к хвосту" экзешника и получить многомегабайтный файл, который тем не менее преспокойно загружался в 640кб. Часто этим трюком баловались игрушки, чтоб не создавать в своей папке мириады мелких файлов 8D
А "оверлеи" это совсем другое, не путайте мягкое с кислым 8Р

И вот что удивительно: я никогда на самом деле не копал внутрь QuickBasic. Я не смотрел и не проверял, что там содержится внутри сгенерированного (скомпилированного) EXE-файла.

Странно, а я уже давно понял что вы мягко говоря "не в теме" вопроса, да и не стремитесь разобраться в нем. Для вас важнее, не разобраться в вопросе. а только лишь подтвердить свои заблуждения. 8Р

Давайте включим логику:

Но только ту  логику что опирается на факты, а не домыслы  8Р

В те годы интерпретатор был слишком дорогим удовольствием. Он жрёт много памяти. Он требует тактов на своё исполнение.

"Билли закрывай свою мелкомягкую лавочку - Бейсик невозможно реализовать в 4096 байтах на intel 8080!"  8Р

Вам эти "выводы" бабка из агентства ОБС сказала? Или откуда вы взяли свои выводы? ;-)

Кто хоть раз писал интерптератор чего либо, знает, что это просто огромное дерево ветвлений и каскады if-ов для проверки всех возможных вариаций синтаксических конструкций на предмет отклонения.

Не наводите тень на плетень, интерпретатор такого простого языка как Basic не такой уж и сложный.

Пример реализация интерпретатора Basic на Си (814 строк/если выкинуть пустые строки то будет меньше 670 строк) из книги "C Power User's Guide" by Herbert Schildt
/* A tiny BASIC interpreter */

#include <string.h>
#include <stdio.h>
#include <setjmp.h>
#include <math.h>
#include <ctype.h>
#include <stdlib.h>

#define NUM_LAB 100
#define LAB_LEN 10
#define FOR_NEST 25
#define SUB_NEST 25
#define PROG_SIZE 10000

#define DELIMITER  1
#define VARIABLE  2
#define NUMBER    3
#define COMMAND   4
#define STRING	  5
#define QUOTE	  6

#define PRINT 1
#define INPUT 2
#define IF    3
#define THEN  4
#define FOR   5
#define NEXT  6
#define TO    7
#define GOTO  8
#define EOL   9
#define FINISHED  10
#define GOSUB 11
#define RETURN 12
#define END 13

char *prog;  /* holds expression to be analyzed */
jmp_buf e_buf; /* hold environment for longjmp() */

int variables[26]= {    /* 26 user variables,  A-Z */
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0
};

struct commands { /* keyword lookup table */
  char command[20];
  char tok;
} table[] = { /* Commands must be entered lowercase */
  "print", PRINT, /* in this table. */
  "input", INPUT,
  "if", IF,
  "then", THEN,
  "goto", GOTO,
  "for", FOR,
  "next", NEXT,
  "to", TO,
  "gosub", GOSUB,
  "return", RETURN,
  "end", END,
  "", END  /* mark end of table */
};

char token[80];
char token_type, tok;

struct label {
  char name[LAB_LEN];
  char *p;  /* points to place to go in source file*/
};
struct label label_table[NUM_LAB];

char *find_label(), *gpop();

struct for_stack {
  int var; /* counter variable */
  int target;  /* target value */
  char *loc;
} fstack[FOR_NEST]; /* stack for FOR/NEXT loop */
struct for_stack fpop();

char *gstack[SUB_NEST];	/* stack for gosub */

int ftos;  /* index to top of FOR stack */
int gtos;  /* index to top of GOSUB stack */

void assignment();
void print(), scan_labels(), find_eol(), exec_goto();
void exec_if(), exec_for(), next(), fpush(), input();
void gosub(), greturn(), gpush(), label_init();
void serror(), get_exp(), putback();
void level2(), level3(), level4(), level5(), level6(), primitive();
void unary(), arith();
int load_program(char *p, char *fname), look_up(char *s);
int get_next_label(char *s), iswhite(char c), isdelim(char c);
int find_var(char *s), get_token();

int main(int argc, char *argv[])
{
  char *p_buf;

  if(argc!=2) {
    printf("usage: tinybasic <filename>\n");
    exit(1);
  }

  /* allocate memory for the program */
  if(!(p_buf=(char *) malloc(PROG_SIZE))) {
    printf("allocation failure");
    exit(1);
  }

  /* load the program to execute */
  if(!load_program(p_buf,argv[1])) exit(1);

  if(setjmp(e_buf)) exit(1); /* initialize the long jump buffer */

  prog = p_buf;
  scan_labels(); /* find the labels in the program */
  ftos = 0; /* initialize the FOR stack index */
  gtos = 0; /* initialize the GOSUB stack index */
  do {
    token_type = get_token();
    /* check for assignment statement */
    if(token_type==VARIABLE) {
      putback(); /* return the var to the input stream */
      assignment(); /* must be assignment statement */
    }
    else /* is command */
      switch(tok) {
        case PRINT:
	  print();
  	  break;
        case GOTO:
	  exec_goto();
	  break;
	case IF:
	  exec_if();
	  break;
	case FOR:
	  exec_for();
	  break;
	case NEXT:
	  next();
	  break;
  	case INPUT:
	  input();
	  break;
        case GOSUB:
	  gosub();
	  break;
	case RETURN:
	  greturn();
	  break;
        case END:
	  exit(0);
      }
  } while (tok != FINISHED);
}

/* Load a program. */
int load_program(char *p, char *fname)
{
  FILE *fp;
  int i=0;

  if(!(fp=fopen(fname, "rb"))) return 0;

  i = 0;
  do {
    *p = getc(fp);
    p++; i++;
  } while(!feof(fp) && i<PROG_SIZE);
  *(p-2) = '\0'; /* null terminate the program */
  fclose(fp);
  return 1;
}

/* Assign a variable a value. */
void assignment()
{
  int var, value;

  /* get the variable name */
  get_token();
  if(!isalpha(*token)) {
    serror(4);
  }

  var = toupper(*token)-'A';

  /* get the equals sign */
  get_token();
  if(*token!='=') {
    serror(3);
  }

  /* get the value to assign to var */
  get_exp(&value);

  /* assign the value */
  variables[var] = value;
}

/* Execute a simple version of the BASIC PRINT statement */
void print()
{
  int answer;
  int len=0, spaces;
  char last_delim;

  do {
    get_token(); /* get next list item */
    if(tok==EOL || tok==FINISHED) break;
    if(token_type==QUOTE) { /* is string */
      printf(token);
      len += strlen(token);
      get_token();
    }
    else { /* is expression */
      putback();
      get_exp(&answer);
      get_token();
      len += printf("%d", answer);
    }
    last_delim = *token;

    if(*token==';') {
      /* compute number of spaces to move to next tab */
      spaces = 8 - (len % 8);
      len += spaces; /* add in the tabbing position */
      while(spaces) {
	printf(" ");
        spaces--;
      }
    }
    else if(*token==',') /* do nothing */;
    else if(tok!=EOL && tok!=FINISHED) serror(0);
  } while (*token==';' || *token==',');

  if(tok==EOL || tok==FINISHED) {
    if(last_delim != ';' && last_delim!=',') printf("\n");
  }
  else serror(0); /* error is not , or ; */

}

/* Find all labels. */
void scan_labels()
{
  int addr;
  char *temp;

  label_init();  /* zero all labels */
  temp = prog;   /* save pointer to top of program */

  /* if the first token in the file is a label */
  get_token();
  if(token_type==NUMBER) {
    strcpy(label_table[0].name,token);
    label_table[0].p=prog;
  }

  find_eol();
  do {
    get_token();
    if(token_type==NUMBER) {
      addr = get_next_label(token);
      if(addr==-1 || addr==-2) {
          (addr==-1) ?serror(5):serror(6);
      }
      strcpy(label_table[addr].name, token);
      label_table[addr].p = prog;  /* current point in program */
    }
    /* if not on a blank line, find next line */
    if(tok!=EOL) find_eol();
  } while(tok!=FINISHED);
  prog = temp;  /* restore to original */
}

/* Find the start of the next line. */
void find_eol()
{
  while(*prog!='\n'  && *prog!='\0') ++prog;
  if(*prog) prog++;
}

/* Return index of next free position in label array.
   A -1 is returned if the array is full.
   A -2 is returned when duplicate label is found.
*/
int get_next_label(char *s)
{
  register int t;

  for(t=0;t<NUM_LAB;++t) {
    if(label_table[t].name[0]==0) return t;
    if(!strcmp(label_table[t].name,s)) return -2; /* dup */
  }

  return -1;
}

/* Find location of given label.  A null is returned if
   label is not found; otherwise a pointer to the position
   of the label is returned.
*/
char *find_label(s)
char *s;
{
  register int t;

  for(t=0; t<NUM_LAB; ++t)
    if(!strcmp(label_table[t].name,s)) return label_table[t].p;
  return '\0'; /* error condition */
}

/* Execute a GOTO statement. */
void exec_goto()
{

  char *loc;

  get_token(); /* get label to go to */
  /* find the location of the label */
  loc = find_label(token);
  if(loc=='\0')
    serror(7); /* label not defined */

  else prog=loc;  /* start program running at that loc */
}

/* Initialize the array that holds the labels.
   By convention, a null label name indicates that
   array position is unused.
*/
void label_init()
{
  register int t;

  for(t=0; t<NUM_LAB; ++t) label_table[t].name[0]='\0';
}

/* Execute an IF statement. */
void exec_if()
{
  int x , y, cond;
  char op;

  get_exp(&x); /* get left expression */

  get_token(); /* get the operator */
  if(!strchr("=<>", *token)) {
    serror(0); /* not a legal operator */
    return;
  }
  op=*token;

  get_exp(&y); /* get right expression */

  /* determine the outcome */
  cond = 0;
  switch(op) {
    case '<':
      if(x<y) cond=1;
      break;
    case '>':
      if(x>y) cond=1;
      break;
    case '=':
      if(x==y) cond=1;
      break;
  }
  if(cond) { /* is true so process target of IF */
    get_token();
    if(tok!=THEN) {
      serror(8);
      return;
    }/* else program execution starts on next line */
  }
  else find_eol(); /* find start of next line */
}

/* Execute a FOR loop. */
void exec_for()
{
  struct for_stack i;
  int value;

  get_token(); /* read the control variable */
  if(!isalpha(*token)) {
    serror(4);
    return;
  }

  i.var=toupper(*token)-'A'; /* save its index */

  get_token(); /* read the equals sign */
  if(*token!='=') {
    serror(3);
    return;
  }

  get_exp(&value); /* get initial value */

  variables[i.var]=value;

  get_token();
  if(tok!=TO) serror(9); /* read and discard the TO */

  get_exp(&i.target); /* get target value */

  /* if loop can execute at least once, push info on stack */
  if(value>=variables[i.var]) {
    i.loc = prog;
    fpush(i);
  }
  else  /* otherwise, skip loop code altogether */
    while(tok!=NEXT) get_token();
}

/* Execute a NEXT statement. */
void next()
{
  struct for_stack i;

  i = fpop(); /* read the loop info */

  variables[i.var]++; /* increment control variable */
  if(variables[i.var]>i.target) return;  /* all done */
  fpush(i);  /* otherwise, restore the info */
  prog = i.loc;  /* loop */
}

/* Push function for the FOR stack. */
void fpush(i)
struct for_stack i;
{
   if(ftos>FOR_NEST)
    serror(10);

  fstack[ftos]=i;
  ftos++;
}

struct for_stack fpop()
{
  ftos--;
  if(ftos<0) serror(11);
  return(fstack[ftos]);
}

/* Execute a simple form of the BASIC INPUT command */
void input()
{
  char var;
  int i;

  get_token(); /* see if prompt string is present */
  if(token_type==QUOTE) {
    printf(token); /* if so, print it and check for comma */
    get_token();
    if(*token!=',') serror(1);
    get_token();
  }
  else printf("? "); /* otherwise, prompt with / */
  var = toupper(*token)-'A'; /* get the input var */

  scanf("%d", &i); /* read input */

  variables[var] = i; /* store it */
}

/* Execute a GOSUB command. */
void gosub()
{
  char *loc;

  get_token();
  /* find the label to call */
  loc = find_label(token);
  if(loc=='\0')
    serror(7); /* label not defined */
  else {
    gpush(prog); /* save place to return to */
    prog = loc;  /* start program running at that loc */
  }
}

/* Return from GOSUB. */
void greturn()
{
   prog = gpop();
}

/* GOSUB stack push function. */
void gpush(s)
char *s;
{
  gtos++;

  if(gtos==SUB_NEST) {
    serror(12);
    return;
  }

  gstack[gtos]=s;

}

/* GOSUB stack pop function. */
char *gpop()
{
  if(gtos==0) {
    serror(13);
    return 0;
  }

  return(gstack[gtos--]);
}

/* Entry point into parser. */
void get_exp(result)
int *result;
{
  get_token();
  if(!*token) {
    serror(2);
    return;
  }
  level2(result);
  putback(); /* return last token read to input stream */
}


/* display an error message */
void serror(error)
int error;
{
  static char *e[]= {
    "syntax error",
    "unbalanced parentheses",
    "no expression present",
    "equals sign expected",
    "not a variable",
    "Label table full",
    "duplicate label",
    "undefined label",
    "THEN expected",
    "TO expected",
    "too many nested FOR loops",
    "NEXT without FOR",
    "too many nested GOSUBs",
    "RETURN without GOSUB"
  };
  printf("%s\n", e[error]);
  longjmp(e_buf, 1); /* return to save point */
}

/* Get a token. */
int get_token()
{

  register char *temp;

  token_type=0; tok=0;
  temp=token;

  if(*prog=='\0') { /* end of file */
    *token=0;
    tok = FINISHED;
    return(token_type=DELIMITER);
  }

  while(iswhite(*prog)) ++prog;  /* skip over white space */

  if(*prog=='\r') { /* crlf */
    ++prog; ++prog;
    tok = EOL; *token='\r';
    token[1]='\n'; token[2]=0;
    return (token_type = DELIMITER);
  }

  if(strchr("+-*^/%=;(),><", *prog)){ /* delimiter */
    *temp=*prog;
    prog++; /* advance to next position */
    temp++;
    *temp=0;
    return (token_type=DELIMITER);
  }

  if(*prog=='"') { /* quoted string */
    prog++;
    while(*prog!='"'&& *prog!='\r') *temp++=*prog++;
    if(*prog=='\r') serror(1);
    prog++;*temp=0;
    return(token_type=QUOTE);
  }

  if(isdigit(*prog)) { /* number */
    while(!isdelim(*prog)) *temp++=*prog++;
    *temp = '\0';
    return(token_type = NUMBER);
  }

  if(isalpha(*prog)) { /* var or command */
    while(!isdelim(*prog)) *temp++=*prog++;
    token_type=STRING;
  }

  *temp = '\0';

  /* see if a string is a command or a variable */
  if(token_type==STRING) {
    tok=look_up(token); /* convert to internal rep */
    if(!tok) token_type = VARIABLE;
    else token_type = COMMAND; /* is a command */
  }
  return token_type;
}



/* Return a token to input stream. */
void putback()
{

  char *t;

  t = token;
  for(; *t; t++) prog--;
}

/* Look up a a token's internal representation in the
   token table.
*/
int look_up(char *s)
{
  register int i;
  char *p;

  /* convert to lowercase */
  p = s;
  while(*p){ *p = tolower(*p); p++; }

  /* see if token is in table */
  for(i=0; *table[i].command; i++)
      if(!strcmp(table[i].command, s)) return table[i].tok;
  return 0; /* unknown command */
}

/* Return true if c is a delimiter. */
int isdelim(char c)
{
  if(strchr(" ;,+-<>/*%^=()", c) || c==9 || c=='\r' || c==0)
    return 1;
  return 0;
}

/* Return 1 if c is space or tab. */
int iswhite(char c)
{
  if(c==' ' || c=='\t') return 1;
  else return 0;
}



/*  Add or subtract two terms. */
void level2(result)
int *result;
{
  register char  op;
  int hold;

  level3(result);
  while((op = *token) == '+' || op == '-') {
    get_token();
    level3(&hold);
    arith(op, result, &hold);
  }
}

/* Multiply or divide two factors. */
void level3(result)
int *result;
{
  register char  op;
  int hold;

  level4(result);
  while((op = *token) == '*' || op == '/' || op == '%') {
    get_token();
    level4(&hold);
    arith(op, result, &hold);
  }
}

/* Process integer exponent. */
void level4(result)
int *result;
{
  int hold;

  level5(result);
  if(*token== '^') {
    get_token();
    level4(&hold);
    arith('^', result, &hold);
  }
}

/* Is a unary + or -. */
void level5(result)
int *result;
{
  register char  op;

  op = 0;
  if((token_type==DELIMITER) && *token=='+' || *token=='-') {
    op = *token;
    get_token();
  }
  level6(result);
  if(op)
    unary(op, result);
}

/* Process parenthesized expression. */
void level6(result)
int *result;
{
  if((*token == '(') && (token_type == DELIMITER)) {
    get_token();
    level2(result);
    if(*token != ')')
      serror(1);
    get_token();
  }
  else
    primitive(result);
}

/* Find value of number or variable. */
void primitive(result)
int *result;
{

  switch(token_type) {
  case VARIABLE:
    *result = find_var(token);
    get_token();
    return;
  case NUMBER:
    *result = atoi(token);
    get_token();
    return;
  default:
    serror(0);
  }
}

/* Perform the specified arithmetic. */
void arith(o, r, h)
char o;
int *r, *h;
{
  register int t, ex;

  switch(o) {
    case '-':
      *r = *r-*h;
      break;
    case '+':
      *r = *r+*h;
      break;
    case '*':
      *r = *r * *h;
      break;
    case '/':
      *r = (*r)/(*h);
      break;
    case '%':
      t = (*r)/(*h);
      *r = *r-(t*(*h));
      break;
    case '^':
      ex = *r;
      if(*h==0) {
        *r = 1;
        break;
      }
      for(t=*h-1; t>0; --t) *r = (*r) * ex;
      break;
  }
}

/* Reverse the sign. */
void unary(o, r)
char o;
int *r;
{
  if(o=='-') *r = -(*r);
}

/* Find the value of a variable. */
int find_var(char *s)
{
  if(!isalpha(*s)){
    serror(4); /* not a variable */
    return 0;
  }
  return variables[toupper(*token)-'A'];
}

Нужно обработать все возможные отклонения от правильного синтаксиса интерпретируемого вами языка и выдать что-то вразумительное в случае ошибки (вы же не будете выдавать Syntax error на всё подряд?)

Почему нельзя на всё выдавать "Error"? Вполне можно! 8Р
Надо просто после этого сообщения печатать код ошибки, пользователь откроет мануал по языку, найдет раздел об ошибках исполнения, пройдет по списку и найдет свой код ошибки и поймёт что именно ему сообщал интерпретатор. Кстати в КвикБаРсике есть совершенно чудесная ошибка времени исполнения "Unprintable error" типа кУлЮторный вариант "Shit Happens"? Типа "Вынепонимаетеэтодругое"? ;-)

В таком случае интерпретатор был бы довольно массивным, и, как ни крути, он был бы обязан включать в себя хотя бы «текстовки» ошибок (сообщений об ошибках) для всех возможных вариантов нарушения синтаксиса. И даже за счёт одних только этих текстов сообщений об ошибок он уже получился бы прилично раздутым.

Как мы видим@firehacker ничтоже сумняще натягивает сову на глобус выдавая за истину свои спорные умозаключения о массивности интерпретаторов (Биллу Гейтсу с его четырех килобайтным бейсиком наверное очень обидно), об обязательности включения в интерпретатор текстовок ошибок, неимеющего под собой какого то обоснования "всех возможных вариантов нарушения синтаксиса" которое к бейсику не имеет ни какого отношения. ;)

А теперь представьте, что кто-то хочет написать 10 абсолютно простых, миниатюрных программок на QB. Тогда получается, что каждый EXE-файл содержал бы вшитую в него логику интерпретирования и ещё пачки строковых последовательностей с сообщениями об ошибках?

Вау! Это что получается, в каждый исполняемый файл нужно будет писать run-time модуль исполнения и текс всех ошибок исполнения?! .Есличё, то это был сарказм, при попытке натягивания совы. %))

Выглядит не очень логичным, но умозрительная рациональность или логичность какого-то подхода так себе аргумент «за» или «против» того, насколько такой подход соотносится с реальностью.

Добро пожаловать в реальный мир, где в любом EXE оттранслированном с высокоуровневого языка программирования есть сообщения об ошибках исполнения.

Поэтому давайте представим, что я тот самый человек, который хочет сделать вброс и заявить, что скомпилированная QB-программа на самом деле не скомпилированная, а просто склейка заранее заготовленного интерпретатора и исходного кода, который нужно интерпретировать. Или наоборот, я хочу сделать вброс, опровергающий такое общеустоявшееся мнение. Как бы там ни было, прежде чем делать вброс, я бы проверил свои тезисы, чтобы не сесть в лужу.

Спойлер для нетерпеливых: автор таки не просто "сел в лужу" с проверкой тезисов, он в этой "луже гордо плавал аки лебедь" 8))

Я бы запустил QuickBasic и написал простенькую программу в духе Hello world:

А зачем? Можно взять широко известную в узких кругах ретрокомьюнити Multi-platform Mandelbrot set plots, на которой тестируют производительность интерпретаторов и компиляторов на различных платформах, и увидеть насколько был эффективна реализация Basic на той или иной платформе.

В текст программы были внесены несущественные изменения, позволяющие более комфортно снимать показания о времени исполнения программы используя системную переменную TIMER (которая возвращает значения одинарной точности, представляющие количество секунд, прошедших с полуночи или сброса системы), строки 5,270. Строка 280 организует задержку путем ожидания ввода одного символа с клавиатуры. программа без какой-то модификации может быть исполнена в GW-Basic 3.23,QBasic,QuickBasic 4.5, PowerBasic 2.0. Размер текста программы на диске 448 байт.

5 CLS : z = TIMER
10 SCREEN 0
100 FOR PY = 0 TO 21
110 FOR PX = 0 TO 31
120 XZ = PX * 3.5 / 32 - 2.5
130 YZ = PY * 2 / 22 - 1
140 X = 0
150 Y = 0
160 FOR I = 0 TO 14
170 IF X * X + Y * Y > 4 THEN GOTO 215
180 XT = X * X - Y * Y + XZ
190 Y = 2 * X * Y + YZ
200 X = XT
210 NEXT I
215 IF I = 15 THEN I = 0
220 LOCATE PY + 1, PX * 2 + 1
230 COLOR 0, I
235 PRINT "  "
240 NEXT PX
260 NEXT PY
270 PRINT (TIMER - z)
280 a$=input$(1)

Сохраненная в GWBasic программа весит уже 374 байта. Вау! Компрессия!!! 83% от исходного текста! А что там внутри? Кровь, кишки и расчлененка! 8Р "Шутка! Бамбарбия! Киргуду!" (с) "Кавказская пленница"

"Ненси! Где мой торт *ука?!" (c) ужастик с голо��ом Володарского
"Ненси! Где мой торт *ука?!" (c) ужастик с голосом Володарского

И где тут FOR NEXT LOCATE PRINT COLOR? Бабайка унесла? «Господа гусары, молчать, куда девать лишнюю свечку!»;‑)

А теперь посмотрим что насохранял QuickBasic в своём "внутреннем формате с быстрым сохранение/восстановлением" который сохраняет всё-всё? 1064 байта! Вау! Экспаншен! Прибавка в 2,375 от чистого текста. А что там внутри?

"Ненси! Где мой торт *ука?!" (c) ужастик с голосом Володарского (дубль)
"Ненси! Где мой торт *ука?!" (c) ужастик с голосом Володарского (дубль)

А здесь где FOR NEXT LOCATE PRINT COLOR? Под санкции попали? А что размер в два с лишним раза увеличился? "What is this? The way of Microsoft, Blow everything up twice!" (с) MS

Обратите внимание, что здесь нам предлагают выбор: породить на свет EXE-файл, нуждающийся во внешнем BRUN45.EXE, или породить полностью самостоятельный или независимый EXE-файл. Давайте подыграем распространителям фейков и поверим в то, что тот самый BRUN45.EXE и есть интерпретатор, и нам предлагают либо вшить интерпретатор в сам итоговый EXE-файл, либо оставить в выходном EXE-файле маленький кусочек со ссылкой на и подгрузкой внешнего интерпретатор. Выберем вариант с зависимостью от внешнего BRUN45.EXE — в таком случае в нашем EXE-файле должен якобы остаться только исходный код программы и небольшой кусочек машинного кода, подгружающий интерпретатор из внешнего файла.

Обратите внимание, как было показано выше - СЫРОК QuickBasic является бинарным, а не текстовым форматом доступным для чтения "невооруженным" глазом.

Компилируем!

А линковать прогу за вас кто будет? Иван Федорович Крузенштерн?

И смотрим насколько быстро компилировалась и выполнялась тестовая программа, поскольку надо понять насколько был эффективен Мертвый лев стриженный пудель безродный кабыздох QuickBasic 4.5 от Мелкомягких и их конкурент PowerBasic (TurboBasic в девичестве).

Чтобы избежать разнотолков "как было на самом деле", будем использовать не DOSBox, а PCEm v17 на референсной машине (Xi8088) с 8088@4.77мгц, DOS 5 (RUS). И получаем следующие результаты:

"Вот и приплыли!" "Ну здравствуйте девочки!"
"Вот и приплыли!" "Ну здравствуйте девочки!"

И что мы видим? Мертвый лев стриженный пудель безродный кабыздох QuickBasic 4.5 слил PowerBasic по всем пунктам: И по скорости компиляции, и по качеству получаемого кода. Обратите внимание, скорость исполнения программы в IDE QB45 ниже на более чем сорок процентов чем откомпилированной программе, у PowerBasic скорость исполнения в IDE не отличается. Теперь понятно, что никакого заговора хейтеров "чудесной технологии QB" не было, продукт проигрывал по реальным, а не надуманным причинам. И программеры желавшие кардинально ускорить свои программы написанные в GWBasic, выбирали Turbo/PowerBasic.

Кстати, о компиляторе Microsoft Basic Bascom v2.0 (который потом ребрендируют в QuickBasic v1), время компиляции и линковки программы около 40 секунд, время исполнения откомпилированной программы 27,8 секунды.

А теперь берём hex-редактор HIEW и смотрим содержимое только что сгенерированного EXE-файла.

"А на ху а?" - как сказали бы китайские хакеры. "Ведь тут нужен OBJ2ASM!" На самом деле нет, но об этом чуть позже.

Прокрутимся в самый конец, ведь именно там должен быть исходный код, бережливо засунутый туда компилятором для последующей интерпретации в момент запуска:

А с какого перепугу вы делаете такие многозначительные выводы? Что им мешает быть в начале или в середине исполняемого файла? "Вера магометанская?" ;-)

Упс! Где же чёртов исходный код? Где же так милые сердцу ключевые слова DECLARE, SUB, END, FOR? Где же SOUND и PRINT? Где наш милый исходный код? Кажется, им тут и не пахнет! Может он не в конце, а в начале?

Но и в начале его нет! Ни в каком месте полученного EXE-файла исходного кода на языке QuickBasic не наблюдается и нет вообще.

А где здесь "чертов" исходный код с CLS TIMER FOR NEXT LOCATE PRINT COLOR? "Ты суслика видишь? А он есть!" ;-)

"Ненси! Где мой торт *ука?!" (c) ужастик с голосом Володарского (дубль)
"Ненси! Где мой торт *ука?!" (c) ужастик с голосом Володарского (дубль два)

Его здесь нет: ни целиком. Ни в виде отдельных процедур. Ни в виде отдельный statement-ов. Может хотя бы идентификаторы из нашего кода найдутся? Поищем-ка идентификатор HELLO и идентификатор MyCoolVariableI (именно для этого там в цикле не просто каноническое «i», а переменная со столь длинным именем):

Но никаких следов идентификаторов «HELLO» и «MyCoolVariableI» в содержимом EXE-файла не находится даже близко:

Неожиданно, интерпретатору не обязательно имя переменной для интерпретации, ему для работы будет вполне достаточно и адреса памяти. ;-)

Как же так? Ведь нас уверяют, что в EXE-файл просто тупо вшивается исходник, а при запуске EXE-файла встроенный (или не встроенный, а лежащий рядышком?) интерпретатор начинает его интерпретировать? Но на поверку оказывается, что в EXE-файле не обнаруживается исходный код ни в каком виде. Не то, что даже в виде отдельных строк, а даже отдельно взятые идентификаторы в EXE-файл не попадают.

Выше уже было показано что СЫРЕЦ в QB45 может быть бинарным и нечитаемым "невооруженным" глазом. Но вы, судя по всему, об этом даже не подозреваете. ;-)

Но подождите, нам могут сурово возразить и обвинить нас в манипуляции. Ведь изначальный посыл звучал так:

"Ничего он не переводил, а просто упаковывал исходник в виде ресурса и прицеплял его к exe интерпретатора"

Здесь не сказано, что компилятор просто копировал исходный код в EXE-файл как есть, а сказано, что он упаковывал его. Наверное имеется в виду сжатие каким-нибудь PKZIP или LZW. Ведь это конец 80-х, и нам не на что больше тратить драгоценные такты CPU, кроме как на сжатие и разжатие исходного кода. <sarcasm>Тогда абсолютно логично, почему в содержимом файла мы не видим зашитого исходника — он сжат алгоритмом сжатия!</sarcasm>

Но подождите. Если сжать исходный код упаковщиком, пройтись по нему каким-то алгоритмом сжатия, тогда от исходного текста действительно не останется и следа. А я, кажется, вижу в содержимом EXE-файла признаки человеческой речи:

А давайте договоримся что мы в 80-х и у нас каждый байт на счету, что нам нужно экономить память и при загрузке заменим ключевые слова Basic на коды? К примеру ABS это код 00, ASC это код 01 и так далее до WRITE#. И назовём эти кода ТОКЕНАМИ! И о чудо! Наша программа неожиданно сожмётся в 1,5-2 раза! И интерпретация загруженной программы, упростится и ускорится, код токена можно использовать как индекс в массиве ссылок на код выполняющий работу оператора/функции Basic. И что у нас получается? Поток байтов, без явно выраженной текстовой составляющей исходника.
А что до текста в программе, то будем просто его пропускать как не избежное зло. ;-)

Так что нет, версия, что исходный код при компиляции сжимается, а при запуска готово EXE-файла распаковывается в первозданный вид и передаётся интерпретатору — не оправдывается.

Гусары молчать! Человек просто не знал что токенизацию изобрели более 50 лет. 8Р

Но подождите вновь! В современном сумасшедшем мире давно есть такая вещь как «минификация»: фронтендеры скармливают свои JS-файлы минификаторам, которые удаляют все пробельные символы, вырезают комментарии, заменяют идентификаторы на однобуквенные (или имеющие минимальное достаточно число букв), но строковые литералы при этом остаются как есть. Может и здесь что-то такое же происходит? Может создатели QB пошли дальше и все ключевые слова и идентификаторы заменили на бинарное представление, а строки остались? Может именно это имелось в виду под упаковкой?

Ан нет господа гусары! Он об этом знал, но прикидывался шлангом, чтобы натянуть сову на глобус. >8))

А что если мы пойдём ва-банк?

А давайте! И возьмем настоящий инструмент, который в отличии от HIEW без СМС и регистрации даст нам полный листинг откомпилированной программы. Барабанная дробь! На арене цирка компилятор BC.EXE из состава QB4.5. Для пущего удобства создадим пакетник BC2ASM.BAT с единственной строчкой bc %1.bas,%1.obj,%1.lst /a /d /zd /zi (опции компилятора можно посмотреть вот тут)
Нас в первую очередь интересует опция /a Создает список дизассемблированного объектного кода для каждой строки исходного кода и показывает код на языке ассемблера, сгенерированный компилятором.
Оставшиеся опции не так интересны: /d Генерирует отладочный код для проверки ошибок во время выполнения и включает сочетание клавиш CTRL+BREAK.
/zd Создает объектный файл, содержащий записи с номерами строк, соответствующими номерам строк исходного файла.
/zi Создает объектный файл, содержащий отладочную информацию, используемую отладчиком Microsoft CodeView

Запускаем наш пакетник BC2ASM Mandelb1 и получаем на выходе

Ассемблерный листинг
                                                                      PAGE   1
                                                                      01 Jan 80
                                                                      00:57:31
Offset  Data    Source Line      Microsoft (R) QuickBASIC Compiler Version 4.50

 0030   0006    5 CLS : z = TIMER
 0030    **            I00002:
 0030   0006    10 SCREEN 0
 0030    **            L00005: mov   ax,0FFFFh
 0033    **                    push  ax
 0034    **                    call  B$SCLS
 0039    **                    call  B$TIMR
 003E    **                    mov   si,ax
 0040    **                    int   35h
 0042    **                    db    04h
 0043    **                    int   35h
 0045    **                    db    1Eh
 0046    **                    dw    Z!
 0048    **                    int   3Dh
 004A   000A    100 FOR PY = 0 TO 21
 004A    **            L00010: mov   ax,0001h
 004D    **                    push  ax
 004E    **                    xor   ax,ax
 0050    **                    push  ax
 0051    **                    mov   ax,0002h
 0054    **                    push  ax
 0055    **                    call  B$CSCN
 005A    **            L00100: int   35h
 005C    **                    db    06h
 005D    **                    dw    <00000000>
 005F    **                    jmp   I00003
 0062   000A    110 FOR PX = 0 TO 31
 0062    **            I00004:
 0062    **            L00110: int   35h
 0064    **                    db    06h
 0065    **                    dw    <00000000>
 0067    **                    jmp   I00005
 006A   000A    120 XZ = PX * 3.5 / 32 - 2.5
 006A    **            I00006:
 006A   000A    130 YZ = PY * 2 / 22 - 1
 006A    **            L00120: int   35h
 006C    **                    db    06h
 006D    **                    dw    PX!
 006F    **                    int   34h
 0071    **                    db    0Eh
 0072    **                    dw    <00006040>
 0074    **                    int   34h
 0076    **                    db    36h
 0077    **                    dw    <00000042>
 0079    **                    int   34h
 007B    **                    db    06h
 007C    **                    dw    <000020C0>
 007E    **                    int   35h
 0080    **                    db    1Eh
 0081    **                    dw    XZ!
 0083    **                    int   3Dh
 0085   0012    140 X = 0
 0085    **            L00130: int   35h
 0087    **                    db    06h
                                                                      PAGE   2
                                                                      01 Jan 80
                                                                      00:57:31
Offset  Data    Source Line      Microsoft (R) QuickBASIC Compiler Version 4.50

 0088    **                    dw    PY!
 008A    **                    int   34h
 008C    **                    db    0Eh
 008D    **                    dw    <00000040>
 008F    **                    int   34h
 0091    **                    db    36h
 0092    **                    dw    <0000B041>
 0094    **                    int   34h
 0096    **                    db    06h
 0097    **                    dw    <000080BF>
 0099    **                    int   35h
 009B    **                    db    1Eh
 009C    **                    dw    YZ!
 009E    **                    int   3Dh
 00A0   001A    150 Y = 0
 00A0    **            L00140: int   35h
 00A2    **                    db    06h
 00A3    **                    dw    <00000000>
 00A5    **                    int   35h
 00A7    **                    db    1Eh
 00A8    **                    dw    X!
 00AA    **                    int   3Dh
 00AC   001E    160 FOR I = 0 TO 14
 00AC    **            L00150: int   35h
 00AE    **                    db    06h
 00AF    **                    dw    <00000000>
 00B1    **                    int   35h
 00B3    **                    db    1Eh
 00B4    **                    dw    Y!
 00B6    **                    int   3Dh
 00B8    **            L00160: int   35h
 00BA    **                    db    06h
 00BB    **                    dw    <00000000>
 00BD    **                    jmp   I00007
 00C0   0022    170 IF X * X + Y * Y > 4 THEN GOTO 215
 00C0    **            I00008:
 00C0    **            L00170: int   35h
 00C2    **                    db    06h
 00C3    **                    dw    <00008040>
 00C5    **                    int   35h
 00C7    **                    db    06h
 00C8    **                    dw    X!
 00CA    **                    int   34h
 00CC    **                    db    0Eh
 00CD    **                    dw    X!
 00CF    **                    int   35h
 00D1    **                    db    06h
 00D2    **                    dw    Y!
 00D4    **                    int   34h
 00D6    **                    db    0Eh
 00D7    **                    dw    Y!
 00D9    **                    int   3Ah
 00DB    **                    db    0C1h
 00DC    **                    int   3Dh
                                                                      PAGE   3
                                                                      01 Jan 80
                                                                      00:57:31
Offset  Data    Source Line      Microsoft (R) QuickBASIC Compiler Version 4.50

 00DE    **                    call  B$FCMP
 00E3    **                    jna   $+03h
 00E5    **                    jmp   L00215
 00E8   0022    180 XT = X * X - Y * Y + XZ
 00E8   0022    190 Y = 2 * X * Y + YZ
 00E8    **            L00180: int   35h
 00EA    **                    db    06h
 00EB    **                    dw    X!
 00ED    **                    int   34h
 00EF    **                    db    0Eh
 00F0    **                    dw    X!
 00F2    **                    int   35h
 00F4    **                    db    06h
 00F5    **                    dw    Y!
 00F7    **                    int   34h
 00F9    **                    db    0Eh
 00FA    **                    dw    Y!
 00FC    **                    int   3Ah
 00FE    **                    db    0E9h
 00FF    **                    int   34h
 0101    **                    db    06h
 0102    **                    dw    XZ!
 0104    **                    int   35h
 0106    **                    db    1Eh
 0107    **                    dw    XT!
 0109    **                    int   3Dh
 010B   0026    200 X = XT
 010B    **            L00190: int   35h
 010D    **                    db    06h
 010E    **                    dw    <00000040>
 0110    **                    int   34h
 0112    **                    db    0Eh
 0113    **                    dw    X!
 0115    **                    int   34h
 0117    **                    db    0Eh
 0118    **                    dw    Y!
 011A    **                    int   34h
 011C    **                    db    06h
 011D    **                    dw    YZ!
 011F    **                    int   35h
 0121    **                    db    1Eh
 0122    **                    dw    Y!
 0124    **                    int   3Dh
 0126   0026    210 NEXT I
 0126    **            L00200: int   35h
 0128    **                    db    06h
 0129    **                    dw    XT!
 012B    **                    int   35h
 012D    **                    db    1Eh
 012E    **                    dw    X!
 0130    **                    int   3Dh
 0132    **            L00210: int   35h
 0134    **                    db    06h
 0135    **                    dw    I!
                                                                      PAGE   4
                                                                      01 Jan 80
                                                                      00:57:31
Offset  Data    Source Line      Microsoft (R) QuickBASIC Compiler Version 4.50

 0137    **                    int   34h
 0139    **                    db    06h
 013A    **                    dw    <0000803F>
 013C    **            I00007: int   35h
 013E    **                    db    1Eh
 013F    **                    dw    I!
 0141    **                    int   3Dh
 0143    **                    int   35h
 0145    **                    db    06h
 0146    **                    dw    <00006041>
 0148    **                    int   35h
 014A    **                    db    06h
 014B    **                    dw    I!
 014D    **                    int   3Dh
 014F    **                    call  B$FCMP
 0154    **                    ja    $+03h
 0156    **                    jmp   I00008
 0159   002A    215 IF I = 15 THEN I = 0
 0159    **            L00215: int   35h
 015B    **                    db    06h
 015C    **                    dw    <00007041>
 015E    **                    int   35h
 0160    **                    db    06h
 0161    **                    dw    I!
 0163    **                    int   3Dh
 0165    **                    call  B$FCMP
 016A    **                    je    $+03h
 016C    **                    jmp   I00010
 016F    **                    int   35h
 0171    **                    db    06h
 0172    **                    dw    <00000000>
 0174    **                    int   35h
 0176    **                    db    1Eh
 0177    **                    dw    I!
 0179    **                    int   3Dh
 017B   002A    220 LOCATE PY + 1, PX * 2 + 1
 017B    **            I00010:
 017B   002A    230 COLOR 0, I
 017B    **            L00220: mov   ax,0001h
 017E    **                    push  ax
 017F    **                    int   35h
 0181    **                    db    06h
 0182    **                    dw    PY!
 0184    **                    int   34h
 0186    **                    db    06h
 0187    **                    dw    <0000803F>
 0189    **                    call  B$FIS2
 018E    **                    push  ax
 018F    **                    mov   ax,0001h
 0192    **                    push  ax
 0193    **                    int   35h
 0195    **                    db    06h
 0196    **                    dw    PX!
 0198    **                    int   34h
                                                                      PAGE   5
                                                                      01 Jan 80
                                                                      00:57:31
Offset  Data    Source Line      Microsoft (R) QuickBASIC Compiler Version 4.50

 019A    **                    db    0Eh
 019B    **                    dw    <00000040>
 019D    **                    int   34h
 019F    **                    db    06h
 01A0    **                    dw    <0000803F>
 01A2    **                    call  B$FIS2
 01A7    **                    push  ax
 01A8    **                    mov   ax,0004h
 01AB    **                    push  ax
 01AC    **                    call  B$LOCT
 01B1   002A    235 PRINT "  "
 01B1    **            L00230: mov   ax,0001h
 01B4    **                    push  ax
 01B5    **                    xor   ax,ax
 01B7    **                    push  ax
 01B8    **                    mov   ax,0001h
 01BB    **                    push  ax
 01BC    **                    int   35h
 01BE    **                    db    06h
 01BF    **                    dw    I!
 01C1    **                    call  B$FIS2
 01C6    **                    push  ax
 01C7    **                    mov   ax,0004h
 01CA    **                    push  ax
 01CB    **                    call  B$COLR
 01D0   002A    240 NEXT PX
 01D0    **            L00235: mov   ax,offset <const>
 01D3    **                    push  ax
 01D4    **                    call  B$PESD
 01D9    **            L00240: int   35h
 01DB    **                    db    06h
 01DC    **                    dw    PX!
 01DE    **                    int   34h
 01E0    **                    db    06h
 01E1    **                    dw    <0000803F>
 01E3    **            I00005: int   35h
 01E5    **                    db    1Eh
 01E6    **                    dw    PX!
 01E8    **                    int   3Dh
 01EA    **                    int   35h
 01EC    **                    db    06h
 01ED    **                    dw    <0000F841>
 01EF    **                    int   35h
 01F1    **                    db    06h
 01F2    **                    dw    PX!
 01F4    **                    int   3Dh
 01F6    **                    call  B$FCMP
 01FB    **                    ja    $+03h
 01FD    **                    jmp   I00006
 0200   002A    260 NEXT PY
 0200    **            L00260: int   35h
 0202    **                    db    06h
 0203    **                    dw    PY!
 0205    **                    int   34h
                                                                      PAGE   6
                                                                      01 Jan 80
                                                                      00:57:31
Offset  Data    Source Line      Microsoft (R) QuickBASIC Compiler Version 4.50

 0207    **                    db    06h
 0208    **                    dw    <0000803F>
 020A    **            I00003: int   35h
 020C    **                    db    1Eh
 020D    **                    dw    PY!
 020F    **                    int   3Dh
 0211    **                    int   35h
 0213    **                    db    06h
 0214    **                    dw    <0000A841>
 0216    **                    int   35h
 0218    **                    db    06h
 0219    **                    dw    PY!
 021B    **                    int   3Dh
 021D    **                    call  B$FCMP
 0222    **                    ja    $+03h
 0224    **                    jmp   I00004
 0227   002A    270 PRINT (TIMER - z)
 0227   002A    
 0227   002A    
 0227    **            L00270: call  B$TIMR
 022C    **                    mov   si,ax
 022E    **                    int   35h
 0230    **                    db    04h
 0231    **                    int   34h
 0233    **                    db    26h
 0234    **                    dw    Z!
 0236    **                    sub   sp,04h
 0239    **                    mov   bx,sp
 023B    **                    int   35h
 023D    **                    db    1Fh
 023E    **                    int   3Dh
 0240    **                    call  B$PER4
 0245    **                    call  B$CENP
 024A   002A    

43997 Bytes Available
43114 Bytes Free

    0 Warning Error(s)
    0 Severe  Error(s)

Немая пауза...

Действительно, немая пауза: Ассемблерный листинг с командами мы имеем, но почему он так щедро пересыпан нестандартными прерываниями INT 26/34/35/3A/3D? Да потому что это FLOATING POINT EMULATION, интерпретатор команд математического сопроцессора который содержится в модуле исполнения BRUN45.

И обратите внимание, что вместо использования какого-нибудь отладчика я специально выбрал простейший hex-редактор HIEW, чтобы ни у кого не было соблазна сказать, что найденный код и найденные инструкции образовались в памяти процесса в результате распаковки/интерпретации/компиляции кода в процессе запуска EXE.

Да, да, обратите внимание что HIEW тут нафиг не нужен, поскольку в тюрьме которую вы построили, НЕТ четвертой стены

С тем, что QuickBasic всё-таки умеет генерировать .EXE и при этом это настоящий .EXE, самый настоящий, более настоящего не придумать — мы вроде бы разобрались.

Ну как умеет создавать? Делает очень рыхлый и неоптимальный код, со встроенным интерпретатором команд математического сопроцессора. таблицу сравнения результатов эффективности компиляторов смотреть выше.

Но QuickBasic никогда меня особо не интересовал.

Это уже давно понятно, но зачем повторять это снова и снова?

Мне хочется воззвать хотя бы к логике людей, которые тиражируют подобные байки. Если люди в Microsoft в 80-х годах сумели сделать и осилили такую вещь как генерацию настоящих полноценных EXE-файлов с машинным кодом в таком в общем-то несерьёзном и игрушечном продукте, как QuickBasic, неужели вы хоть на минуту допускаете, что в таком монструозном продукте как Visual Basic кто-то сделал бы такую дичь как засовывание в EXE-файл исходника в склейке с интерпретатором? Разве хоть немного правдоподобным это выглядит?

А вот здесь идет прямая манипуляция, в исполняемых файлах порождаемых Visual Basic хранится (БОЛЬШИМИ БУКВАМИ) P-CODE и мелкотравчатые НИКОГДА этого НЕ СКРЫВАЛИ, более они этим СПРАВЕДЛИВО ГОРДИЛИСЬ.
Visual Basic 6.0 - Superior Source Code The truth about P-Code Thursday, June 18, 2015

"Microsoft Visual Basic — это инструмент быстрой разработки приложений (Rapid Application Development, RAD), который позволяет гибко компилировать приложения в p-код (псевдокод) или машинный код.

Компиляция в p-код оптимизирует размер файла, что делает p-код подходящим вариантом для создания интернет-приложений в условиях низкой пропускной способности. Компиляция в машинный код оптимизирована для повышения скорости, но получаемые исполняемые файлы больше, чем версии в p-коде. Visual Basic — единственный инструмент быстрой разработки приложений, который поддерживает как быструю разработку приложений с помощью p-кода, так и компиляцию в машинный код для повышения производительности.

ПРИМЕЧАНИЕ: Для проектов Visual Basic, скомпилированных в p-код или машинный код, по-прежнему требуется, чтобы в целевой системе была установлена библиотека времени выполнения Visual Basic (MSVBVM50.DLL или MSVBVM60.DLL). Эта библиотека времени выполнения предоставляет ряд сервисов для вашей скомпилированной программы, таких как код запуска и завершения работы приложения, функциональные возможности для форм и встроенных элементов управления, а также функции времени выполнения, такие как Format и CLng.

Когда вы пишете строку кода в интегрированной среде разработки, Visual Basic разбивает её на выражения и кодирует выражения в предварительном формате, называемом кодами операций. Другими словами, каждая строка частично компилируется по мере написания. Некоторые строки содержат общую информацию, которую нельзя скомпилировать отдельно (в основном это операторы Dim и определения процедур). Вот почему вам нужно перезапустить программу, если вы изменили некоторые строки в режиме отладки. При компиляции коды операций преобразуются в инструкции p-кода (в фоновом режиме, если установлены параметры «Компилировать по запросу» и «Фоновая компиляция»).

Во время выполнения программы интерпретатор p-кода обрабатывает её, декодируя и выполняя инструкции p-кода. Эти инструкции p-кода меньше по размеру, чем эквивалентные инструкции машинного кода, что значительно уменьшает размер исполняемой программы. Но система должна загрузить в память интерпретатор p-кода в дополнение к коду и декодировать каждую инструкцию.

P-код, или псевдокод, — это промежуточный этап между высокоуровневыми инструкциями в вашей программе на Basic и низкоуровневым машинным кодом, который выполняет процессор вашего компьютера. Во время выполнения Visual Basic преобразует каждую инструкцию p-кода в машинный код. При прямой компиляции в машинный код промежуточный этап p-кода не требуется."

Напомню что компиляция в нативный машинный код в VB появилась только с 5-ой версии. Причем она так и осталась опциональной.

А теперь важные вещи, касающиеся VBA/EB и VB в плане генерации кода:

Да, будет очень важное взаимоисключающее заявление в двух смежных абзацах

  • EB/VBA никогда исторически не был интерпретируемым. Собственно, даже QuickBasic не интерпретирует исходный код в момент запуска программы под отладчиком — код интерпретируется в момент его вводы в редакторе кода и в дальнейшем внутри QB представлен не как код, а как нечто более абстрактное и предобработанное (но не как AST). EB унаследовал эту концепцию, и тоже не интерпретировал код (как это делал, скажем VBScript).

  • Поскольку EB должен был быть кроссплатформенным, поскольку, Excel, например, поставлялся так же и под Mac, а под Mac была своя аппаратная архитектура, то EB исторически никогда не компилировался в машинный код. Вместо этого авторы EB разработали свою виртуальную машину (почти как в Java) со своей собственной системой высокоуровневых команд. Байт-код для этой виртуальной машиной назывался P-код. Весь код, написанный на EB/VBA, в конечном счёте компилировался в P-код и в рабочем режиме (а также в режиме отладки) этот P-код исполнялся собственной P-кодной виртуальной машиной. Это был единственный и основной режим компиляции EB/VBA, что, в общем-то и логично. Реализация же самой виртуальной машины на разных аппаратных архитектурах и под разными ОС могла быть совершенно разной, а вот система команд была одной и той же, что теоретически означало бы, что единожды скомпилированный VB-код в P-код мог выполняться без перекомпиляции под сильно разными платформами.

Мля, вы или портки оденьте, или крестик снимите. Никогда не был интерпретатором/никогда не был компилятором? Может быть он вообще НЕ БЫЛ?

И да, технология фоновой интерпретации в P-CODE, была применена в QB4, об это даже вышла статья в BYTE за ноябрь 1987 года (страница 111).

"The absence of the compile delay displays the major innovation in QuickBASIC 4.0— the threaded p-code interpreter (see the text box "The Threaded Pcode Interpreter" on page 114 for an explanation of this technology). QuickBASIC 4.0 does not actually compile a program in memory."

114 страница (врезка) "The Threaded P-code Interpreter
Two key ideas make QuickBASIC 4.0's threaded p-code interpreter workable: precompilation of BASIC source code lines into code that you can execute but still display and edit at the source level, and an efficient way to run this code.
Here's how QuickBASIC 4.0 uses these ideas:
First, QuickBASIC 4.0 precompiles each line of BASIC source code into a form that is 90 percent executable machine code, housekeeping code being the remaining 10 percent of the precompiled code. This code is called Parsed in the Microsoft hierarchy. Parsed code can be edited but contains no symbol information about the program's variables.
At this point in the program creation process, you can run the entered program. When you invoke the Start option, the interpreter changes the state of the code to Symbolic by adding a symbol table for variables. Immediately upon completion of the change to Symbolic code, QuickBASIC 4.0 changes the code state again, this time to what Microsoft calls the Threaded state. This state change adds type-checking code, binds procedure calls and control structures to actual memory locations, links COMMON data, and generates code and addresses for executors, the actual machine code fragments that perform operations like PRINT or * (multiply).
Now, QuickBASIC 4.0 can execute the code by jumping from executor address to executor address. The p-code interpreter consists of two lines of assembly language code:
LODSW ES
JMP AX
These two lines are appended to each BASIC operation's machine code fragment during the Threaded state change.
The interpreter code does just two things: First, L0DSW ES simply loads the microprocessor's AX register with the address of the next executor in the code; JMP AX merely jumps to the address in AX, the next executor, and executes its code, whereupon it encounters another LODSW ES, JMP AX instruction pair that sends it on to another executor. In this way, the code fragments are threaded together, executing in a sequence determined by how they were entered and parsed .
The threaded p-code interpreter actually becomes part of the executing program. Because the interpreted execution of the program is part of the program, you can stop a program in the middle of its execution, change parts of the code, and then resume execution. QuickBASIC 4.0 recompiles only the executor code that you changed and alters only the symbol table information affected by your changes.
The process of converting code from Parsed to Symbolic to Threaded happens at the rate of 60,000 lines per minute. When you stop and alter a running program, QuickBASIC 4.0 has to change the program's state from Threaded back to Symbolic to let the editor take over the microprocessor. This process happens at a rate of 150,000 lines per minute.
When you finally complete and debug your program, you can save it to disk as an executable file using QuickBASIC 4.0 's Make .EXE option. Here, QuickBASIC 4.0 acts like a normal compiler. All the interpreter operation codes are discarded, and the compiler produces just executable machine code and symbol information.
The interpreter technology in QuickBASIC 4.0 is likely to appear in other products, according to a Microsoft spokesman, including language and application products."

Первый механизм, используемый VB — это компиляция кода во всё тот же P-код, как в VBA. Все процедуры компилируются в P-код и помещаются в EXE-файл вместе во вспомогательными структурами данных, чем-то похожими на RTTI в C++.

Это не компиляция, это трансляция в P-CODE, не путайте эти понятия.

Выбор, какой режим компиляции использовать, лежал на программисте. Оба варианта имели свои преимущества и недостатки. По умолчанию действовал вариант с генерацией машинного кода. Вариант с генерацией P-кода давал очень компактный код, потому что P-код инструкции были весьма высокоуровневыми: одна P-code инструкция могла делать то, что делает 50 машинных инструкций процессора. Зачастую P-кодный вариант был медленнее, чем исполняемый файл, сгенерированный в Native-код. Но не всегда: если куски машинного кода, составляющие реализацию виртуальной машины, удачно попадают в кеш инструкций процессора, выполнение P-code варианта могло (и может) наоборот обогнать Native-код.

До пятой версии VB, выбора режима компиляции то и не было. Была трансляция в P-CODE без вариантов. В пятой по умолчанию была трансляция в P-CODE, но появилась возможность компиляции в Native Code. И только в шестой версии VB компиляции в Native Code стала выбираться по умолчанию.

Использовать будем два продукта одного года выпуска: Visual Basic 6 и Visual C++ 6

А давайте провернем тоже самое c VB4 и Visual C++ 4?
И ещё, а как там с прямым выхлопом в ассемблер? Чтоб хренью не страдать? "Ия ия натюрлих!" или "нихт найн забытые устаревшие технологии предков!"?

Что мы здесь видим? Главным образом мы видим то, что внутри EXE-файла, порождённого на свет силами VB, содержится, чёрт его возьми, машинный код.

А что мы должны были увидеть при компиляции в Native Code? Проплывающий барк "Седов"?

Я просто ума не приложу, чем EXE-файл, генерируемый силами VB, не является настоящим, если он, зараза, байт-в-байт, инструкция-в-инструкцию идентичен результату компиляции эквивалентного кода, написанного на C++?

Потому что вы занимаетесь манипуляцией и подменой понятий, поскольку для проверки используете банальный код, на котором компилятор не может продемонстрировать возможности оптимизации кодогенерации.

Куда прикольнее просто пнуть тушу мёртвого льва, ведь лев уже не даст сдачи.

Повторюсь ещё раз, что для этого надо:
А) быть мертвым
Б) быть львом
В) должен существовать желающий заниматься хной

В случае с функцией Fact() варианты, которые выдали компиляторы VB и C++ отличаются: VB-шный выхлоп более многословен. Но это ни в коем случае не означает, что VB-шный компилятор в каком-то смысле менее полноценный или оптимальный.

А это доказывает что VB в качестве промежуточного шага выполняет трансляцию в P-CODE, а уже потом его транслирует в Native Code. Поэтому и "многословность" вылезла не смотря на "оптимизацию по скорости".

Но мы с вами помним — VB просто вшивает исходный код в EXE-шник, наряду с интерпретатором. VB умеет делать EXE, но эти EXE априори ненастоящие, неполноценные, недоделанные какие-то. Ну, по крайней мере, так искренне считают люди, которые распространяют эти бредовые байки и поверия.

До пятой версии всё было именно так: Только P-CODE, только хардкор!

А то что появилось в пятой версии и выше (компиляция в Native Code), это как говорит Леонид Каневский "Совсем другая история"

Нужно знать, как устроен компилятор (транслятор) C/C++, являющийся частью Microsoft Visual C++ 6.0 (для более ранних версий это актуально в той же степени — не только для шестой).

Компилятор != транслятор

CL.EXE разбивает работу по компиляции (трансляции) исходного файла на два этапа:

рукалицо.жпг Это называется двухпроходная компиляция

Первый этап выполняет фронтенд, который зависит от языка исходного кода. У Си свой фронтенд (C1.DLL), у Си++ — свой (C1XX.DLL). Задача фронтенда — выполнить первые, языко-специфичные шаги трансляции. Это обработка директив препроцессора, токенизация, построение AST-деревьев, построение таблиц имён, объектов, олицетворяющих процедуры, генерация графов хода выполнения, разворачивание циклов и тому подобное.

Пардон муа, циклы и оптимизация идут на втором проходе.

В случае же Visual Basic для генерации EXE-файлов в режиме «Compile into Native Code» Microsoft позаимствовали бэкенд (C2) компилятора C/C++ у команды Visual C++ и включили его в состав продукта VB. С единственной оговоркой: что теперь это не C2.DLL, а C2.EXE, который в процессе компиляции вызывается средой (VB IDE) и выполняет всю грязную работу.

Это называется оптимизация команды разработчиков. Зачем держать N команд разработчиков кодогенераторов с каждого ЯП, когда можно обойтись одной из команды Си(++). А что это не всегда учитывает особенность другого ЯП, ничего страшного "процессоры с каждым годом становятся всё быстрее". И вообще, оптимизация "это проблемы индейцев".

 И абсолютно глупо сравнивать или задаваться целью сравнить, какой компилятор сгенерирует более хороший, либо быстрый, либо компактный машинный код для одной и той же задачи — VB или C/C++ — просто потому, что генерацией машинного кода и в том и в другом случае занимается один и тот же компилятор (а точнее его половинка) — C2.

Ещё одна манипуляция, кодогенератор то один, а вот какой IL попадает ему на вход совсем другое дело. Бытовой пример: 76 - бензин, и 98 - бензин. Но есть нюанс.

Но вопрос не в том, что оптимальнее и насколько оптимальнее. Вопрос в том, что генерация машинного кода в случае с VB не уступает таковой у C++ (того же поколения, версии) просто по той причине, что делается силами и средствами того же самого механизма кодогенерации.

Отличается то, что попадает на вход кодогенератора, и если там на входе будет шлак, то навряд ли что на выходе посыпятся жемчужины.

Компиляция в P-code же — совсем другая история.

Это называется трансляция

Да, в этом случае код пользовательских процедур не превратится в машинный код. Он превратится в P-код и будет исполнен виртуальноый машиной VB.

Алилуя браться и сестры! Наконец то автор признал существование ВИРТУАЛЬНОЙ машины исполняющей P-CODE! Правда под конец статьи, но кто читает статьи на Хабре до конца? 8))

Но это ни коим образом не делает эти EXE каким-то неполноценными и не даёт право называть их интерпретируемыми,

За то это делает ТАКИЕ EXE существенно медленнее. И да, P-CODE в этих файлах интерпретируется виртуальной машиной.

так же как выполнение Java-приложения на JVM не делает Java-код интерпретируемым.

Делает.

Виртуальная машина VB — стековая.

Вообще это виртуальная машина P-CODE, которая не заточена эксклюзивно под Васик

Теперь, когда вы встретите людей, либо утверждающих, что QuickBasic является интерпретируемым языком

QBASIC - интерпретатор (P-CODE), QB45 IDE - интерпретатор (P-CODE), код исполняемых файлов порождаемый QB45 IDE или компилятором командной строки, существенно уступал по качеству кода альтернативным решениям. И его критика по этому параметру вполне оправдана и уместна. Причем не надо забывать, что скорость исполнения программы QB45 IDE была ниже чем у откомпилированного EXE

либо утверждающих, что Visual Basic является интерпретируемым языком (с той же чушью относительного неполноценности EXE-файлов),

Все правильно, до пятой версии Visual Basic был привязан к виртуальной машине которая лежала в DLL и интерпретировала, и исполняла P-CODE. Во всеми прилагающимися к этому ограничениями с падение производительности.

вы знаете, что делать.

«Натянуть сову на глобус, не привлекая внимания санитаров» ;-)

Вежливо и конструктивно объясните им, что они мягко говоря заблуждаются.

Бейтесь в истерике, главное побольше громких патетичных высказываний. И главное не проболтайтесь: Microsoft САМА похоронила Visual Basic посчитав его тупиковой веткой развития, и "хейтеры и холиварщики из интернета" тут не причем. :)

Пришлите им ссылку на эту статью.

И не читайте результаты независимых тестов производительности получаемых EXE файлов. Вдруг ваша картина мира пошатнется?

И обязательно скажите, что делать громкие, но непроверенные заявления нужно обязательно с припиской «одна бабка сказала».

И главное, не попадайтесь на глаза поклонникам зеленогописа, за сову на глобусе они могут вам сделать не только «а‑та‑та», но и использовать вас вместо пострадавшей совы на том самом глобусе.

Фух, а теперь можно выдохнуть...

Спасибо, что разрешили ;-) Если чё то это был /сарказм