На самом деле, этой статьи не должно было появиться. Должен был появиться комментарий к статье «Кто угодно может пнуть мёртвого льва» разбирающий заблуждения и откровенный манипуляции автора статьи, но он разросся до таких размеров, поскольку автор н��гнал такого кринжу, что проще стало оформить его в полноценную статью (что бы 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Р "Шутка! Бамбарбия! Киргуду!" (с) "Кавказская пленница"

И где тут FOR NEXT LOCATE PRINT COLOR? Бабайка унесла? «Господа гусары, молчать, куда девать лишнюю свечку!»;‑)
А теперь посмотрим что насохранял QuickBasic в своём "внутреннем формате с быстрым сохранение/восстановлением" который сохраняет всё-всё? 1064 байта! Вау! Экспаншен! Прибавка в 2,375 от чистого текста. А что там внутри?

А здесь где 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? "Ты суслика видишь? А он есть!" ;-)

Его здесь нет: ни целиком. Ни в виде отдельных процедур. Ни в виде отдельный 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 файлов. Вдруг ваша картина мира пошатнется?
И обязательно скажите, что делать громкие, но непроверенные заявления нужно обязательно с припиской «одна бабка сказала».
И главное, не попадайтесь на глаза поклонникам зеленогописа, за сову на глобусе они могут вам сделать не только «а‑та‑та», но и использовать вас вместо пострадавшей совы на том самом глобусе.
Фух, а теперь можно выдохнуть...
Спасибо, что разрешили ;-) Если чё то это был /сарказм