Что такое лексема в программировании

от admin

2.2. Лексемы

Из символов алфавита формируются лексемы(или элементарные конструкции) языка – минимальные значимые единицы текста в программе:

– ключевые (зарезервированные) слова;

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

Границы лексем определяются другими лексемами, такими как разделители или знаки операций, а также комментариями.

2.3. Идентификаторы и ключевые слова

Идентификатор (ID) – это имя программного объекта  (константы, переменной, метки, типа, функции и т.д.). В идентификаторе могут использоваться латинские буквы, цифры и знак подчеркивания; первый символID– не цифра; пробелы внутриIDне допускаются.

Длина идентификатора определяется выбранной версией среды программирования. Например, в среде Borland C++ 6.0 идентификаторы могут включать любое число символов, из которых воспринимаются и используются только первые 32 символа. Современная тенденция – снятие ограничений длины идентификатора.

При именовании объектов следует придерживаться общепринятых соглашений:

IDпеременных и функций обычно пишутся строчными (малыми) буквами –index, max();

IDтипов пишутся с большой буквы, например,Spis,Stack;

IDконстант (макросов) – большими буквами –INDEX,MAX_INT;

– идентификатор должен нести смысл, поясняющий назначение объекта в программе, например, birth_date– день рождения,sum– сумма;

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

В Си прописные и строчные буквы – различные символы. Идентификаторы Name,NAME,name– различные объекты.

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

Что такое лексема в программировании

Важно отметить, что препроцессор не знает синтаксиса С.

2.3.2. Пространство между лексемами

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

Например, рассмотрим две последовательности: int i; int j;
и
int i;
int j;

  1. int
  2. i
  3. ;
  4. float
  5. f
  6. ;

2.3.3. Запись длинных строк

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

Здесь записана одна длинная строка символов.

2.3.4. Комментарии

Комментарии представляют собой фрагменты текста, предназначенные для записи пояснений. Комментарии предназначены для программистов, которые будут читать исходный текст. Комментарии в процессе трансляции программы игнорируются. Комментарии можно записать двумя способами. В первом способе комментарий открывается парой символов /*, а закрывается символами */. Например: int /* объявить */ i /* счетчик */ ;

В процессе трансляции будет получено всего три лексемы: int, i, ;

Второй способ записи комментария в С++ состоит в записи двух подряд символов «косой черты» (//). Комментарий начинается от этих символов (//) и заканчивается символом перевода строки. Например: class X // это комментарий;
. ;

Следует быть осторожным в использовании /* и // одновременно. Это может привести к нежелательным последствиям. Например: int i = j //* разделить на k */k;
+m;

H#, Создаём свой язык программирования


Доброго времени суток.
В этой статье я хочу сделать обзор одного из главных нововведений в Visual Studio 2010, а именно — функционального языка программирования F#.

Рассматривать синтаксис и потенциал F# мы будем на примере создания своего собственного интерпретатора для придуманного нами языка программирования (Ведь рассказвать о чем-то всегда интереснее на примерах).

Опишем задачу, которую мы собираемся решать, а именно — синтаксис нашего языка программирования, он будет достаточно прост, вот приблизительно то, что мы хотим видеть:

function MyEvaluations(a,b,c)
<
val in = Summ(a,b,1);
val out = Summ(b,c,1);
val d = in*out;
return d;
>

Итак, у нас есть поддержка функций с передачей параметров и возвращаемым значением, печать, арифметические операции и локальные переменные.
Начнем это реализовывать, для этого нам понадобится установить дополнительный пакет F# Power Pack отсюда

Лексика

Теперь создаём новый проект F# из онлайн шаблона F# Parsed Language Starter и очищаем все файлы кроме lexer.fsl
Для того, чтобы разобраться с содержимым этого файла, давайте для начала разберём наш язык по косточкам. Под словом «лексема», будем понимать минимальные кирпичики из которых состоит язык программирования.
К лексемам относятся ключевые слова, скобки, запятые, имена, в общем всё то, из чего состоит исходный код программы. На уровне лексем у нас не существует функций и прочих логических единиц.

Откроем файл lexer.fsl
В F# аналогом namespace служит ключевое слово module, аналогом using — ключевое слово open, поэтому первое, что мы сейчас напишем — будет определение той области, в которой мы работаем:

<
module Lexer
open System
open Parser
open Microsoft.FSharp.Text.Lexing

let lexeme lexbuf =
LexBuffer<char>.LexemeString lexbuf
>
let digit = [ ‘0’ — ‘9’ ]
let whitespace = [ ‘ ‘ ‘\t’ ]
let newline = ( ‘\n’ | ‘\r’ ‘\n’ )
let symbol = [ ‘a’ — ‘z»A’ — ‘Z’ ]

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

Синтаксический разбор

%token <Double> DECIMAL
%token PLUS MINUS MULTIPLE DIVIDE
%token LPAREN RPAREN
%token FUNCTIONKW RETURN VARIABLE
%token BEGIN END
%token EQUALS
%token <String> VARNAME
%token ENDOFLINE
%token COMMA
%token EOF
%type <WholeProgram> start

Здесь указаны все наши типы данных, если наш тип должен иметь значение — то тип этого значения пишется в угловых скобках.
start — это так называемая «аксиома грамматики», то, что мы будем пытаться «собрать» при интерпретации нашего кода.

Грамматика записывается в следующей форме:

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

Запишем теперь что же представляет из себя функция:

OperatorsList:
| Operator
| OperatorsList Operator

AdditionParametersList:
| Comma VarName
| AdditionParametersList Comma VarName

namespace ParserLibrary
open System

type Operator =
| VarOperator of string * Expression
| ReturnOperator of Expression

and Operand =
| DECIMALOP of Double
| SUBEXPROP of Expression
| VARNAMEOP of String
| FUNCOP of FunctionTitle

and HighExpression =
| MULTIPLY of HighExpression * Operand
| DIVIDEOP of HighExpression * Operand
| VAR of Operand

and Expression =
| SUMM of Expression * HighExpression
| SUB of Expression * HighExpression
| HighExpression of HighExpression

and FunctionTitle = String * List<String>
and FunctionDescription = FunctionTitle * OperatorsList
and OperatorsList = List<Operator>
and AdditionParamsList = List<String>
and WholeProgram = List<FunctionDescription>

WholeProgram:
| FunctionDescription < $1 :: [] >
| WholeProgram FunctionDescription

FunctionTitle:
| VARNAME LPAREN RPAREN < $1, [] >
| VARNAME LPAREN VARNAME RPAREN < $1, $3 :: [] >
| VARNAME LPAREN VARNAME AdditionParamsList RPAREN

AdditionParamsList:
| COMMA VARNAME < $2 :: [] >
| AdditionParamsList COMMA VARNAME

OperatorsList:
| Operator < $1 :: [] >
| OperatorsList Operator

FunctionDescription:
| FUNCTIONKW FunctionTitle BEGIN END < $2, [] >
| FUNCTIONKW FunctionTitle BEGIN OperatorsList END

Operator:
| VARIABLE VARNAME EQUALS Expression ENDOFLINE < VarOperator($2, $4) >
| RETURN Expression ENDOFLINE

Expression:
| Expression PLUS HighExpression < SUMM($1, $3) >
| Expression MINUS HighExpression < SUB($1, $3) >
| HighExpression

HighExpression:
| HighExpression MULTIPLE Operand < MULTIPLY($1, $3) >
| HighExpression DIVIDE Operand < DIVIDEOP($1, $3) >
| Operand

Operand:
| DECIMAL < DECIMALOP($1) >
| VARNAME < VARNAMEOP($1) >
| FunctionTitle < FUNCOP($1) >
| LPAREN Expression RPAREN

$1, $2 и т.д. — это номер переменной в условии
На примере этого кода познакомимся с ещё одной замечательной возможностью F# — работой со списками.
Выражение A :: B означает создание и возврат нового списка, основанного на списке B с добавлением в него элемента A
[] — пустой список
Также мы можем задавать списки в коде напрямую или из диапазонов, например:
[1,2,3,4,5] или [1..5]
В таком случае 100 :: [1..5] вернёт нам вот такой список: [100,1,2,3,4,5]
Оператор @ — создание нового списка из объединения двух существующих.
Например: [1..5] @ [6..10] вернёт [1..10]

Заметим, что в случае с выбором по шаблону — мы указываем именованный конструктор, для тех же типов, которые просто являются синонимами каких либо кортежей (FunctionDescription, OperatorsList и т.д.) — мы просто перечисляем параметры в то порядке, как мы объявили в кортеже.
Итак, это всё что нам необходимо для того, чтобы из простого текста F# сделал нам дерево синтаксического разбора, вторая часть — интерпретация.
Открываем файл Program.fs и пишем open для тех пространств имён, которые мы будем использовать:

Интерпретация

В классическом функциональном программировании все объекты неизменяемые, это можно увидеть на примере работы со списками, когда мы вместо добавления элемента в список — создаваём новый на основе сущестующих. Это позволяет безболезненно распараллеливать программы, т.к. не нужно иметь сложных синхронизаций на запись.
F# же, будучи функциональным языком, отлично поддерживает всю библиотеку .NET, позволяя работать с изменяемыми объектами. Полного копирования данных в данном случае не происходит, копируются только ссылки на объекты, это позволяет достичь компроммиса по скорости не ломая возможность красиво распараллеливать данные, своеобразный copy on write, если хотите.

Итак, займёмся интерпретацией:
Начнем с разбора выражения
Объявим функцию с выбором по шаблону, let rec – ключевые слова, EvaluateExpression – имя, expression – параметр.
Вспомним, что объявляя типы мы создавали конструкторы с выбором по шаблону, эти же шаблоны мы используем при выборе ветви исполнения функции. К примеру: если передаваемый параметр expression был создан с помощью конструктора SUMM(expression, highExpression), то мы выполняем ветку сложения, и т.д.
Можно заметить, что эта функция практически повторяет созданные ранее конструкторы, приписывая каждому из них определённое действие

let rec evaluateExpression expression =
match expression with
| SUMM (expr, hexpr) -> (evaluateExpression expr) + (evaluateHighExpression hexpr)
| SUB (expr, hexpr) -> (evaluateExpression expr) — (evaluateHighExpression hexpr)
| HighExpression hexpr -> evaluateHighExpression hexpr

and evaluateHighExpression highExpression =
match highExpression with
| MULTIPLY (hexpr, oper) -> (evaluateHighExpression hexpr) * (EvaluateOperand oper)
| DIVIDEOP (hexpr, oper) -> (evaluateHighExpression hexpr) / (EvaluateOperand oper)
| VAR oper -> EvaluateOperand oper

and EvaluateOperand oper =
match oper with
| DECIMALOP x -> x
| VARNAMEOP x -> stack.Item(currentFunctionName).Item(x)
| FUNCOP x -> evaluateFunction x
| SUBEXPROP x -> evaluateExpression x

Так как мы ввели поддержку переменных и функций, то нам необходимо написать обработчики на этот случай. В случае переменных всё более-менее просто, мы идём в словарь, содержащий значения переменных текущей функции и получаем значение по имени переменной (помним, что VARNAMEOP проассоциирован с типом String)

В случае попадания на функцию – нам необходимо скопировать параметры из вызывающей функции в соответствии с заголовком функции и начать её выполнение.
Для этого допишем следующий код:

and evaluateFunction(f:FunctionTitle) =
let caller = currentFunctionName;
let newStack = Dictionary<String, Double>()
let realParams = functionsList.Item (f |> GetFunctionName) |> GetFormalParamsListDecription

let formalParams = GetFormalParamsListTitle(f)
ignore <| List.mapi2 ( fun i x y -> newStack.Add(x, stack.Item(caller).Item(y))) realParams formalParams

currentFunctionName <- GetFunctionName(f)
stack.Add(currentFunctionName, newStack)

let operatorsList = functionsList.Item(GetFunctionName f) |> GetOperatorsList

let result = RunFunction operatorsList

currentFunctionName <- caller
result
// Часть вызываемых здесь функций будет определена чуть позже

Это тоже функция, но уже не с выбором по шаблону, а в более привычном для нас виде.

Разберём оператор конвеера (pipeline) “|>”, по сути – это более понятный способ вызова цепочек функций, если раньше нам нужно было писать OuterFunction(InnerFunction(Validate(data))), то в F# можно «развернуть» эту цепочку: data |> Validate |> InnerFunction |> OuterFunction
Обе записи приводят к одинаковому результату, однако при использовании оператора “|>” у нас функции применяются слева направо, в случае длинных цепочек – это более удобно.
Особенностью данных функций служит то, что нам не нужно писать return, например функция
test(a) = a*10
вернёт a*10, это отчасти удобно, однако нам надо позаботится о том, чтобы не вернуть какое либо значение «случайно» (к слову, VS подчеркнёт все непреднамеренные возвраты), для этого мы используем метод ignore, который ничего не делает и ничего не возвращает, чтобы все ignore были у нас в начале строки – мы используем обратный конвеерный оператор «<|», он делает тоже самое что и “|>”, только в обратном направлении, теперь функции применяются справа налево.
В случае простого присваивания, чтобы избежать возврата значения вместо «=» используется оператор “<-“

Разберём подробнее строку:

Мы получим результат: 16 27 38 49 60

Так как порядок параметров в скобках при вызове функции и при её описании в нашем языке– одинаковый, то мы просто обрабатываем парами элементы из списков вызова и объявления функции.
Ключевое слово fun определяет новую функцию (можно провести аналогию с лямбдами в C#), которая мы будем использовать для обработки списков, наша функция принимает 3 параметра, i – номер текущего элемента в обоих списках, x и y – соответственно элементы из первого и второго списков.
Таким образом здесь мы копируем в наш новый стек переменную из стека вызывающей функции.

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

let result = RunFunction operatorsList

currentFunctionName <- caller
result

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

Теперь напишем метод runFunction:

and RunFunction(o:OperatorsList) =
if currentFunctionName.Equals «print» then
stack.Item(currentFunctionName).Add( «return» , 0.0)
Console.WriteLine(stack.Item(currentFunctionName).Item( «toPrint» ).ToString())
if currentFunctionName.Equals «get» then
Console.Write( «Input: » );
stack.Item(currentFunctionName).Add( «return» , Double.Parse(Console.ReadLine()))
if currentFunctionName.Equals «Program» then
stack.Item(currentFunctionName).Add( «return» , 1.0)

ignore <| List.map ( fun x -> EvaluateOperator x) o
stack.Item(currentFunctionName).Item( «return» )

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

В F# используются значащие отступы, в частности — для выделения тела блоков if.
Последнее, что нам осталось сделать – определись недостающие функции, которые мы использовали, и написать оболочку, которая будет использовать наш парсер:

Эти записи абсолютно эквивалентны.

Последние штрихи

let mutable source = «» ;
let rec readAndProcess() =
printf «:»
match Console.ReadLine() with
| «quit» -> ()
| «GO» ->
try
printfn «Lexing. »
let lexbuff = LexBuffer<char>.FromString(source)

printfn «Parsing. »
let program = Parser.start Lexer.tokenize lexbuff
ignore <| List.map ( fun x -> functionsList.Add(GetFunctionNameDescription(x),x)) program
functionsList.Add( «print» ,(( «print» , [ «toPrint» ]), []))
functionsList.Add( «get» ,(( «get» , []), []))
printfn «Running. »
ignore <| (functionsList.Item( «Program» ) |> GetTitle |> evaluateFunction)

with ex ->
printfn «Unhandled Exception: %s» ex.Message

readAndProcess()
| expr ->
source <- source + expr
readAndProcess()

Определяем новую функцию, в которой мы будем пытаться распарсить введенную с клавиатуры строку, если разбор завершился успешно – добавляем все функции из полученного списка (program) в нашу переменную functionsList.
Для вызова функции в начале работы программы просто пишем её имя без отступов в конце файла.
Так как у нас в нашем языке есть две предопределённые функции (get и print) – добавляем их, затем выполняем функцию с именем Program.

Последнее что можно описать в этом примере – это интересный способ конструирования объектов для которых мы не определили конструкторы через match

Запуск

Чтож, запустим наш интерпретатор:
i

После нажатия Enter мы увидим приглашение ввести значение переменной:
i

И после этого:
i

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

Если теперь ещё раз взглянуть на код на F#, то можно увидеть, что мы написали минимум, просто описывающий то, что мы хотим сделать, это позволило написать свой интерпретатор и уложиться всего в

200 строк кода, F# позволяет избавиться от рутинной работы по написанию кода, который не отвечает на вопрос «что», а отвечает на вопрос «как». Безусловно, программирование на этом языке по началу кажется трудным, но в ряде случаев оно того стоит, так как даёт возможность писать гибкий код, который легко поддерживать.
Спасибо за внимание.

Что такое лексема в программировании

  1. int
  2. i
  3. ;
  4. float
  5. f
  6. ;

image
Доброго времени суток.
В этой статье я хочу сделать обзор одного из главных нововведений в Visual Studio 2010, а именно — функционального языка программирования F#.

function MyEvaluations(a,b,c)
val in = Summ(a,b,1);
val out = Summ(b,c,1);
val d = in*out;
return d;
>

module Lexer
open System
open Parser
open Microsoft.FSharp.Text.Lexing

%token <Double> DECIMAL
%token PLUS MINUS MULTIPLE DIVIDE
%token LPAREN RPAREN
%token FUNCTIONKW RETURN VARIABLE
%token BEGIN END
%token EQUALS
%token <String> VARNAME
%token ENDOFLINE
%token COMMA
%token EOF
%type <WholeProgram> start

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

Запишем теперь что же представляет из себя функция:

OperatorsList:
| Operator
| OperatorsList Operator

AdditionParametersList:
| Comma VarName
| AdditionParametersList Comma VarName

namespace ParserLibrary
open System

type Operator =
| VarOperator of string * Expression
| ReturnOperator of Expression

and Operand =
| DECIMALOP of Double
| SUBEXPROP of Expression
| VARNAMEOP of String
| FUNCOP of FunctionTitle

and HighExpression =
| MULTIPLY of HighExpression * Operand
| DIVIDEOP of HighExpression * Operand
| VAR of Operand

and Expression =
| SUMM of Expression * HighExpression
| SUB of Expression * HighExpression
| HighExpression of HighExpression

and FunctionTitle = String * List<String>
and FunctionDescription = FunctionTitle * OperatorsList
and OperatorsList = List<Operator>
and AdditionParamsList = List<String>
and WholeProgram = List<FunctionDescription>

WholeProgram:
| FunctionDescription
| WholeProgram FunctionDescription

FunctionTitle:
| VARNAME LPAREN RPAREN
| VARNAME LPAREN VARNAME RPAREN
| VARNAME LPAREN VARNAME AdditionParamsList RPAREN

AdditionParamsList:
| COMMA VARNAME
| AdditionParamsList COMMA VARNAME

OperatorsList:
| Operator
| OperatorsList Operator

FunctionDescription:
| FUNCTIONKW FunctionTitle BEGIN END
| FUNCTIONKW FunctionTitle BEGIN OperatorsList END

Operator:
| VARIABLE VARNAME EQUALS Expression ENDOFLINE
| RETURN Expression ENDOFLINE

Expression:
| Expression PLUS HighExpression
| Expression MINUS HighExpression
| HighExpression

HighExpression:
| HighExpression MULTIPLE Operand
| HighExpression DIVIDE Operand
| Operand

Operand:
| DECIMAL
| VARNAME
| FunctionTitle
| LPAREN Expression RPAREN

let rec evaluateExpression expression =
match expression with
| SUMM (expr, hexpr) -> (evaluateExpression expr) + (evaluateHighExpression hexpr)
| SUB (expr, hexpr) -> (evaluateExpression expr) — (evaluateHighExpression hexpr)
| HighExpression hexpr -> evaluateHighExpression hexpr

and evaluateHighExpression highExpression =
match highExpression with
| MULTIPLY (hexpr, oper) -> (evaluateHighExpression hexpr) * (EvaluateOperand oper)
| DIVIDEOP (hexpr, oper) -> (evaluateHighExpression hexpr) / (EvaluateOperand oper)
| VAR oper -> EvaluateOperand oper

and EvaluateOperand oper =
match oper with
| DECIMALOP x -> x
| VARNAMEOP x -> stack.Item(currentFunctionName).Item(x)
| FUNCOP x -> evaluateFunction x
| SUBEXPROP x -> evaluateExpression x

and evaluateFunction(f:FunctionTitle) =
let caller = currentFunctionName;
let newStack = Dictionary<String, Double>()
let realParams = functionsList.Item (f |> GetFunctionName) |> GetFormalParamsListDecription

let formalParams = GetFormalParamsListTitle(f)
ignore <| List.mapi2 ( fun i x y -> newStack.Add(x, stack.Item(caller).Item(y))) realParams formalParams

currentFunctionName <- GetFunctionName(f)
stack.Add(currentFunctionName, newStack)

let operatorsList = functionsList.Item(GetFunctionName f) |> GetOperatorsList

let result = RunFunction operatorsList

let result = RunFunction operatorsList

currentFunctionName <- caller
result

and RunFunction(o:OperatorsList) =
if currentFunctionName.Equals «print» then
stack.Item(currentFunctionName).Add( «return» , 0.0)
Console.WriteLine(stack.Item(currentFunctionName).Item( «toPrint» ).ToString())
if currentFunctionName.Equals «get» then
Console.Write( «Input: » );
stack.Item(currentFunctionName).Add( «return» , Double.Parse(Console.ReadLine()))
if currentFunctionName.Equals «Program» then
stack.Item(currentFunctionName).Add( «return» , 1.0)

ignore <| List.map ( fun x -> EvaluateOperator x) o
stack.Item(currentFunctionName).Item( «return» )

let mutable source = «» ;
let rec readAndProcess() =
printf «:»
match Console.ReadLine() with
| «quit» -> ()
| «GO» ->
try
printfn «Lexing. »
let lexbuff = LexBuffer<char>.FromString(source)

printfn «Parsing. »
let program = Parser.start Lexer.tokenize lexbuff
ignore <| List.map ( fun x -> functionsList.Add(GetFunctionNameDescription(x),x)) program
functionsList.Add( «print» ,(( «print» , [ «toPrint» ]), []))
functionsList.Add( «get» ,(( «get» , []), []))
printfn «Running. »
ignore <| (functionsList.Item( «Program» ) |> GetTitle |> evaluateFunction)

with ex ->
printfn «Unhandled Exception: %s» ex.Message

readAndProcess()
| expr ->
source <- source + expr
readAndProcess()

Чтож, запустим наш интерпретатор:
i

После нажатия Enter мы увидим приглашение ввести значение переменной:
i

И после этого:
i

Читать:
Почему кулер на процессоре крутится на максимум

Похожие статьи