ZX-Ревю 1993 №11-12 1992 г.

Sinclair logo - глава 9. ввод и вывод информации, окончание.


Темы статьи: Программирование  

SINCLAIR LOGO

(Окончание)

Начало см. на с. 69-74, 90-96, 134 - 138, 180 - 186.

ГЛАВА 9. ВВОД И ВЫВОД ИНФОРМАЦИИ

До сих пор мы рассматривали только один метод ввода информации в компьютер -READLIST и (если не учитывать черепашью графику) только один метод вывода информации - PRINT. В этой главе мы рассмотрим некоторые методы, которые дадут программисту новые возможности для ввода/вывода, хотя и ценой более сложного программирования.

READCHAR ждет, когда пользователь нажмет какую-либо клавишу, принимает один символ с клавиатуры и выдает его в качестве выходного результата. После нажатия клавиши не надо нажимать ENTER, принятый символ не изображается на экране, ввод символа не сопровождается изображением "приглашения". Попробуйте: MAKE "CHARACTER READCHAR

Нажмите ENTER - система ожидает нажатия любой клавиши. Нажмите любую клавишу. На экране появится приглашение "?" - это означает, что ЛОГО-систена вернулась в командный режим.

Вы можете проверить, что за символ был введен: PRINT :CHARACTER

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

TO DRAW

MAKE "CHARACTER

READCHAR

IF :CHARACTER =

"F

[FORWARD 5]

IF :CHARACTER =

"B

[BACK 5]

IF :CHARACTER =

"R

[RIGHT 10]

IF :CHARACTER =

"L

[LEFT 10]

IF :CHARACTER =

"S

[STOP]

DRAW

END

Функция READCHAR бывает очень полезной, когда вместо символов Вы работаете с их кодами. Коды символов с 0 по 127 называют кодами ASCII (American Standard Code for Information Interchange). Коды с 0 по 31 используются компьютером в качестве управляющих при операциях ввода/вывода (например для перемещения курсора) и не являются печатными, т.е. их нельзя изобразить на экране, хотя они и влияют на то, как изображаются другие (печатные) коды. Коды с 32-го по 127 - печатные символы (буквы, цифры, знаки препинания). Дополнительно к стандарту ASCII "Спектрум" имеет еще ряд кодов для символов.

Коды 123 - 143 представляют символы блочной графики, а символы 144 - 164 - это символы графики пользователя.

Вы можете узнать, какому символу какой код соответствует с помощью следующей несложной программы.

TO SHOWASCII

MAKE "CHARACTER READCHAR

PRINT ASCII :CHARACTER

SHOWASCII

END

Создав такую процедуру, Вы можете дать команду SHOWASCII и, нажав любую клавишу, увидеть на экране код этого символа.

Противоположное действие имеет команда CHAR. Она переводит код от 32 до 143 в соответствующий графический символ.

Так, PRINT CHAR 65 даст:

А

Можно использовать команду CHAR совместно с командой ASCII. Нижеприведенная

процедура будет шифровать ту информацию, которую Вы введете с клавиатуры.

TO CONFUSE

MAKE "CODE ASCII READCHAR IF :CODE = 13 [STOP] TYPE CHAR (:CODE + 1) CONFUSE

END

Процедура прекратит свою работу, когда будет нажата клавиша ENTER (ее код = 13). Если Вам не надо ожидать нажатия клавиши, то Вы можете использовать функцию KEYP. Она проверяет факт нажатия какой-либо клавиши и выдает TRUE или FALSE в зависимости от того, имело или нет место такое событие.

Вот еще одна программа для черепашки, отличающаяся тем, что между нажатиями клавиш черепашка продолжает свое движение.

TO DRAW2

IF KEYP [DOIT] FORWARD 5 DRAW2

END

TO DOIT

MAKE "CHARACTER READCHAR IF :CHARACTER ="M [RIGHT 90] IF :CHARACTER ="Z [LEFT 90]

END

Клавишами M и Z Вы теперь можете управлять движением черепашки по экрану. Символы от 144 до 164 включительно могут задаваться непосредственно пользователем. О том, как это сделать, рассказано в инструкции к компьютеру (и в книгах ИНФОРКОМа). Но для того, чтобы задать эти символы, в определенные адреса памяти компьютера надо внести свои значения. В языке ЛОГО для этой цели служит команда .DEPOSIT. Но будьте осторожны при работе с этой командой. Она напрямую меняет содержимое ячеек памяти компьютера. ЛОГО-система не в состоянии проверить правильность Ваших действий и, если Вы станете использовать эту команду без четкого понимания того, что и зачем Вы делаете, есть большая вероятность того, что Вы можете разрушить созданные Вами и хранящиеся в памяти компьютера процедуры.

Пара нижеприведенных процедур демонстрирует задание нового символа графики пользователя путем помещения в память компьютера (начиная с адреса 64215) списка из восьми чисел, которые и определяют конструкцию нового символа.

TO DEFCHAR :CH :ALIST

IF OR NOT NUMBERP :CH NOT LISTP :ALIST [STOP] IF OR :CH<144 :CH>164 [STOP] DODEF :ALIST 0

END

TO DODEF :ALIST :COUNT IF EMPTYP :ALIST [STOP] .DEPOSIT 64216 + :CH*8 + :COUNT FIRST :ALIST DODEF BUTFIRST :ALIST :COUNT +1

END

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

DEFCHAR 144 [16 16 16 16 146 84 56 16]

Для управления выводом текста на экран компьютера тоже существует ряд полезных процедур и мы последовательно их рассмотрим.

SETCURSOR - принимает в качестве входных параметров список из двух чисел и помещает курсор в соответствующую позицию экрана. С этой позиции и начнется в последующем печать выводимого текста. Первое число списка указывает на номер экранного столбца (от 0 до 31), а второе число - на номер экранной строки (от 0 до 21). Левому верхнему углу соответствуют координаты (0,0).

SETTC (SET Text Color) тоже принимает на входе два числа. Первое число задает цвет

фона (аналог оператора БЕЙСИКА PAPER), а второе число задает цвет символов (аналог INK). Обычная ситуация для режима печати черным по белому - [7,0]. Вы можете поэкспериментировать со следующей процедурой:

TO TESTCOL TEXTSCREEN SETCUR [12 10] SETTC [1 6] PRINT [HELLO THERE] MAKE "G READCHAR SETTC [7 0]

END

В последних строках организовано ожидание нажатия клавиши для восстановления нормальных цветов. Параметр яркости фона, на котором печатаются символы, может задаваться командой BRIGHT (как и в БЕЙСИКе). Команда BRIGHT должна иметь при себе один параметр, который может быть равен 0 или 1. BRIGHT 1 - повышенная яркость, BRIGHT 0 - обычный режим.

Команда INVERSE включает режим печати символов в инвертированном виде, т.е. цвет символов становится цветом фона и наоборот.

Команда FLASH включает режим мигания символов, когда попеременно цвета символов и фона меняются местами.

Команда NORMAL отбивает эффект команд BRIGHT, INVERSE, FLASH и восстанавливает исходный режим печати текста.

Для тех, кто хочет посмотреть эти команды в работе, предлагается следующий пример:

TO FANCYNAME

PRINT [WHAT IS YOUR NAME?]

MAKE "NAME READLIST

TEXTSCREEN

BRIGHT 1

SHOWNAME

SETTC [7 0]

SETCUR [0 21]

END

TO SHOWNAME

IF KEYP [STOP] MAKE "X RANDOM 31 MAKE "Y RANDOM 21 MAKE "BG RANDOM 8 MAKE "FG RNDCOL SETTC SENTENCE :BG :FG SETCUR SENTENCE :X :Y TYPE :NAME SHOWNAME

END

TO RNDCOL

MAKE "FG RANDOM 8

IF :FG=:BG [OUTPUT RNDCOL] [OUTPUT :FG]

END

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

А вот еще один пример для демонстрации приведенной выше техники. Группа процедур обрабатывает результаты матчей небольшой футбольной лиги. Главная процедура организует списки и печатает в цвете заголовки будущей таблицы, затем вызывается процедура HEADCOL, которая печатает заголовки столбцов, а потом процедура DOTEAM, обрабатывающая результаты для каждой команды.

TO LEAGUE

MAKE "TEAMS [SPARTAK DINAMO AVANGARD PROGRESS]

MAKE "COLS [P W D L PTS]

TEXTSCREEN

SETCURSOR [9 0]

SETTC [5 0]

TYPE [LEAGUE TABLE]

SETCURSOR [0 2]

SETTC [7 3]

TYPE "TEAM

HEADCOL 1

SETTC [7 0]

DOTEAM 1

PRINT "

END

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

TO HEADCOL :COL IF :COL>5 STOP

SETCURSOR SENTENCE (3*:COL + 11] 2 TYPE ITEM :COL :COLS HEAADCOL :COL + 1

END

Процедура DOTEAM определяет номер строки, в которой печатаются результаты для данной команды (он зависит от номера команды в списке команд). Она принимает из списка название команды, а затем принимает от пользователя с клавиатуры количество сыгранных игр (Р), количество побед (W) и ничьих (D). При этом она использует еще одну процедуру -GETNUM.

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

Количество проигрышей команды (L) и набранные ею очки (PTS) вводить не надо, они могут быть рассчитаны самой программой. Предполагается, что победа оценивается двумя очками, а ничья - одним очком.

TO DOTEAM :TEAMNO

IF :TEAMNO COUNT :TEAMS [STOP] MAKE "LINE 2*:TEMNO + 2 SETCURSOR SENTENCE 0 :LINE TYPE ITEM :TEAMNO :TEAMS MAKE "PLAYED GETNUM 14 :LINE " MAKE "WON GETNUM 17 :LINE " MAKE "DRAWN GETNUM 20 :LINE " MAKE "LOST :PLAYED - :WON - :DRAWN SETCURSOR SENTENCE 23 :LINE TYPE :LOST

MAKE "POINTS 2*:WON + :DRAWN SETCURSOR SENTENCE 25 :LINE TYPE :POINTS DOTEAM :TEAMNO + 1

END

Прежде, чем рассмотреть процедуру GETNUM, мы рассмотрим еще одну связанную с ней процедуру GETDIG. Она принимает цифру с клавиатуры, а возвращает и эту цифру и ее код ASCII. Она передает также коды ENTER (13) и DELETE (12), если соответствующие клавиши нажаты. Если же нажата любая другая клавиша, процедура ее игнорирует и вызов повторяется. Цифрам от нуля до 9 соответствуют коды ASCII от 48 до 57.

TO GETDIG

MAKE "DIG READCHAR

MAKE "CODE ASCII :DIG

IF OR :CODE = 13 :CODE = 12 [STOP]

IF OR :CODE<48 :CODE>57 [GETDIG]

END

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

В качестве курсора для печати используем мигающий вопросительный знак. Это легко реализуется следующими строками: FLASH TYPE *? NORMAL

Цифра, введенная пользователем, поставляется из процедуры GETDIG. Оттуда же могут поступить и коды ENTER или DELETE. При появлении этих кодов следует убрать вопросительный знак из текущей позиции, в противном случае он продолжает мигать. Удаление вопросительного знака исполняется печатью на его месте пробела (символ 32). Могут быть проблемы с точным определением местоположения вопросительного знака. Самый лучший способ - пойти к началу вводимого числа (мы знаем где это начало) и снова напечатать число, а за ним уже пробел - на месте вопросительного знака. SETCURSOR SENTENCE :X :Y TYPE NO TYPE CHAR 32

Процедура GETDIG может выдать три возможных результата: была нажата цифра с кодом от 48 до 57, либо клавиша ENTER (код 13) либо DELETE (код 12).

Если это цифра, то ее просто надо напечатать, добавив к ранее напечатанным цифрам вводимого числа. В нижеследующем примере предполагается, что числа имеют не более двух знаков и при попытке ввести третью цифру она игнорируется. IF AND :CODE>13 (COUNT :NO)<2 MAKE "NO WORD :NO :DIG

Если нажата клавиша ENTER и если число не пустое, то мы можем напечатать это

число.

Если нажата клавиша DELETE, то мы должны удалить последний символ числа, используя BUTLAST. Но и в этом случае сначала надо удостовериться, что число не является пустым словом. И если Вы рассмотрите нижеприведенную процедуру, то увидите в ней несколько необычную строку: IF EMPTYP :NO [MAKE "NO"]

Кажется, что в ней нет необходимости, но это не так. Дело в том, что когда мы применяем BUTLAST к слову, в котором есть только один символ, то рассчитываем получить пустое слово, но вместо него получаем пустой список, с которым команда WORD, помещающая очередную цифру в число не сможет работать. Эта странная операция конвертирует пустой список в пустое слово, TO GETNUM :X :Y :NO

SETCURSOR SENTENCE :X :Y

TYPE :NO

FLASH

TYPE "?

NORMAL

GETDIG

SETCURSOR SENTENCE :X :Y TYPE :NO TYPE CHAR 32

IF AND :CODE = 13 (NOT EMPTYP :NO) [OUTPUT :NO]

IF AND :CODE = 12 (NOT EMPTYP :NO) [MAKE "NO BUTLAST :NO]

IF EMPTYP :NO [MAKE "NO"] IF AND :CODE>13 (COUNT :NO)<2 MAKE "NO WORD :NO :DIG OUTPUT GETNUM :X :Y :NO

END

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

ЗВУК.

"Спектрум" может выдавать звуковые сигналы. Это делается с помощью команды SOUND, после которой идет список из двух чисел. Первое число выражает длительность звучания ноты в секундах и должно быть в диапазоне от 0 до 10.5. Второе число - высота ноты, измеренная в полутонах относительно ноты "до" первой октавы. Это число должно быть в диапазоне от -60 до +69. Следующая процедура превратит клавиатуру Вашего компьютера в электроорган, но играть на нем весьма сложно.

TO KEYBOARD

MAKE "KEY (ASCII READCHAR)-65 IF AND :KEY>-59: KEY<70 [SOUND SENTENCE 0.5 :KEY] KEYBOARD

END

Сыграть список нот равной длительности Вы можете с помощью процедуры:

TO PLAY :ALIST

IF EMPTYP :ALIST [STOP] SOUND SENTENCE 1 FIRST :ALIST PLAY BUTFRST :ALIST

END

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

PLAY [0 2 4 5 7 9 11 12]

К сожалению, способ задания звуков с помощью команды SOUND очень далек от общепринятой нотной нотации. Впрочем, можно написать процедуру, которая сделает за нас необходимое преобразование. Для этого служит следующая приведенная нами процедура. Первый параметр, который она ожидает, задает темп музыкальной мелодии. Это продолжительность звучания (в секундах) ноты, длительность которой принята нами за единицу. Затем процедура принимает список нот и размещает их в предварительно созданном списке SLIST, после чего исполняется мелодия.

TO MUSIC

MAKE "NOTELIST [DO DO# RE RE# MI FA FA# SO SO# LA LA# SI DO'] MAKE "SLIST[] PRINT [TEMP?]

PRINT [ВВЕДИТЕ НОТЫ ПО ОДНОЙ, СНАЧАЛА ДЛИТЕЛЬНОСТЬ]

GETMUS

PLAY :SLIST

END

Процедура GETMUS принимает с клавиатуры список из двух чисел. Первое число - длительность ноты. Предполагается, что самая короткая из них имеет длину 1, а длительность прочих - 1, 2, 3,... и т.д. Реальная же длительность (в секундах) будет зависеть от введенного Вами параметра TEMP.

Высоту ноты будет определять ее название - Do, Re, Mi и т.д.

Процедуре GETMUS нужна еще одна вспомогательная процедура FIND, которая найдет объект в списке. Первые два входных параметра для процедуры FIND - это имена объекта и списка, а третий параметр - стартовый номер, с которого начинается отсчет в списке. Если мы хотим, чтобы первой нотой была "ДО" первой октавы, то положим этот параметр равным нулю.

TO GETMUS

MAKE "NIOTE READLIST

IF EMPTYP :NOTE [STOP]

MAKE "DUR :TEMP*FIRST :NOTE

MAKE "PITCH FIND LAST :NOTE :NOTELIST 0

MAKE "SLIST LPUT LIST :DUR :PITCH :SLIST

GETMUS

END

TO FIND :OBJECT :ALIST :N IF EMPTYP :ALIST [OUTPUT 0]

IF :OBJECT = FIRST :ALIST [OUTPUT :N]

OUTPUT FIND :OBJECT BUTFIRST :ALIST :N + 1

END

И, наконец, само исполнение музыки реализует процедура PLAY. Она отличается от ранее приведенной процедуры с тем же именем PLAY только тем, что полагает каждый элемент своего входного списка тоже списком, состоящим из двух элементов, которые затем передаются команде SOUND).

TO PLAY :ALIST

IF EMPTYP :ALIST [STOP]

SOUND FIRST :ALIST

PLAY BUTFIRST :ALIST

END

Введя все процедуры, попробуйте программу в деле:

TEMP?

?0.3

ВВЕДИТЕ НОТЫ ПО ОДНОЙ, СНАЧАЛА ДЛИТЕЛЬНОСТЬ

?2SO

?4DO'

?2SI

?2LA

?4SO

?2LA

?1SI

?1DO'

?2MI

?2MI

?2FA

?2RE

?6DO

Когда закончите, нажмите ENTER и после короткой паузы музыка заиграет. Один раз введя мелодию, Вы сможете воспроизвести ее всякий раз, как захотите с помощью команды:

PLAY :SLIST

ГЛАВА 10. ЕСЛИ ЧТО-ТО НЕ ПОЛУЧАЕТСЯ

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

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

Можно сказать, что при программировании на языке ЛОГО Вас ждут два разных типа ошибок. Если Вы сделали ошибку при написания команд и ЛОГО не понимает, что Вы хотите сделать, Вы получите сообщение об ошибке. В других случаях ЛОГО понимает, что Вы хотели сделать и выполняет Ваши команды, но результат Вы получаете совсем не тот, который Вам необходим.

Методика отыскания и исправления ошибок и в том и в другом случае очень похожа. Хотя первый тип ошибок проще поддается исправлению, поскольку ЛОГО-система сама уже сказала Вам в своем сообщении что ей не нравится и в какой процедуре она наткнулась на препятствие. В этом смысле ЛОГО удобнее многих другие языков программирования, поскольку его сообщения как правило более осмысленны и содержательны, чем в других языках. Тем не менее, все-таки эти сообщения были написаны создателями языка задолго до того, как Вы начали писать свою программу. Им как бы пришлось "предугадать" Ваши возможные ошибки. Это не всегда можно сделать точно. Потому не удивляйтесь, если эти

сообщения будут не абсолютно точны в определении Ваших ошибок.

Делу может помочь краткий обзор некоторых наиболее часто встречающихся ошибок и сообщений. Например:

1. *You don't say what to do with ABCD* (Вы не указали, что делать с ABCD)

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

Рассмотрим пример. Предположим, что некоторая процедура SAYABCD выдает в качестве результата ABCD и Вы запишете такую строку:

PRINT "RESULT SAYABCD

В результате Вы получите приведенное выше сообщение об ошибке. Дело в том, что команда PRINT ожидает только один параметр в качестве входного и он здесь есть -"RESULT. Никакого указания, что делать с ABCD, здесь нет. Чтобы исправить ошибку, очевидно надо использовать команду SENTENCE, дабы команда PRINT могла распечатывать несколько данных.

2. *Not enough inputs to ABCD* (недостает входных параметров для ABCD)

Если Вы рассмотрите определение процедуры ABCD, то должны обнаружить, что она ожидает большое число входных параметров, чем Вы ей дали. Может быть, это будет выглядеть и неочевидным, но метод анализа строк ЛОГО, который мы привели в Главе 3 Вам должен помочь.

3. *XYZ doesn't output to ABCD* (XYZ не выдает данные для ABCD

Эта ошибка возникает тогда, когда операция ABCD применяется к некоторой команде XYZ (не являющейся операцией), которая не выдает выходного результата, необходимого для успешной работы ABCD.

4. *Too many inside parenthesis* (слишком много скобок).

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

5. "XYZ doesn't like ABCDE as input*

(XYZ не принимает ABCDE в качестве входного параметра). Эта ошибка может происходить в двух случаях. Либо результат ABCDE неприемлем для XYZ, т.к. он имеет не тот тип (это, например, число, а должен быть список):

SETCUR 10 15

Либо результат имеет правильный тип, но не находится в допустимых пределах:

SETCUR [10 45]

При появлении такой ошибки Вам следует внимательно вручную проверить что именно Ваша процедуру ожидает в качестве входного параметра.

Сообщение об этой ошибке может иметь весьма обескураживающий вид, например:

*XYZ doesn't like as input*

Здесь выпало слово ABCDE - это потому, что результат ABCDE является в данном случае пустым словом.

6. *Not enough space to proceed* (Не хватает рабочего пространства).

Это сообщение о том, что наступила нехватка оперативной памяти компьютера для продолжения дальнейших вычислений. В такой ситуации можно попробовать принять предохранительные меры. Один из приемов - запустить процедуру RECYCLE. Эта команда выполнит то, что на компьютерном жаргоне называют "уборкой мусора". Она разыщет области памяти, выделенные в свое время для каких-то операций, в которых более нет необходимости и освободит дополнительный объем памяти. Может быть, эта мера и не приведет к какому-то положительному результату, поскольку время от времени система сама производит "уборку мусора" автоматически.

Другой, более радикальный прием - удалить из памяти те процедуры, которые Вам более не нужны.

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

Теперь мы рассмотрим ряд приемов, с помощью которых отыскивают и устраняют ошибки в программах. В качестве примера мы вернемся к одной из ранее рассмотренных процедур - это процедура GETNUM из Главы 9. Процедура предназначена для ввода двузначных чисел и изображения мигающего курсора в экранной позиции с координатами x и у. Процедура GETNUM использует в работе процедуру GETDIG, рассмотренную в предыдущей главе.

TO GETDIG

MAKE "DIG READCHAR MAKE "CODE ASCII :DIG IF OR :CODE=13 :CODE=12[STOP] IF OR :CODE<48 :CODE>57[GETDIG]

END

Испытание процедуры GETDIG прошло нормально. При этом нажимались различные цифровые клавиши и процедура правильно распечатывала значения DIG и CODE.

После этого испытания была набрана (так, как показано ниже) процедура GETNUM.

TO GETNUM :X :Y :NO SETCUR SE :X :Y TYPE :NO FLASH TYPE "? NORMAL GETDIG

IF AND :CODE=13 NOT EMPTYP :NO

[OUTPUT :NO] IF AND :CODE=12 NOT EMPTYP :NO

[MAKE "NO BUTLAST :NO] IF AND :CODE>13 COUNT :NO<2 [MAKE "NO WORD :NO :DIG] OUTPUT GETNUM :X :Y :NO

END

Может быть Вам будет интересно набрать эту процедуру и проследить этапы отладки. Первое тестирование процедуры выполнялось командой:

PRINT GETNUM 10 10 "

Все прошло нормально и мигающий вопросительный знак был напечатан в позиции с координатами [10 10]. Но при попытке нажать клавишу 3 появилось сообщение об ошибке:

5 is not TRUE or FALSE in GETNUM

(5 не имеет значения "ИСТИНА" или "ЛОЖЬ" в процедуре GETNUM). Единственно, где в процедуре GETNUM могут участвовать логические значения TRUE или FALSE - это три строки IF... Неясно только, какая же из трех работает неправильно.

Для выяснения этого вопроса в процедуру GETNUM были встроены три отладочные строки печати после каждой из строк IF.

PRINT "I1

PRINT "I2

PRINT "I3

После этого вновь была нажата клавиша "3" и был получен следующий результат:

11

12

5 is not TRUE or FALSE in GETNUM

Теперь стало ясно, что первые две строки IF проходят нормально и неприятности происходят в третьей строке. Три вспомогательные отладочные строки PRINT были удалены, но перед последним IF были поставлены две новые контрольные строки:

PRINT :CODE

PRINT COUNT :NO

Процедура была запущена еще раз и вновь нажата клавиша "3". Результат был таким:

51

0

5 is not TRUE or FALSE in GETNUM

Итак, 51 - это код символа "3", а 0 - это количество символов в переменной NO (в этот момент времени). Так откуда же появилось число 5? Для проверки были опять изменены две строки контрольной печати.

PRINT :CODE>13

PRINT COUNT :NO<2

Вторая строка дала неожиданный результат:

< does not like as input

Обратим внимание на то, что единственный случай, когда команда "<" может получить пустое слово в качестве входного параметра, может быть только тогда, когда вместо COUNT :NO будет стоять просто :NO. Итак, может быть все дело просто в порядке исполнения операций в этой строке?

Тогда решение будет в использовании круглых скобок так, чтобы сначала выполнялась функция COUNT :NO, а потом ее результат сравнивался бы с числом 2.

IF AND :CODE>13 (COUNT :NO)<2

[MAKE "NO WORD :NO :DIG]

Итак, что же произошло? Сначала почему-то при сравнении пустого слова :NO и числа 2 прошел результат FALSE (видимо, это некорректное сравнение), а потом COUNT FALSE дал число 5 (по количеству символов в слове FALSE), которое конечно не может служить корректным вводом для последующей операции AND.

Теперь процедура работает нормально? Нажав клавиши "3" и "2", мы получим на экране "32" и мигающий знак вопроса. После нажатия ENTER на экране появится число 32, как результат работы команды PRINT. Прочие тесты показывают, что прочие клавиши игнорируются и более двух цифр ввести в число не удается.

Но нам надо еще проверить работу DELETE. Запустим процедуру еще раз. Нажмем клавишу "2". На экране появится:

2?

Теперь нажмем DELETE и на экране появится:

??

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

После исправления и этой ошибки нажатие на цифру "2" дает:

2?

А стирание - DELETE дает:

?

Отлично! Теперь для проверки нажмем на цифру "3" ... и получим новое сообщение об ошибке:

Word doesn't like [] as input in GETNUM

(команда WORD не принимает [] в качестве входного параметра в процедуре GETNUM)

Локализовать эту ошибку несложно, поскольку в процедуре GETNUM команда WORD встречается только один раз. Но сообщение вполне определенно указывает, что в качестве параметра команды WORD оказалось не слово, а пустой список []. Более того, мы обнаружили, что проявляется эта ошибка только после использования DELETE и потому может быть не строка, в которой стоит WORD является причиной ошибки, а та строка, которая реализует операцию DELETE.

Теперь можно поэкспериментировать:

MAKE "NO 2

MAKE "NO BUTLAST :NO

И попробуйте дать команду

PRINT :NO

В качестве результата Вы получите пустую строку, что и следовало ожидать. А теперь попробуйте так:

MAKE "NO WORD :NO 3

и Вы получите сообщение об ошибке такое же, как в предыдущей процедуре. Теперь надо вспомнить об еще одной полезной команде вывода, о которой мы до сих пор не говорили. Мы рассказывали только о PRINT и TYPE, а есть еще команда SHOW. Она отличается тем, что показывает списки вместе с наружными скобками - очень полезная вещь для проверок.

SHOW :NO

дает:

[]

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

Способ обхода этой ошибки был нами продемонстрирован в предыдущей главе и здесь мы не будем повторяться.

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

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

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

Другой метод поиска ошибок - трассирование (трейсинг). Он состоит в прослеживании шаг за шагом того, что делает программа. Этот метод является наиболее важным в тех случаях, когда программа "зависает", т.е. она работает, но не выдает никаких результатов на экран. Возникает впечатление, что она "не работает".

Трейсинг можно делать двумя разными способами. Самый простой - сделать так, чтобы в начале каждой процедуры стоял оператор печати имени этой процедуры. Тогда даже при "зависании" программы Вы сможете увидеть, в какой процедуре или в каких процедурах это "зависание" произошло. Подобный трейсинг делать очень просто, а эффект он дает большой. Когда программа отлажена, то лишние строки печати можно убрать.

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

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

Вы можете написать и свою трассирующую процедуру, назвав ее например TRACE. При создании такой процедуры Вы можете использовать три новых примитива. COPYDEF - выполняет новую копию процедуры

TEXT - конвертирует процедуру в текстовой список ее операторов. С этим списком может работать ЛОГО, как со списком, а не как с исполняемой процедурой.

DEFINE - выполняет обратное действие - конвертирует список инструкций в исполняемую процедуру.

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

TRACE "PROC

А отключается

UNTRACE "PROC

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

TO TRACE :APROC

IF DEFINEDP WORD ". :APROC

[PRINT SENTENCE :APROC [ALREADY TRACED] STOP] IF NOT DEFINEDP :APROC

[PRINT SENTENCE :APROC [NOT A PROCEDURE] STOP] COPYDEF WORD ". :APROC :APROC MAKE :APROC TEXT :APROC

MAKE ".P LIST FIRST THING :APROC SENTENCE "PRINT WORD CHAR 34 :APROC TREST BUTFIRST THING :APROC DEFFINE :APROC :.P

END

TO TREST :ALIST

IF EMPTYP :ALIST [STOP] MAKE ".P LPUT LIST "PRINT

FIRST :ALIST :.P

MAKE ".P LPUT FIRST :ALIST :.P

MAKE ".P LPUT MAKE ". READCHAR :.P

TREST BUTFIRST :ALIST END TO UNTRACE :APROC

IF NO DEFINEDP WORD ". :AFROC [STOP]

COPYDEF :APROC WORD ". :APROC

ERASE WORD ". :APROC

END

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

Стюарт Николс.




СОДЕРЖАНИЕ:


  Оставте Ваш отзыв:

  НИК/ИМЯ
  ПОЧТА (шифруется)
  КОД



Темы: Игры, Программное обеспечение, Пресса, Аппаратное обеспечение, Сеть, Демосцена, Люди, Программирование

Похожие статьи:
Millennium party - Информация о проведении Минского демопати.
Club IM2 - Терминология и теоритические аспекты прерываний и многозадачности.
Двигатель торговли - Пpодается пpинтеp МС 6337. Hедоpого.

В этот день...   4 июля