Ошибка трансляции непарные скобки

Эти
действия осуществляются в среде
программирования QBASIC.
Для входа в главное меню необходимо
нажать клавишу ALT.
Затем нажать подсвеченную букву пункта
меню и выбрать нужный пункт под меню.
Если пункт подменю оканчивается
троеточием «…», что при выборе
этого пункта возникнет диалоговое окно.

Главное
меню включает пункты «ФАЙЛ, РЕДАКТИРОВАНИЕ,
ПРОСМОТР, ПОИСК, ЗАПУСК, ОТЛАДКА,
ПАРАМЕТРЫ, СПРАВКА». Рассмотрим содержание
пунктов меню:

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

При
редактировании удаление текста слева
от курсора производится клавишей
Backspace
(),
а справа — клавишей Del.
Чтобы копировать, вырезать, удалить
текст, его предварительно необходимо
выделить. Выделяется текст с помощью
комбинации клавиши Shift
+ клавиши со стрелками или с нажатой
левой клавишей мыши. Приемы ввода текста
мало отличаются от работы в других
редакторах. Наиболее применяемые команды
меню дублируются комбинациями клавиш
(см. таблицу 0.0.5).

Среда
программирования QBASIC
позволяет запустить программу на
выполнение. Если появилось сообщение
об ошибке, и она понятна, следует щелкнуть
по кнопке OK,
при необходимости уточнений выбрать
Справку
или обратиться к таблице 0.0.6.

Вот
некоторые приемы, применяемые при
отладке программ. Например, если программа
по 2-й лабораторной работе дает неправильные
значения, то это может быть по двум
причинам: либо неправильно реализованы
формулы, либо не реализовано ветвление
и программа считает не по той формуле.
Чтобы проверить второй вариант, нужно
воспользоваться клавишей F8,
высвечивающей траекторию движения по
программе. При этом строки с оператором
IF…THEN
следует сделать многооператорными,
поставив в конце пустой PRINT.
Если какой-либо оператор мешает
разобраться в причинах появления ошибки,
то его можно вывести из рассмотрения,
не стирая, поставив перед ним оператор
REM.
А чтобы вывести из рассмотрения большой
фрагмент программы, следует использовать
GOTO
с указанием номера строки, куда следует
перепрыгнуть. Чтобы разобраться с
циклом, например, выяснить, правильно
ли считается сумма, можно в него вставить
оператор PRINT,
поставив за ним SLEEP,
организующий паузу до нажатия любой
клавиши. Впрочем, с целью создания паузы
можно применять и просто SLEEP.
Проверить, работает ли цикл или иной
фрагмент программы, можно, использовав
PRINT
«Я здесь».

Таблица 0.0.5.

Назначение
функциональных клавиш в среде
QBASIC

Клавиши

Назначение

F1

Справка
по ключевому слову, функции или
оператору, отмеченному курсором

Shift+F1

Вывод
на дисплей оглавления справочной
информации

F2

Вывод
на экран списка имен всех задействованных
в программе процедур и функций, а также
самой программы

Sfift+F2

Вывод
на экран следующей процедуры или
функции

Ctrl+F2

Вывод
на экран предыдущей процедуры или
функции

F3

Повтор
поиска по ключевому слову

F4

Переход
к экрану вывода и обратно

F5

Продолжение
работы по программе

Shift+F5

Запуск
программы

F6

Переброс
курсора из окна ввода программы в окно
непосредственного счета и обратно

Shift+F6

Переброс
курсора из одной створки окна
редактирования в другую и обратно

F7

Выполнение
программы до курсора

F8

Пошаговое
выполнение программы с заходом в
процедуры и функции

F9

Установка
или снятие контрольной точки в программе

F10

Пошаговое
выполнение программы без захода в
процедуры и функции

Shift+
+клавиши
со
стрелками

Выделение
фрагмента программы

Shift
+Del

Вырезание
фрагмента программы

Ctrl
+Y

Вырезание
строки программы

Shift
+Ins

Вставка
в программу ранее вырезанного фрагмента

Ctrl
+Ins

Копирование
выделенного фрагмента программы

Ctrl
+ Shift

Русский
шрифт (правые), английский (левые)

Ctrl +Break

Приостановка
выполнения программы

Примечание:

Комбинация
клавиш, например, сначала нажать Ctrl,

затем, не отпуская Ctrl,
нажать Shift

Таблица 0.0.6.

Код

Сообщение об
ошибке и возможная причина

1

NEXT
без
FOR
(NEXT
without FOR)

Для
окончания цикла NEXT
нет соответствующего заголовка FOR.
Количество FOR
и NEXT
должны совпадать

2

Синтаксическая
ошибка
(Syntax error)

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

3

RETURN
без
GOSUB
(RETURN
without GOSUB)

Для
оператора возврата из подпрограммы
RETURN
нет соответствующего обращения к
подпрограмме GOSUB

4

Нет
данных
(Out of DATA)

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

5

Неверный
вызов
функции
(Illegal function call)

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

6

Переполнение
(Overflow)

Числовая
переменная или строковая константа
выходят за пределы допустимого
диапазона (например, в знаменателе
получается очень малая величина или
при работе с возведением в степень).
Проверьте и измените значение при
необходимости

7

Не
хватает
памяти
(Out of memory)

8

Метка
не определена
(Label not defined)

Для
операторов GOTO
или GOSUB
задается переход на несуществующую
метку

9

Индекс
вне
диапазона
(Subscript out of range)

Сообщение
возникает при работе с массивами,
когда индекс какого- либо элемента
массива превышает его объявленный в
операторе DIM
размер, а также в том случае, когда
массив занимает в памяти объем более
64 Кбайт. Появляется также, если в
формуле, оперирующей с элементами
массива, они заменены другими переменными
(x(i)
заменен на просто x).

10

Повторяющееся
определение
(Duplicate definition)

Может
возникнуть, если элемент массива,
объявленного в операторе DIM,
фигурирует далее (в формуле или
выражении) в несвязанном или неправильно
связанном виде

Продолжение таблицы
0.0.6.

11

Деление
на ноль
(Division of zero)

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

12

Ошибка в режиме
управления

13

Ошибка ввода

14

В
строке нет места
(Out
of
string
space)

16

Слишком
сложная строковая формула

(String
formula too complex)

17

Невозможно
продолжить

18

Функция
не определена
(Function not defined)

Возможно,
используемая функция не определена
опера-тором DEF
FN,
или допущена ошибка при определении
или вызове функции

19

Нет
RESUME
(No
RESUME)

20

RESUME
без
ошибки
(RESUME
without error)

24

Устройство
в тайм-ауте
(Device timeout)

25

Ошибка
устройства
(Device fault)

26

FOR
без
NEXT (FOR
without NEXT)

Для
заголовка цикла FOR
нет соответствующего окончания цикла
NEXT.
Количество FOR
и NEXT
должны совпадать

27

Нет
бумаги
(Out of paper)

29

WHILE
без
WEND
(WHILE without WHILE)

Для
ключевого слова WHILE
нет соответствующего слова WEND

30

WEND
без
WHILE
(WEND without WHILE)

Для
ключевого слова WEND
нет соответствующего слова WHILE

33

Повторяющаяся
метка
(Duplicate label)

При
расстановке меток допущен повтор
одной и той же метки в разных местах
программы. Обычно возникает при
редактировании текста программы
копированием

35

Подпрограмма не
определена

Сообщение
возникает при попытке обращения к
несуществующей подпрограмме

37

Ошибка счетчика
аргументов

38

Массив не определен

Попытка
работать с элементами массива, который
не был объявлен оператором DIM

39

Требуется
CASE ELSE
(CASE ELSE expected)

40

Необходима
переменная
(Variable required)

Возникает
при попытке записи иных элементов
программы в том месте, где должна быть
переменная (попытка заменить x
русской буквой x)

Продолжение таблицы
0.0.6.

50

Переполнение
FIELD
(FIELD overflow)

51

Внутренняя
ошибка
(Internal
error)

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

52

Плохое
имя файла / плохой номер

(Bad
file name or number)

Имя
файла не соответствует требованиям
DOS
(например, не указан путь для файла не
из текущего каталога)

53

Файл
не
найден
(File not found)

При
попытке обращения к файлу неправильно
указано его имя или путь к нему

54

Плохой
режим файла
(Bad file mod)

Возникает,
если файл создан в редакторе не
совместном с редактором, используемым
в настоящем случае

55

Файл
уже
открыт
(File already open)

Попытка
повторного открытия файла или удаления
открытого файла

56

Оператор
FIELD активен
(FIELD statement activ)

57

Ошибка
в/вв устройства
(Device I/O error)

Ошибка
устройства ввода/вывода, с которой не
справляется DOS.
Попробуйте посмотреть, все ли в порядке
с аппаратной частью, т.е. внешними
устройства компьютера

58

Файл
уже существует
(File already exists)

Попытка
сохранить файл под именем уже
существующего на диске файла

59

Неверная
длина записи
(Bad record length)

61

Диск
заполнен
(Disk full)

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

62

Ошибка:
введен конец файла
(Input
past end of file)

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

63

Неверный
номер
записи
(Bad record number)

64

Плохое
имя
файла
(Bad file name)

Имя
файла не соответствует требованиям
DOS

67

Слишком
много файлов

(Too
many
files)

68

Устройство
недоступно

(Device
unavailable)

В
дисководе нет диска или он испорчен

Окончание таблицы
0.0.6.

69

Переполнение
буфера коммуникации

(Communication-buffer
overflow)

Попытка
копирования в буфер слишком большого
объема информации

70

Нет
разрешения

(Permission
denied)

71

Ошибка
формата диска

(Disk
not
ready)

Открыта
защелка дисковода, в дисководе нет
диска или он испорчен.

72

Ошибка
диска
(Disk-media error)

В
дисководе нет диска или он испорчен

73

Недоступная
возможность
(Advanced
feature unavailable)

74

Переименование
через диски
(Rename
across
disks)

75

Ошибка
доступа к пути / файлу
(Path
/ File
access
error)

76

Путь
не найден
(Path
not
found)

При
попытке обращения к файлу неправильно
указано его имя или путь к нему

Пишем простой транслятор на Лиспе — I +17

Lisp


Рекомендация: подборка платных и бесплатных курсов создания сайтов — https://katalog-kursov.ru/

Давайте попробуем написать на Лиспе… транслятор простого императивного языка. Нет-нет, я не ошибся – именно транслятор. Транслировать он будет в Лисп-код. А дальше этот код может быть выполнен Лисп-системой.

Здесь бесценную услугу нам окажет то обстоятельство, что в Лиспе нет барьера между кодом и данными (это редкое свойство некоторых языков программирования называется “гомоиконность”). Но и изобразительные возможности Лиспа тоже сыграют не последнюю роль.

В качестве реализации я буду использовать HomeLisp. Желающие смогут адаптировать этот проект под Common Lisp. Скажу сразу – применительно к рассматриваемой задаче существенная разница между Common Lisp и HomeLisp состоит только в обработке строк и файлов.

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

Предлагаемая вашему вниманию тема послужила основой для моей мастерской на знаменитой Новосибирской ЛШЮП-2018. С результатами мастерской можно ознакомиться здесь. А далее я излагаю свой подход. Предполагаю, что читатель знаком с языком Лисп.

Приступаем

Начнем с “простого императивного языка”, который мы будем транслировать в Лисп.
Язык будет обрабатывать только числовые данные. Код на этом языке состоит из функций (процедур, возвращающих значения). Среди этих функций одна должна называться main. Именно с этой функции начинается выполнение кода. Хотя зачем так себя связывать? Пишем функции на императивном языке, они транслируются в Лисп и могут использоваться вместе с лисповскими функциями. Но не будем забегать вперед…

Набор операторов языка обычен: присвоение, ветвление, арифметический цикл, досрочный выход из цикла, ввод, вывод и вызов функции. Впрочем, синтаксически вызов функции оформляется как присвоение (результата вызова). Комментарии пусть содержат звездочку в первой позиции строки. Язык, разумеется, должен обеспечивать возможность создания рекурсивных функций. Чтобы стало понятнее, приведу примеры кода – печать последовательных нечетных чисел и вычисление их суммы:

proc main()
  local s,n,k
  input n
  for i=1 to n
      k=2*i-1
      print k
      s=s+k
  end_for
  print s  
end_proc

По своему духу – это бэйсик-подобный язык. Я буду называть его “мини-бэйсик”. Наш транслятор должен преобразовать приведенный код в следующую Лисп-функцию:

(defun main nil
   (let ((s 0) (n 0) (k 0))
     (setq n (read))
     (iter (for i from 1 to n)
       (setq k (- (* 2 i) 1))
       (printline k)
       (setq s (+ s k)))
   (printline s)))

Мне очень нравится пакет iterate, который в профессиональных пакетах Common Lisp реализован в виде макро. В HomeLisp функция iter (реализующая значительную часть возможностей макро iterate) включена в ядро языка. Мое пристрастие к iter и послужило причиной того, что циклы нашего “мини-бэйсика” будут транслироваться в вызовы iter.

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

(defun start (&optional (fname nil))
   (setq *numline* 0)
   (setq *flagerr* nil)
   (when (null fname)
         (setq fname (sysGetOpenName (sysHome) "Мини-бэйсик|*.mbs")))
   (let ((fi (gensym 'fi)))
        (when fname          
          (filOpen fi fname _INPUT)
          (loop
              (getLine fi) 
              (when (or *flagerr* (filEOF fi)) (return t)))
          (filClose fi)
          (when *flagerr* (printsline "**** Были найдены ошибки"))))
   (unset '*numline*)
   (unset '*flagerr*))

Функция имеет необязательный параметр fname – имя файла, содержимое которого будет транслироваться. При входе в функцию создаются две глобальные переменные numLine номер строки исходного файла и flagerr — флаг состояния ошибки. Перед завершением функции эти переменные уничтожаются (функция HomeLisp-а unset уничтожает глобальные переменные).

Если имя входного файла опущено – то вызывается стандартный windows-диалог выбора файла (sysGetOpenName). В качестве стартовой директории используется текущая директория (sysHome). Далее создается уникальный символ для файлового манипулятора и файл открывается для текстового чтения. Затем в бесконечном цикле производится чтение файла строка за строкой (функция getLine). После каждой операции проверятся, не возникла ли ошибка, и не достигнут ли конец файла. Если возникла ошибка или зафиксирован конец файла – цикл разрывается, файл закрывается, и, если были ошибки – выводится итоговое сообщение.
Собственно чтение из файла выполняет функция getLine:

(defun getLine (fil)
  (let ((stri ""))
    (loop
      (when (filEof fil) (return ""))
      (setq *numline* (add1 *numline*)) 
      (setq stri (filGetline fil))
      (printsline (strCat (format *numline* "0000") " " 
                  (strRTrim stri)))
      (setq stri (strATrim stri)) 
      (unless (or (eq "" stri) (eq "*" (strLeft stri 1))) 
      (return stri)))))

Эта функция принимает идентификатор открытого файла и в бесконечном цикле выполняет следующие действия:

  • проверяет состояние “конец файла”. В этом случае цикл разрывается и функция возвращает пустую строку;
  • увеличивается на единицу счетчик прочитанных строк;
  • читается очередная строка файла;
  • прочитанная строка печатается с удалением возможных пробелов справа;
  • если прочитанная строка непуста и не содержит звездочку в первой позиции, то она
    возвращается из функции;

Таким образом, в выходной листинг попадают все строки файла в их исходном виде.

Разбиваем на процедуры

А теперь давайте научим наш код разбивать входной поток на отдельные процедуры. Сначала введенную строку потребуется разбить на лексемы (неделимые входные лексические единицы). Этот процесс называется парсингом; нам предстоит создать парсер. Написание парсеров — классическая тема, существуют библиотеки готовых парсеров и специальные средства, позволяющие сгенерировать нужный парсер… Мы пойдем своим путем.

Прежде, чем описывать алгоритм парсера, обратим внимание на то, что все символы входной строки можно поделить на два класса:

  • Обычные символы;
  • Символы-разделители.

Так, в операторе присвоения “x = 15 + y^2” символы x,1,5,y и 2 – есть обычные символы, а символы “пробел”, +,^ — разделители. Чем обычный символ отличается от разделителя? Разделитель – всегда отделяет одну лексему от другой. Наш оператор присвоения, будучи разбит на лексемы, выглядит так: “x”, “=”, ”15”, “y”, “^”, “2”.

Как можно заметить, не все разделители попадают в результат парсинга (пробелы, в частности, не попадают). Будем называть разделители, которые не попадают в результат, разделителями первого типа. Остальные разделители будут называться разделителями второго типа.

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

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

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

Вот код парсера:

(defun parser (txt &optional (d1 " ,") (d2 "()+-*/^=<>%"))
  (let ((res nil)
        (lex "") )
   (iter (for s in-string (strCat txt (strLeft d1 1)))
     (cond ((plusp (strInd d1 s))
            (when (> (strLen lex) 0) (collecting lex into res))
                  (setq lex ""))
           ((plusp (strInd d2 s)) 
            (when (> (strLen lex) 0) (collecting lex into res))
                  (collecting s into res)  
                  (setq lex ""))
           (t (setq lex (strCat lex s))))) res))

У функции кроме обязательного параметра имеется два необязательных: d1 содержит строку, каждый символ которой есть разделитель первого типа, а строка d2 содержит разделители второго типа.

Программная логика функции parser описана выше. Следует только отметить, что перед началом работы к концу входной строки присоединяется разделитель. Это сделано для того, чтобы последняя обработанная лексема на “зависла” в аккумуляторе (роль аккумулятора играет локальная переменная lex).

Проверим наш парсер “в деле”:

(parser "x = 15 + y^2")
==> ("x" "=" "15" "+" "y" "^" "2")

Все верно, не так ли? Но работать со списками строк – это не вполне по-лисповски. Давайте от списков строк перейдем к списку атомов. Для этого нам понадобится функция, которая… склеит все лексемы опять в длинную строку (но между лексемами вставит по пробелу), потом к началу этой строки приклеит открывающую скобку, к концу – закрывающую… а затем заставит Лисп прочитать полученный список:

(defun mk-intf (txt)
  (let ((lex (parser txt " ," "()+-*/^=<>%"))
        (intf ""))
   (iter (for a in lex) (setq intf (strCat intf a " ")))
   (input (strCat "(" intf ")"))))

Теперь, если подать на вход функции mk-intf наш оператор присвоения, то получим:

(mk-intf "x = 15 + y^2")
==> (X = 15 + Y ^ 2)

Что, согласитесь, гораздо приятнее.

Теперь немного изменим функцию start: эта функция должна будет читать и обрабатывать целые процедуры:

(defun start (&optional (fname nil))
   (setq *numline* 0)
   (setq *flagerr* nil)
   (when (null fname)
         (setq fname (sysGetOpenName (sysHome) "Мини-бэйсик|*.mbs")))
   (when fname      
      (let ((fi (gensym 'fi)))
           (filOpen fi fname _INPUT)
           (loop
               (let ((curr-proc (action-proc fi)))
                    (when (or *flagerr* (filEOF fi)) (return t))
                    (eval curr-proc)))
           (filClose fi))
           (when *flagerr* (printsline "**** Были найдены ошибки")))
   (unset '*numline*)
   (unset '*flagerr*))

В теле цикла вызывается функция action-proc (обработать процедуру), которая будет формировать тело принятой процедуры уже на Лиспе. Тело процедуры, сохраненное как S-выражение в переменной curr-proc, передается затем на вход eval. И принятая функция “реинкарнируется” в среде Лисп!

Что должна делать action-proc? Эта функция получает в качестве параметра идентификатор открытого файла. Функция читает из файла строку за строкой, пропускает пустые строки и комментарии, остальные строки парсит, переводит в форму списка, и генерирует тело процедуры.

Мы будем постепенно “учить” action-proc генерации. И начнем с того, что научим нашу функцию распознавать начало и конец процедуры. В мини-бэйсике начало процедуры имеет вид:

proc name(p1,p2,p3)

попробуем распарсить такую строку:

(mk-intf "proc name(p1,p2,p3)")
==> (PROC NAME (P1 P2 P3))

Как же должна реагировать на этот ввод функция action-proc? Вполне естественно: убедившись, что голова списка есть атом PROC, нужно взять второй элемент списка как имя функции, а третий элемент – как список параметров. Имя и список параметров следует сохранить в локальных переменных. Когда же прочитывается оператор end_proc, то нужно из имени функции и списка параметров сформировать форму defun с пустым (пока) телом, и вернуть эту форму как результат. Вот как это выглядит:

(defun action-proc (fi)
   (let ((stmt nil)
         (proc-name nil)
         (proc-parm nil))
    (loop
         (setq stmt (mk-intf (getLine fi))) 
         (when (null stmt) (return t))
         (cond ((eq (car stmt) 'proc) 
                    (setq proc-name (nth 1 stmt))
                    (setq proc-parm (nth 2 stmt)))                    
               ((eq (car stmt) 'end_proc) (return t))
               (t (printsline (strCat "**** Оператор " 
                              (output stmt) 
                              " не распознан")) 
                  (setq *flagerr* t))))
    `(defun ,proc-name ,proc-parm (quote OK))))

Для окончательного формирования предложения defun используется обратная блокировка. Обратите внимание на то, что в качестве результата сгенерированная процедура будет выдавать атом OK.

Теперь мы можем проверить наш код в действии. Занесем в файл 0000.mbs следующий код:

proc f1(x,y)
end_proc 
proc f2(x)
end_proc

Запустим процедуру start, выберем 0000.mbs и увидим на консоли:

0001 proc f1(x,y)
0002 end_proc
0003 proc f2(x)
0004 end_proc

При желании можно убедиться, что Лисп-машине теперь доступны две (пока бесполезные) функции f1 и f2:

(getd 'f1)
==> (EXPR (X Y) (QUOTE OK))
(getd 'f2)
==> (EXPR (X) (QUOTE OK))

Более того! Их уже можно и запустить:

(f1 1 2)
==> OK
(f2 2)
==> OK

Наш транслятор сделал первый вдох…

Ввод, вывод и локальные переменные

А сейчас самое время научить наш новорожденный транслятор обрабатывать операторы input, print и local.

Проще всего обработаем ввод и печать. Оба оператора имеют одинаковую синтаксическую структуру: ключевое слово и переменная. Оператор input x должен превратиться в такую Лисп-форму (setq x (read)). Соответственно, оператор print x превращается в форму (printline x). Для хранения этих форм необходимо в функции action-proc предусмотреть локальную переменную body. В этой переменной будут накапливаться формы, осуществляющие вычисления будущей функции. Дальше все довольно просто:

(defun action-proc (fi)
   (let ((stmt nil)
         (proc-name nil)
         (proc-parm nil)
         (loc-var nil)
         (body nil))
    (loop
         (setq stmt (mk-intf (getLine fi))) 
         (when (null stmt) (return t))
         (cond ((eq (car stmt) 'proc) 
                    (setq proc-name (nth 1 stmt))
                    (setq proc-parm (nth 2 stmt)))                    
               ((eq (car stmt) 'end_proc) (return t))
               ((eq (car stmt) 'print) 
                    (setq body (append body 
                               (list (cons 'printline (cdr stmt))))))
               ((eq (car stmt) 'input) 
                    (setq body (append body 
                      (list (list 'setq (cadr stmt) (list 'read) )))))
               (t (printsline (strCat "**** Оператор " 
                              (output stmt) 
                              " не распознан"))
                              (setq *flagerr* t))))
    `(defun ,proc-name ,proc-parm ,@body)))

Давайте теперь подготовим вот такой исходник на мини-бэйсике:

proc f1(x,y)
 print x
 print y
end_proc 
proc f2(x)
 input x
 print x
end_proc

и попробуем его протранслировать… У нас появятся две лисповские функции f1 и f2. Посмотрим на их определяющие выражения и убедимся, что они сгенерированы верно:

(getd 'f1)
==> (EXPR (X Y) (PRINTLINE X) (PRINTLINE Y))
(getd 'f2)
==> (EXPR (X) (SETQ X (READ)) (PRINTLINE X))

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

Оператор local может находиться в любом месте процедуры и встречаться более одного раза. Если в процессе обработки процедуры встретился оператор local, то необходимо взять список переменных и сохранить его в локальной переменной. После встречи оператора end_proc необходимо сгенерировать форму let и “заключить в нее” все выполняемые операторы (пока – только input и print). Вот как теперь будет выглядеть action-proc:

(defun action-proc (fi)
   (let ((stmt nil)
         (proc-name nil)
         (proc-parm nil)
         (loc-var   nil)
         (lv        nil)
         (body      nil))
    (loop
         (setq stmt (mk-intf (getLine fi))) 
         (when (null stmt) (return t))
         (cond ((eq (car stmt) 'proc) 
                    (setq proc-name (nth 1 stmt))
                    (setq proc-parm (nth 2 stmt)))                    
               ((eq (car stmt) 'end_proc) (return t))
               ((eq (car stmt) 'print) 
                    (setq body (append body 
                               (list (cons 'printline (cdr stmt))))))
               ((eq (car stmt) 'input) 
                    (setq body (append body 
                               (list (list 'setq (cadr stmt) 
                                     (list 'read) )))))
               ((eq (car stmt) 'local) 
                    (setq loc-var (append loc-var (cdr stmt))))
               (t (printsline (strCat "**** Оператор " 
                              (output stmt) " не распознан")) 
                              (setq *flagerr* t))))
    (iter (for a in (setof loc-var)) (collecting (list a 0) into lv))           
    `(defun ,proc-name ,proc-parm (let ,lv ,@body))))

Список локальных переменных накапливается в переменной loc-var. После завершения обработки процедуры из этого списка строится список пар вида (имя 0). При этом нежелательно повторение одинаковых имен… Как его предотвратить? Конечно, можно на каждой обработке оператора local проверять, нет ли повторения имен (если есть – выдавать сообщение об ошибке). Но, мне кажется, лучше просто устранить повторения, что и делает вызов setof. Теперь давайте протранслируем и запустим вот эту программу:

proc f1(x,y)
 local a,b,c
 print x
 print y
 input a
 print a
end_proc

Убеждаемся, что она работает именно так, как и предполагает алгоритм. Но все самое интересное впереди!

Отсюда можно скачать окончательный вариант того, что мы с вами нашкодили…

Продолжение следует!

Исправление ошибки «непарные круглые скобки»

Автор Konstanta, 25 октября 2019, 09:05

0 Пользователи и 1 гость просматривают эту тему.

Приветствую всех!

Простой макрос (поиск последней строки с переходом на число шагов вправо)
в LO вызывает синтаксическую ошибку «Непарные круглые скобки»

Вот код:
Range(«A» & Rows.Count).End(xlUp).Offset(0).Select
ActiveCell.Offset (0;7).Range («A1»).Select

Но все скобки парные! Я не могу понять что именно ему не нравится.


Точку с запятой заменить на запятую.



Так тут и зарыта проблема:
если я ставлю в синтаксисе LO в качестве разделителя точку с запятой, то появляется ошибка не парных круглых скобок.
Если ставлю запятую данная ошибка исчезает и макрос работает, но результат формулы выдаёт ошибку, поскольку LO преобразует формулу по-другому



Мне кажется, Вы путаете два разных языка.

В языке LibreOffice Basic разделителем параметров функций является всегда запятая. А в языке формул Calc разделитель — точка с запятой. Когда Вы задаёте в бейсике строку формулы для калька, в строке формулы должен использоваться синтаксис калька, а в самом бейсике — синтаксис бейска.


Если вы макросом хотите вставить формулу в ячейку, как было в начале поста, то нужно использовать точку с запятой, она является разделителем в табличных формулах. В самом коде vba/starbasic ,  разделитель запятая


Разобрался!

Задача решена, всем Спасибо!


Rust8

0 / 0 / 0

Регистрация: 02.04.2016

Сообщений: 2

1

02.04.2016, 07:20. Показов 2510. Ответов 4

Метки нет (Все метки)


Студворк — интернет-сервис помощи студентам

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<iostream>
#include<cstdio>
#include<clocale>
using namespace std;
int main()
{
    int a, b, f, i;
    setlocale(LC_CTYPE, "rus");
    for (i = 0; i < 10; i++)
    {
        a = rand();
 
        if (a % 2 == 0)
        {
            cout << a << "-четное число" << endl;
            cout << &a << "-адрес четного числа" << endl;
 
        }
        
        else {
 
            cout << a << "-нечетное число" << endl;
            cout << &a << "-адреса нечетного числа" << endl;
        }
    }
 
 
    system("pause");



0



Вездепух

Эксперт CЭксперт С++

10982 / 5965 / 1630

Регистрация: 18.10.2014

Сообщений: 14,962

02.04.2016, 09:09

2

И? В коде очевидная непарная фигурная скобка. В чем вопрос-то?



0



SadenSC

2 / 2 / 1

Регистрация: 03.12.2015

Сообщений: 7

02.04.2016, 09:12

3

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include<iostream>
#include<cstdio>
#include<clocale>
#include<cmath>
#include<cstdlib>
using namespace std;
int main()
{
    int a, b, f, i;
    setlocale(LC_CTYPE, "rus");
    for (i = 0; i < 10; i++)
    {
        a = rand();
 
        if (a % 2 == 0)
        {
            cout << a << "-четное число" << endl;
            cout << &a << "-адрес четного числа" << endl;
 
        }
        
        else {
 
            cout << a << "-нечетное число" << endl;
            cout << &a << "-адреса нечетного числа" << endl;
        }
    }
 
    system("pause");
}



1



zss

Модератор

Эксперт С++

13320 / 10454 / 6253

Регистрация: 18.12.2011

Сообщений: 27,911

02.04.2016, 09:39

4

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include<iostream> // ввод-вывод
#include<clocale> //setlocale
#include<cstdlib> // rand srand
#include <ctime> // time
using namespace std;
int main()
{
    setlocale(LC_CTYPE, "rus");
    srand((unsigned)time(NULL));// Задание начального значения датчика случайных чисел
    int a;
    cout<< &a << " - адрес числа" << endl<<endl; // выводим ОДИН раз, адрес не меняется
    for (int i = 0; i < 10; i++)
    {
        a = rand();
        if (a % 2 == 0)
            cout << a << " - четное" << endl;
        else
            cout << a << " - нечетное" << endl;
    }
    system("pause");
    return 0;
}



1



0 / 0 / 0

Регистрация: 02.04.2016

Сообщений: 2

02.04.2016, 11:52

 [ТС]

5

Моя невнимательность, извиняюсь



0



  1. При несоблюдении
    грамматики языка должны выдаваться
    сообщения об ошибках, записываться в
    поле вывода ошибок и вызывать (при
    выделении) подсвечивание строки, где
    они были найдены.

  2. При отсутствии
    текста, подлежащего трансляции, будет
    выведено сообщение “[Синтаксическая
    ошибка] Строка
    X
    : Неожиданный конец файла”
    ,
    где Х – номер строки с ошибкой, для
    устранения которой следует ввести код
    программы в окно ввода.

  3. При использовании
    в тексте программы (исключая комментарии)
    пользователем символов, не оговоренных
    в ТЗ, будет выведено сообщение типа
    “[Лексическая
    ошибка] Строка
    X:
    Недопустимый символ”
    ,
    где Х – номер строки с ошибкой. Для
    устранения следует просмотреть строку
    и удалить из нее недопустимые алфавитом
    символы.

  4. При использовании
    в тексте программы пользователем
    идентификаторов, не соответствующих
    требованиям ТЗ, возникнет ошибка
    “[Лексическая
    ошибка] Строка
    X
    :
    Y
    — недопустимое имя идентификатора”
    ,
    где Y
    – имя идентификатора. Для устранения
    следует найти и удалить/изменить
    неверные идентификаторы.

  5. При объявлении
    метки способом, не предусмотренным
    ТЗ, появится сообщение “[Синтаксическая
    ошибка] Строка
    X
    : Неправильное объявление метки
    Y”,
    где Y
    – имя метки. Для устранения следует
    объявить метку по правилам в ТЗ.

  6. При повторном
    объявлении метки появится ошибка
    “[Синтаксическая
    ошибка] Строка
    X
    : Повторное определение метки
    Y”,
    где Y
    – имя этой метки. Для устранения следует
    изменить имя объявляемой метки.

  7. При попытке
    перейти на необъявленную метку возникнет
    сообщение “[Синтаксическая
    ошибка] Строка
    X
    : Использование необъявленной метки
    Y”,
    где Y
    – имя необъявленной метки. Для устранения
    ошибки требуется определить метку.

  8. При попытке
    использования необъявленного
    идентификатора возникнет ошибка
    “[Синтаксическая
    ошибка] Строка
    X
    : Необъявленный идентификатор”,
    для
    устранения необходимо объявить
    идентификатор.

  9. При повторном
    объявлении идентификатора возникнет
    ошибка вида “[Синтаксическая
    ошибка] Строка
    X
    : повторное объявление идентификатора
    Y
    ”, для
    устранения которой требуется
    переименовать одну из переменных.

  10. При использовании
    в арифметических выражениях операндов
    разного типа может возникнуть целый
    ряд ошибок вида “[Синтаксическая
    ошибка] Строка
    X
    : Разные
    типы операндов у операции
    Y”,
    где Y
    – одна из поддерживаемых операций
    (+,–,*,/). Для устранения требуется
    привести переменные к соответствующему
    типу.

  11. Отсутствие
    идентификатора в левой части присваивания
    приведет к ошибке типа “[Синтаксическая
    ошибка] Строка
    X
    : Ожидается идентификатор в левой части
    присваивания”
    ,
    для устранения которой следует поместить
    идентификатор слева от оператора
    присваивания.

  12. Аналогично,
    отсутствие в правой части присваивания
    выражения приведет к ошибке
    “[Синтаксическая
    ошибка] Строка
    X
    : Ожидается выражение в правой части
    присваивания»
    ,
    для устранения которой следует поместить
    какое-либо арифметическое выражение
    в правой части присваивания.

  13. Если типы выражений
    по обе стороны от знака присваивания
    различны, то возникнет ошибка
    “[Синтаксическая
    ошибка] Строка
    X
    :

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

  14. При обработке
    текста программы во многих местах
    ожидаются соответствующие ключевые
    слова, либо разделители, поэтому при
    отступлении от правил написания
    программы может наблюдаться следующая
    ошибка: “[Синтаксическая
    ошибка] Строка
    X
    : Ожидается
    Y,
    где Y
    – может быть “(,),:,;.Program,
    Begin,
    End”.
    Для устранения следует в соответствующем
    месте вставить требуемую часть
    конструкции.

  15. При перечислении
    аргументов функции несоответствующего
    типа или количества возникнет ошибка
    “[Синтаксическая
    ошибка] Строка
    X
    : Несоответствие аргументов вызываемой
    функции
    Y,
    для устранения которой следует исправить
    вызов функции таким образом, чтобы
    аргументы совпадали по количеству и
    типу с заявленными.

  16. При задании
    несуществующего типа переменной или
    функции возникнет ошибка “[Синтаксическая
    ошибка] Строка
    X
    :
    Y
    не является поддерживаемым типом»
    ,
    где Y
    — неподдерживаемый тип. Для устранения
    следует объявить тип переменной с
    помощью поддерживаемых типов.

  17. При обнаружении
    непарных кавычек появится ошибка
    “[Синтаксическая
    ошибка] Строка
    X
    : Не закрыты кавычки”
    ,
    для устранения которой следует закрыть
    кавычки.

  18. При обнаружении
    непарных скобок комментария появится
    ошибка “[Синтаксическая
    ошибка] Строка
    X
    : Незакрытый комментарий”
    ,
    для устранения которой следует закрыть
    комментарий.

  19. При обнаружении
    лишнего ключевого слова End
    появится ошибка “[Синтаксическая
    ошибка] Строка
    X
    : Лишний End”,

    для устранения которой следует удалить
    лишний End.

  20. При отсутствии
    в секции Var
    объявлений переменных появится ошибка
    “[Синтаксическая
    ошибка] Строка
    X
    :
    В
    секции Var отсутствуют переменные»
    ,
    для устранения которой следует либо
    удалить заголовок секции, либо объявить
    переменные в этой секции.

  21. При соответствии
    текста программы грамматике языка в
    окне сообщений появится сообщение
    “Трансляция
    программы успешно завершена”
    .

<< вернуться к списку правил

Правило «Непарные скобки или апострофы»

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

Описание: Непарные скобки или апострофы
Категория: Пунктуация

(ID: PUNCTUATION)

Предложения с ошибками,
которые могут быть обнаружены эти правилом:
  • Самоотверженный поступок Оленина (подарок Лукашке коня вызывает лишь удивление и усиливает недоверие к нему станичников.

    Исправления:
    (

Правильные предложения
для проверки:
  • Самоотверженный поступок Оленина (подарок Лукашке коня) вызывает лишь удивление и усиливает недоверие к нему станичников.
Шаблон:

[Java правило]

Sourcecode

Проверить следующий текст только этим правилом:

Номер:

RU_UNPAIRED_BRACKETS

Версия: 6.2-SNAPSHOT (2023-06-24 20:33:03 +0000)

  • Ошибка трансляции на youtube geforce experience
  • Ошибка трансляции на twitch geforce experience
  • Ошибка трансляции oculus quest 2
  • Ошибка трансляции cannot run translation program powermill
  • Ошибка транскрипция слова на русском языке