QSL (Quick Script Language) это просто небольшой диалект языка PERL, немного более близкий к языку «С», чем сам PERL. Интерпретатор QSL был сделан из интерпретатора PERL, для использования в составе программного пакета SOROS. Последний предназначен для построения и отладки систем автоматизации эксперимента на основе персонального компьютера. Взять пакет SOROS, включающий QSL, можно по этой ссылке: http://homelab.atspace.com/soros.html. QSL оптимизирован для манипулирования данными в среде операционной системы Microsoft Windows. QSL, также как и PERL, является легким, не жестким языком программирования. Строгих правил и деклараций переменных нет. Писать программы (скрипты) на нем можно так, как вам больше нравится, корректируя их по результатам их работы или по выдаваемым сообщениям об ошибках. Приводимые в данной статье небольшие примеры, являются только возможными вариантами использования конструкций языка. Полное описание языка QSL вы можете найти в файле qsl.hlp. Интерпретатор QSL независим от периферийных устройств компьютера, команды языка используют доступ к устройствам и файлам, предоставляемый операционной системой. Такими устройствами могут являться, например, параллельный порт, последовательный порт и т. п. QSL поддерживает только текстовую консоль, драйверы устройств должны быть внешними.
Создавать скрипты для QSL можно с помощью любого текстового редактора. Договоримся использовать для файлов с QSL скриптами расширение «.qsl». Для того чтобы было удобно вызывать скрипт на выполнение можно поместить qsl.exe в какую-нибудь директорию, например, Program Files. Кликнув первый раз по файлу с расширением «.qsl», можно задать вызов qsl.exe для всех файлов с расширением «.qsl» (с помощью кнопки «Other…» в возникшем диалоге «Open With»).
Сразу возникает вопрос — для чего это нужно? Прежде всего, консоль используется для отлаживания скриптов, для вывода промежуточных результатов. Консоль может использоваться также для демонстрации результатов измерений и информирования о ходе экспериментальных процессов. Хотя консоль текстовая, не графическая, но может быть использована для вывода простейших графиков и диаграмм. Кроме того, не следует забывать, что язык QSL может быть использован для проведения вычислений «на ходу, на скорую руку». Результаты таких вычислений тоже могут быть выведены на консоль, как, например, в скрипте Lesson 1.
# Lesson 1
$> = 0x10 | 0х0е;
print "\t\t", "Number", "\t\t", "Square root of number\n";
for ($i =1; $i < 10; $i++) {
sleep 500;
$> = 7; print "\n\n\t\t";
$> = 10; print $i;
$> = 7; print "\t\t";
$> = 12; print sqrt($i);
}
warn " ";
Рассмотрим использованные в примере элементы языка QSL (также учтите сноску внизу статьи).
Комментарии, то есть то, что не подлежит выполнению, показываются символом #. Действие этого символа простирается до конца строки. Символ; разделяет команды скрипта. Пробелы в командах и операторах игнорируются. Все команды и операторы должны быть в нижнем регистре (маленькие, прописные буквы).
QSL интерпретатор не держит консоль открытой, консоль вызывается только при необходимости, при завершении скрипта закроется и консоль. Для того чтобы задержать консоль на экране в данном примере используется команда warn. Эта команда выводит указанное сообщение (то, что в парных кавычках, в нашем примере пустое место — пробел) или сообщения в диалоге и задает вопрос «да/нет». Если мы ответим «нет», то скрипт завершается (в нашем примере он в любом случае завершается, поскольку это последняя команда) с кодом «0» (нормальный выход с кодом «1»). Это может быть использовано при вызове QSL из других программ и скриптов (например, из *.bat файлов), но для нашего примера это сейчас неважно.
Команда sleep приостанавливает выполнение скрипта на указанное число миллисекунд. Ее применение в данном примере не особо важно, просто замедляется вывод. В основном она используется для временного согласования вывода и вычислений. Интерпретатор производит вычисления достаточно быстро, тогда как вывод данных наружу зачастую должен осуществляться с определенным интервалом.
Всю работу по выводу делает команда print. Она вызывает консоль и выводит указанный список данных. Элементы списка разделяются запятой. В частном случае может быть просто один элемент. То, что в двойных кавычках выводится как текст (символьная строка) и всегда преобразуется к этому виду. Обратите внимание, что этом примере выводимое первой командой print, можно записать как один элемент, а не список. Символьная строка может включать специальные (управляющие) символы. То, что это управляющий символ, указывается слешем \.
В примере для вывода использовался символ табуляции \t и символ перевода строки \n.
Другим выводимым элементом является содержимое переменной с именем «i». Все переменные имеют предшествующий символ $. Переменные могут иметь любые имена, состоящие из букв, цифр и символа подчеркивания, но первый символ в имени переменной должен быть буквой. Имена переменных зависят от регистра, то есть переменные $Data и $data являются разными. Имена в разных видах хранения независимы, интерпретатор не спутает переменную $days с массивом переменных @days. Переменные в памяти компьютера создаются интерпретатором при первом их упоминании, так что мы не заботимся об их создании. Одни и те же переменные могут содержать числа и/или текст. Представление содержания переменных в нужном формате, при выводе, берет на себя интерпретатор. Если нам что-то не нравится в выводе, то нужно использовать команду форматированного вывода printf, хорошо знакомую программирующим на языке «С».
Особой группой переменных являются системные переменные, они уже существуют до выполнения первой команды, содержат данные (которые по умолчанию) и их имена зарезервированы. В данном примере была использована системная переменная, хранящая цвет и фон выводимых символов $>.
Запись в переменные осуществляется с помощью операции =. Обратите внимание, что в первой записи в переменную $>, участвовали два числа, в шестнадцатеричном представлении, объединенные в одно с помощью битовой операции «или»: |. А в дальнейшем записывалось десятичное представление кодов цвета — это кому, что нравится.
Последняя команда print выводит значение, возвращаемое функцией квадратного корня из числа sqrt(). Вообще говоря, можно писать print($i) и sqrt $i, это кто как привык. То же самое можно сказать об использовании подавляющего большинства команд.
В QSL последовательность (группа) команд может быть интерпретирована как одна команда (блок), если эта группа команд заключена в фигурные скобки. Блок может иметь присваиваемое имя, в этом случае он называется подпрограммой. В данном примере подпрограмм нет, но есть блок. В частном случае блок или подпрограмма может содержать только одну команду.
Еще в этом примере есть оператор циклического повторения в форме for(EXPR1;EXPR2;EXPR3) BLOCK. В переводе на обычный язык это означает: выполнять блок BLOCK с начальным значением переменной из выражения EXPR1 до тех пор, пока будет верным выражение EXPR2, после каждого выполнения блока менять содержание переменной в соответствии с выражением EXPR3 (++ означает добавление единицы). Что интерпретатор и делал.
Скрипт Lesson 2 выводит в файл rand.txt 1000 случайных чисел в диапазоне от 0 до 100. Заодно вы можете проверить насколько распределение псевдослучайных чисел, генерируемых в QSL, соответствует равномерному распределению.
# Lesson 2
$file = "rand.txt";
srand;
open (DATA, "»\\temp\\$file") || die """";
for($i=0; $i<1000; $i++) {
print DATA $i, "\t", int(rand(100)), "\r\n";
}
close DATA;
До выполнения работы по выводу, скрипт осуществляет некоторые подготовительные мероприятия. Первым делом он записывает название файла в переменную $file. Это удобно тем, что в реальном скрипте название файла может использоваться несколько раз, если использовать вместо названия переменную с ним, то, в случае необходимости, смену названия можно сделать только в одном месте по тексту скрипта. QSL включает в себя также ряд средств для взаимодействия с пользователем — название файла можно вводить и в диалогах вызываемых командами ask, аореn.
Второе мероприятие заключается в выборе начального значения псевдослучайной последовательности чисел командой srand. Эта команда без аргумента, в качестве его использует значение возвращаемое командой time, то есть значение счетчика секунд с момента начала компьютерной эры (1 января 1970 года, ровно в полночь).
Вывод данных в файл осуществляет та же самая команда print, но с одним существенным дополнением. Для того чтобы сделать вывод в какой-нибудь файл или устройство, нужно указывать дескриптор для этого файла или устройства. Обратите внимание, что в команде print дескриптор указан сразу после имени команды и не отделяется запятой (не входит в список). Дескриптор файла на самом деле содержит всю необходимую информацию о файле или устройстве и должен быть предварительно создан командой open. В QSL заложен только один постоянный дескриптор CONSOLE, он просто используется командой print по умолчанию, поэтому не указывается, но дескриптор по умолчанию может быть изменен.
Рассмотрим использование команды open в этом примере. DATA — это имя, выбранное для дескриптора. Рекомендуется использовать для имен дескрипторов буквы верхнего регистра, это улучшает читабельность скриптов и предотвращает конфликты с зарезервированными словами (именами команд). Через запятую указано имя файла, который нужно связать с дескриптором. Несколько деталей относительно имени файла в данном примере. Имя файла дано в двойных кавычках. Двойные кавычки, в отличие от одинарных ('), являются интерпретируемыми, то есть переменная $file будет заменена ее содержимым. Поскольку слеш \ задействован для управляющих символов, то для разделения директорий на пути к файлу используется \\, который преобразуется при выполнении к нормальному разделителю. Таким образом, в команде open был дан полный путь к файлу \temp\rand.txt. Конечно, директория temp в корне диска уже существовала. Если бы имя файла было дано только так, то это бы означало, что файл будет открыт только для чтения. Для того чтобы указать, что файл должен быть открыт для записи путем добавления данных в конец файла, к началу имени файла было добавлено >>, показывающее направление движения данных. Таково правило.
Каждый созданный дескриптор должен быть закрыт, то есть убран из памяти компьютера, по крайней мере до завершения скрипта. Делается это командой close. Конечно, интерпретатор сам заботится о закрытии всего открытого, но это очень хорошая привычка на будущее.
Мрачная команда die всего лишь означает выход из скрипта с кодом 0. При этом весь список сообщений, указанный в команде будет дан в диалоге. Эта команда в примере подсоединяется с помощью логической операции «или» ||. То есть на самом деле результирующая команда звучит так: «Открой для добавления файл rand.txt или, если не сможешь, вываливайся из скрипта…». Можно, например, указать die "Can not open file $file", но если ничего не давать в двойных кавычках, то в диалоге будет сообщена причина (сообщение о системной ошибке), взятая из системной переменной $! что является более информативным.
Функция rand(100) возвращает псевдослучайное число в диапазоне от 0 до 100, а функция int(), дополнительно берет от него только целую часть для вывода.
В нашем примере каждая строка вывода завершается "\r\n" (возврат каретки, перевод строки). Причем именно в таком порядке. Так было принято в MS DOS для файлового вывода и продолжает действовать в MS Windows. Это нужно запомнить.
Поскольку, изучив предыдущие примеры, вы уже можете считаться продвинутым программистом, то этот пример немного усложним, добавив обратную сортировку на пути между чтением данных из файла и выводом их на принтер.
# Lesson 3
$file = \\tempWrand.txt;
sub reverse {
$a < $b? 1: $a > $b? — 1: 0;
}
########### INPUT ###########
open(DATA, $file) || die ""
while () {
chop;
($item, $num) = split;
push(0data, dec($num));
}
close DATA;
#### SORT ####
@sdata = sort reverse @data;
########## OUTPUT ###########
open(LPT, ">LPT1") || die "Check out printer!";
foreach(@sdata) {
print LPT $_, "\n";
}
close LPT;
Сортировка данных осуществляется командой sort. Эта команда действует на массивы переменных, то есть несколько переменных, оперирируемых как одно целое. Данная команда возвращает отсортированный массив, не затрагивая исходный. Имена массивов переменных предшествуются префиксом @, вместо $. С командой sort связана одна маленькая проблема — она сортирует числа как текст, то есть в таком порядке 0, 1, 10, 11, 12…19, 2, 20, 21…. С этим явлением вы наверное не раз встречались в MS Windows. В файле же rand.txt у нас хранятся числа, в текстовом виде, но все-таки числа. Для решения таких проблем команда sort имеет дополнительную возможность — она может использовать для сравнения пары данных внешнюю подпрограмму, имя которой дается сразу после имени команды. Сама подпрограмма sub reverse, в данном примере, находится в начале скрипта. Команда sort передает сравниваемые данные в подпрограмму как две переменных $а и $Ь, а не как массив переменных @_, это исключение нужно запомнить. Подпрограмма сравнения должна вернуть 1 если данное из $а нужно поместить дальше данного из переменной $Ь, если наоборот то -1, и 0 — если и так сойдет. Естественно, что в подпрограмму сравнения должны поступать числа, а не текст и сравниваться должны именно числа.
В данном примере подпрограмма сравнения обеспечивает именно обратную сортировку — меньшие числа продвигаются вперед. Само сравнение осуществляется с помощью пары условных операторов?. Условный оператор выполняет одно из двух действий в зависимости от того верно или нет утверждение до?. Его общий формат выглядит так test? expressionl: expression2. В качестве test может быть любое логическое выражение. Результат выполнения expressionl возвращается, если утверждение в test верно. В противном случае возвращается результат выполнения expression2.
Рассмотрим ввод данных из файла. В данном примере используется несколько упрощений для наиболее часто используемых действий, аналогично тому, как в делается в языке «С» (вместо х = х + n, используется х += n; вместо х = х + 1 используется просто х++ и т. п.), только QSL в этом направлении идет еще дальше. Конечно язык QSL, так же как и «С», содержит и стандартные конструкции, скрипты можно писать и на их основе.
Для ввода данных из файла в примере используется не команда, а оператор чтения < >. При его каждом вызове он возвращает символьную строку. Возвращаемая строка должна быть записана в какую-нибудь переменную, но в случае использования оператора чтения в операторе повторения блока while, и только тогда, оператор присваивает возвращаемое в системную переменную $_. Оператор while повторяет выполнение блока пока условие в круглых скобках верно, в данном случае до конца файла («Читай файл, пока считывается»).
Оператор < > считывает все символы строки из файла до \n (код 10), как это принято в UNIX и встречается под MS Windows. Следовательно, оставшийся в конце строки \r (код 13) является для нас лишним. Для его удаления использовалась команда chop, по умолчанию удаляющая последний символ, в переменной $_.
Каждая строка, данного файла имеет вначале номер строки, он нам не нужен. Выделить нужные данные можно с помощью команды разбиения строки split. Эта команда разделяет строку на составляющие, по умолчанию из переменной $_ и используя пробелы и табуляции как разделители. Возвращаемые командой данные должны быть записаны в массив переменных. Такой, но самодельный, мы ей и подсунули (в круглых скобках), хотя конечно имени у него никакого нет, но нам оно, в дальнейшем, и не нужно.
То, что нам нужно будет в переменной $num, но это будет текст, не число. Превратить текст в число можно, например, с помощью функции dec(), которая работает только с десятичной записью числа (для октальных и шестнадцатеричных записей есть другие функции).
Полученное число добавляется в массив переменных @data с помощью команды push.
Осталось отсортировать массив @data и записать отсортированный в массив @sdata, что и было сделано с помощью команды sort.
Переходим к печати на принтере. Для этого должен быть открыт как файл, параллельный порт, к которому подключен принтер. Надеюсь, у вас тоже такой есть. Если принтер у вас сетевой, то этот пример вы не сможете использовать и можете смело переходить к следующему разделу, который посвящен средствам системы SOROS, включающим и возможность работы с сетевыми принтерами. Собственно говоря, QSL знает только последовательные порты, например СОМ1, COM2 и параллельные, например LPT1, но управлять он ими не умеет, это является функцией операционной системы. Для полного доступа к этим портам QSL должен использовать внешние программы, например средства системы SOROS.
Если бы мы использовали для вывода конструкцию print @sdata, то получили бы все числа напечатанные подряд. Чтобы вывести числа в столбик, в данном примере, был использован оператор повторения блока foreach. Этот оператор выполняет блок для каждой переменной массива, содержимое которой присваивается какой-либо, упоминаемой после имени оператора, но по умолчанию, как в нашем примере, присваивается системной переменной $_. Остается только вывести ее на принтер, снабдив символом перевода строки.
В первую очередь речь идет о взаимодействии со средствами пакета SOROS. Основу пакета составляют несколько мини-серверов осуществляющих операции чтения-записи устройств компьютера (параллельные и последовательные порты, все звуковые устройства) и обменивающихся данными с другими модулями пакета SOROS (soros.exe).
Запуск мини-серверов осуществляется нажатием кнопок на модуле DEVICE. Для каждого номера порта или звуковой карты запускается своя копия мини-сервера. Одновременно может быть запущено несколько мини-серверов. На рисунке для примера запущен сервер, оперирующий первым параллельным портом. Контрольно-измерительные модули PARALLEL (параллельный порт), SERIAL (последовательный порт) и INPUT (звуковая карта) могут подключаться к нужной копии минисервера. Для этого они имеют переключатель номера порта/карты (над кнопкой Read). На рисунке модуль PARALLEL подключен к серверу LPT1. Чтение устройства осуществляется кнопкой Read. Для каждого доступного бита есть своя кнопка установки, но запись установленных битов в порт осуществляется кнопкой Set.
На рисунке можно видеть, после чтения, что в порте LPT1, на 1, 5, 8, 10, 13 и 15 выводах разъема установлена логическая единица, а на остальных, соответственно, ноль.
Для запуска осциллографа INPUT нужно не только выбрать номер карты (соответствующий мини-сервер должен быть запущен), но и длительность развертки (время чтения) по оси X. Соответствующие переключатели выбирают усиление и положение на оси Y. Кнопка Stat на модуле INPUT переключает осциллограф в режим показа гистограммы входного сигнала.
Сброс системы SOROS в исходное состояние осуществляется кнопкой Init на контроллере крейта CRATE. Кнопка Printer, там же, позволяет запустить минисервер для принтера. На окне этого сервера можно выбрать нужный принтер, подключится к нему, и выбрать имя для сервера. Подразумевается, что будет использован сетевой принтер, поскольку параллельный порт (порты) необходимы для подключения контрольно-измерительных устройств. Переключатель Strob запускает режим циклического чтения (когда не 0 и кликнута кнопка Read). Чем больше значение на переключателе, тем реже производится чтение (делитель частоты стробового импульса).
При нажатой кнопке Setup можно подшить какой-либо скрипт, написанный на языке QSL, к кнопке на модуле PROGRAM, при ее нажатии конечно. Когда Setup отжата, кнопки модуля PROGRAM могут запускать скрипты на выполнение. Любой QSL скрипт можно также выбрать и выполнить с помощью кнопки Run.
К мини-серверам пакета SOROS может подключаться и интерпретатор QSL. Для взаимодействия с серверами используется «клиент-сервер» модель и DDE. QSL всегда является клиентом с именем «QSL». Сервер может быть открыт как файл, командой open. Имена внешних программ (драйверов) должны предваряться символом #. Сами имена мини-серверов уже заложены внутри их программ, но входящий в них номер порта/карты выбирается при запуске с панели системы SOROS. К некоторым устройствам, таким как аудио и видео периферия, командные последовательности могут быть посланы через MCI (смотрите документацию на QSL и MCI). К настоящему моменту в пакет входит всего 4 сервера: принтер, параллельный порт, последовательный порт и звуковая карта, но каждый желающий может изменить их или добавить другие. Все источники предоставляются.
Далее следуют примеры программирования скриптов для работы с минисерверами.
# Lesson 4
open (PRN, ">#printer) || die "";
print PRN "Nice weather today, isn't it?\n";
close PRN;
Надо полагать, что в этом примере все уже знакомо. Следует учесть только, что до его выполнения соответствующий сервер должен быть запущен и настроен. Тоже самое относится и к последующим примерам.
В действительности QSL не имеет каких-либо специальных команд для контроля драйверов. Одним из возможных решений этой проблемы может являться использование управляющих символов в выводимых данных и использование отрицательного размера буфера для ввода в стандартных командах ввода-вывода QSL.
# Lesson 5
open(LPT, "+#LPT1") || die "";
$reg = " ";
read(LPT, $reg, — 2); # read status and control registers
($sr, $cr) = unpack("CC", $reg);
printf "%x\n%x", $sr, $cr;
$cr |= 0x20; # bi-direct
$reg = pack("CC", 0x01, $cr); # SOH + control byte
print LPT $reg;
print LPT "Hello, world!\r";
sleep 3000;
close LPT;
warn " ";
Обратите внимание, что в команде чтения read размер буфера дан отрицательным, это является просьбой к серверу вернуть значения контрольного регистра и регистра чтения (два байта). Естественно, что сервер понимает это (смотрите программу этого сервера). Поскольку с сервера будет получена текстовая строка, переменная $reg была сделана текстовой, путем записи в нее пары пробелов.
Для передачи значения регистров упаковываются в текстовую строку, команда unpack распаковывает ее ($reg) на два байта без знака (код С) и помещает, в соответствующие названиям регистров, переменные. Последующий printf, просто для наглядности, выводит значения этих регистров на консоль, в шестнадцатеричном формате. Далее, после нужного изменения содержимого регистров, они снова упаковываются в текстовую строку командой pack и отсылаются на сервер командой print. Параллельный порт готов к работе, если там имеется принтер, то можно отпечатать что-нибудь, но на самом деле этот порт необходим для управления контрольно-измерительными устройствами, подключаемыми через него, а принтер можно использовать и сетевой.
Знак + в начале имени файла означает, что файл нужен для чтения и записи. Операция |= (битовое «или»), просто сокращенная форма конструкции $сг = $сг | 0x20.
Последовательный порт (RS-232) тоже может быть использован для управления некоторыми устройствами, тем более что некоторые современные измерительные приборы имеют встроенный интерфейс для него. В нижеследующем примере использован другой подход к проблеме доступа к регистрам порта. В этом подходе первые 2 байта ввода/вывода содержат данные контрольного регистра, другие 2 байта — получаемые/посылаемые портом данные.
#Lesson 6
open(СОМ, "+#СОМ1") || die "";
$sr =2; # -1 for serial register and -1 or 0 for data means do not set/send
$sr <<= 16; # first 2 bytes are register, second 2 bytes are data
# actually for setting/sending is used only second byte in pair
print COM $sr; # DTR is set, it works like printf(COM "%d", $sr);
$str = "";
read(COM, $str, 12); # read serial register and data as decimal string
$sr = dec($str) >> 16; # converts string to bytes and gets register
# first 2 bytes are register, second 2 bytes are data
printf "0x%x", $sr;
close COM;
warn " ";
Операции << и >> производят сдвиг битов числа, соответственно влево и вправо на указанное количество позиций, добавляя на освободившиеся места нули. Каждый сдвиг это, по сути, умножение (влево) или деление (вправо) на 2.
Нижеследующий скрипт считывает 160 байтов из звуковой карты и отправляет на нее, через мини-сервер, тоже 160, случайных байтов. Этого достаточно, чтобы услышать скрип в динамике.
#Lesson 7
open(AUDIO, "+#AUD1") || die "";
$str = "";
read(AUDIO, $str, 160);
@x = unpack("Cl60", $str);
printf "0x%x", $x[10];
srand;
for($idx =0; $idx < 160; $idx++) {
$x[$idx] += rand(50) — rand(50);
}
$str = pack("C160", 0x);
write(AUDIO, $str, 160);
close AUDIO;
warn " ";
В этом примере для вывода используется команда write, обратная по смыслу команде read, но аналогичная по формату.
Обратите внимание, на использование переменной $х в команде printf. Это означает 10 порядковая переменная (или 11 по счету, поскольку в QSL индексы считаются с нуля) массива переменных @х, то есть @х можно записать как $х[].
В блоке, далее, это и используется.
Прим. ред.: Во всех примерах, перед исполнением замените кавычки на нормальные, поскольку те, что присутствуют, не всегда соответствуют требуемым, некоторые были заменены MS Word.