1 (изменено: Rumata, 2016-02-15 17:13:09)

Тема: JScript/VBScript: WSH интерпретатор

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

Предисловие

WSH не хватает нескольких возможностей:

-- "интерактивность" - исполнение кода сразу после ввода команд в командной строке. такая возможность существует в других реализациях, например, в NodeJS, Rhino. Если запустить их интерпретаторы, то сразу попадешь в собственную командную строку, где введя команды тут же увидишь результат. Но они имеют свой, существенно отличный от WSH, и практически несовместимый интерфейс взаимодействия с операционной системой.

-- one-liner program - однострочные программы, это программы написанные и исполняемые здесь и сейчас, минуя стадию создания файла. Они написаны на конкретном языке и выполняются в командной оболочке. Знакомые с AWK, Perl, SED или Python знают о чем идет речь. Практикующие в PowerShell тоже знают об этой возможности. В WSH же для этого требуется создать отдельный файл.

-- автоматическое подключение скриптов (назовем их библиотечкных). В WSH можно вручную описать ссылки на необходимые файлы, однако отсутствует автоматическое подключение как это сделано в Perl, PHP или Python. Функции eval (JavaScript) или ExecuteGlobal (VBScript) лишь частично решают проблему. А что если поручить это программе?

Вот мы и подошли к вопросу о необходимости создания такой программы, которая удовлетворяла бы всем требованиям:
-- автоматическая компоновка программы
-- поддержка интерактивности
-- исполнение однострочных программ

Краткое описание возможностей

Здесь кратко, в общих чертах, заявлены все возможности. Далее они будут рассмотрены подробнее.
-- Исполнение внешних файлов (JScript либо VBScript).
-- Исполнение однострочных программ прямо в командной оболочке. Может выполнить команду, или группу команд JScript/VBscript. А может выполнить определенные действия для заданных входных файлов.
-- Интерактивный режим - принимать команды из консоли и тут же выполнять. Имеется своеобразная поддержка многострочного ввода кода, что упрощает тестирование некоего кода.
-- Компоновка и исполнение результирующего WSF-скрипта с библиотечными файлами.
-- Гибкая конфигурируемость.

Исполнение файлов

Здесь все осталось на прежнем уровне. Запускаете скрипт, он выполняется. Бонусом является то, что если у вас есть свои библиотечные js- vbs-файлы, они будут подключены автоматически. Чтобы библиотечные файлы подключались автоматически поместите их в подкаталоги рядом с программой wscmd.bat:

js\*.js
js\win32\*.js
vbs\win32\*.vbs

В следующем примере будет создан временный wsf-файл, в который (при их наличии) будут включены все внешний библиотечные файлы, а затем выполнен и сам скрипт:

wscmd script

Интерактивный режим

Переход в интерактивный режим осуществляется просто: введите команду wscmd и вы увидите приглашение - программа ожидает ввода команд JScript и немедленно выполнит по нажатию на ввод.

C:\>wscmd
wscmd >

Однострочные программы

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

wscmd /js  /e "alert(Math.random())"

wscmd /vbs /e "alert Rnd"

В этих примерах показано как просто выполнить короткие программы прямо в командной строке. Ключ /js можно опустить, так как JScript - основной язык. В прримере показано "встроенная" функция alert, которая испоьзуется вместо WScript.Echo для упрощения ввода.

Обработка входных файлов

Когда требуется выполнить определенные действия с данными входных файлов, надо открыть файл, прочитать каждую строку и выполнить определенные действия. С этой программой определенные рутинные действия существенно сокращаются.

wscmd /e /n "line && alert(line)" filename

wscmd /vbs /e /n "if line <> """" then : alert line : end if" filename

В примерах выше программа построчно читает файл filename и выводит только непустые строки. Вся "кухня" по работе с открытием и чтением файлов сокрыта внутри программы - она все выполняет сама.

wscmd /e /n "alert(lineNumber, line)" filename

wscmd /vbs /e /n "alert lineNumber, line" filename

Предыдущие примеры показыают как можно вывести содержимое файла с нумерацией строки.

Еще один пример. Посчитать и вывести количество строк в каждом файле и, по окончании, суммарное количество строк:

wscmd /e /after "WScript.Echo(currentNumber, filename)" /end "WScript.Echo(lineNumber)" filename1 filename2

Из предыдущих примеров видно, что опция /n изменяет поведение программы - скрипт, заданный в командной строке выполняется последовательно для каждой строки входного файла. Существует еще одна опция - /p. Она работает аналогично /n, но кроме действий, заданных явно, выводит строку line. Можно посмотреть как будут выглядеть примеры с нумерацией строк с использованием этих двух опций:

wscmd /e /n "alert(lineNumber + ':' + line)" filename

wscmd /e /p "line = lineNumber + ':' + line" filename

Специальные переменные currentNumber, lineNumber, filename - номер последней считанной строки файла, общее количество считанных строк, имя текущего файла, соответственно. Опция /after "code" заставляет выполнить заданный код по завершении всего файла, а /end "code" после всех файлов. Аналогично есть опции /begin "code" для выполнения кода перед всеми файлами, /before "code" - для выполнения кода перед каждым файлом.

"Отладка", компоновка WSF-файла

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

Наверняка у каждого из вас есть свой набор любимых скриптов, которые используете полностью или частично в качестве библиотечных - либо копируете в текущий проект, либо подключаете в как внешний файл в WSF-файл с помощью тега <script src="filename">. Данная программа помогает частично избавиться от этой рутины.

Фактически программа просматривает заданный каталог (по умолчанию, каталог из которого запущена программа) и создает временный WSF-файл, в котором подключаются все js- и vbs-файлы. После компоновки временный файл выполняется и по завершении работы удаляется. Можно изменить порядок выполнения программы с помощью следующих опций:
/compile - собрать временный WSF-файл, в котором все библиотечные файлы подключены как внешние.
/embed - аналогично собирается временный WSF-файл, но все содержимое библиотечных файлов вставляется в результирующий файл.

Проверить как это работает можно используя предыдущие примеры.

wscmd /debug   /e /p "line = lineNumber + ':' + line" filename
wscmd /compile /e /p "line = lineNumber + ':' + line" filename
wscmd /embed   /e /p "line = lineNumber + ':' + line" filename

Конфигурационные файлы

С помощью конфигурационных файлов можно изменить поведение программы и повлиять на результирующий код. wscmd.ini - файл конфигураций, может располагаться в текущем каталоге, либо в каталоге программы wscmd.bat. Именно в таком порядке просматриваются конфигурационные файлы. Если вы создайтите для своего скрипта (назовем его script.vbs) отдельный файл с именем script.vbs.ini, то именно его инструкции будут использованы в первую очередь.

Конфигурационный файл - это простой текстовый файл и может содержать следующие параметры.

Параметры компоновки:
import - параметр определяет маску библиотечных файлов (по умолчанию js\*.js, js\win32\*.js, vbs\win32\*.vbs). Можно указывать несколько параметров import для подключения большего количества файлов.
execute - имя файла, который будет собран и выполнен. Значение по умолчанию - $$$%~n0_$UID.wsf, где $UID - макрос, который заменяется на уникальное числовое значение.
command - путь и опции интерпретатора WSH. По умолчанию - %windir%\system32\cscript.exe //nologo

Параметры кодировки и отладки WSF-файлов:
xml-encoding - кодировка файла (обычно utf-8 или windo-1251)
enable-error - обычно, false. Значение true разрешает вывод синтаксических ошибок времени исполнения WSF-файла.
enable-debug - обычно, false. Значение true разрешает запустить внешний отладчик для WSF-файла.

Заключение

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

Опции командной строки

wscmd /h

Встроенные функции JScript

echo help() | wscmd /q

Ваши вопросы и замечания были интересны.

( 2 * b ) || ! ( 2 * b )

2

Re: JScript/VBScript: WSH интерпретатор

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

greg zakharov пишет:

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

Было бы интересно посмотреть на Ваши разработки, идеи. Будет время - сделайте.

greg zakharov пишет:

вряд ли это для широких масс

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

( 2 * b ) || ! ( 2 * b )

3

Re: JScript/VBScript: WSH интерпретатор

чтение N-последних строк файла

greg zakharov пишет:

В NIX'ах есть для этого tail. В Windows'е на командном языке можно соорудить нечто подобное, но камнем преткновения будут кодировки

wscmd /v n 10 /e /before "s = []" /n "s.push(line); s.length > n && s.shift();" /after "alert(s.join('\n'))" [[/d|/u|/a] filename]

Описание по порядку:
определить переменную n - количество последних выводимых строк (здесь, 10 строк)
/v n 10

начало программы
/e

перед началом каждого файла обнулить массив строк
/before "s = []"

Для каждой строки - поместить новую строку в массив. При превышении длины n, вытолкнуть первую
/n "s.push(line); s.length > n && s.shift();"

По завершении чтения файла - вывести его последние n строк
/after "alert(s.join('\n'))"

/d, /u, /a - опции режима открытия файла, соответственно: открыть используя системные настройки, открыть как Юникод, открыть как ASCII

( 2 * b ) || ! ( 2 * b )

4 (изменено: Rumata, 2012-11-13 12:20:30)

Re: JScript/VBScript: WSH интерпретатор

greg zakharov
ADODB имеет существенный недостаток - читает весь файл в память целиком. Для маленьких файлов это не существенно, а для больших "портянок" лог-файлов это ощутимо по времени. Для чтения 2-4 байт загружать файл в память накладно.

greg zakharov пишет:

Постараюсь не забыть про фидбэки

Не к спеху. По мере Ваших возможностей.

В продолжение темы:
чтение N-первых строк файла

wscmd /v n 10 /e /p "if ( currentNumber > n ) exit()" filenames

описание опций

определить переменную n - количество первых выводимых строк (здесь, 10 строк)
/v n 10

Начало программы
/e

Печать каждой строки
/p

Когда номер текущей строки превысит заданное количество выводимых строк завершить программу
"if ( currentNumber > n ) exit()"

currentNumber - предопределенная переменная, хранит номер последней считанной строки текущего файла
exit() - предопределенная функция, аналог WScript.Quit()

( 2 * b ) || ! ( 2 * b )

5

Re: JScript/VBScript: WSH интерпретатор

Распаковал на Рабочий стол, попробовал запустить — вылетает с ошибкой:

D:\Путь с русскими буквами\wscmd\$$$wscmd_.wsf(0, 1) Windows Script Host: Не удается преобразовать текст  в кодировку Юникод для обработки

Очевидно, причина в том, что в $$$wscmd_.wsf сказано:

<?xml version="1.0" encoding="utf-8" ?>

хотя на самом деле он создаётся в однобайтовой OEM-кодировке, что проявляется в месте:

<script language="javascript" src="D:\Путь с русскими буквами\wscmd\wscmd.bat"></script>

Если не создавать wscmd.ini, то перед этой строкой добавляется 3 одинаковых:

<script language="javascript" src="D:\Путь"></script>

— спотыкается на пробеле в пути.

Указал в wscmd.ini кодировку cp866 (вероятно, влияет на все подключаемые файлы) —
запустилось; но это не универсальное решение:
если бы в пути были бы, например, такие символы …«», то они перекодировались бы в :<>… — как мне кажется лучше было бы использовать относительный путь, исправления потребуют строки 561, 552 и, вероятно, 606.

$$$wscmd_.wsf лучше удалять сразу после его запуска, т.к. если не завершая работы закрыть окно консоли или запустить новый экземпляр, то он зависает, нагружая процессор.


Работоспособность clip() зависит от настроек безопасности IE для зоны «Интернет» (установка «Разрешить операции вставки из сценария»)

6 (изменено: Rumata, 2012-11-23 17:38:20)

Re: JScript/VBScript: WSH интерпретатор

wisgest пишет:

<?xml version="1.0" encoding="utf-8" ?>

Я нигде не нашел в документации упоминаний об обязательности добавления спец.кодов BOM в WSF-файлы.

wisgest пишет:

если бы в пути были бы, например, такие символы …«», то они перекодировались бы в :<>… — как мне кажется лучше было бы использовать относительный путь, исправления потребуют строки 561, 552 и, вероятно, 606.

Это не решит проблему в случае подключения файла "имя_файла.js". В этом случае нет смысла писать относительные пути. Также это не решит проблему файлов, в имени или пути которых содержатся некорректные с точки зрения XML символы амперсанда &, которые должны конвертироваться в последовательность &amp;.

wisgest пишет:

Указал в wscmd.ini кодировку cp866

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

wisgest пишет:

спотыкается на пробеле в пути

Не понятно.

wisgest пишет:

$$$wscmd_.wsf лучше удалять сразу после его запуска

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

wisgest пишет:

Работоспособность clip() зависит от настроек безопасности IE

Это мне известно. Но реализована работа к буфером только в одну сторону.

Какой смысл использовать программу с кривыми именами файлов и каталогов, то есть нелатиница, нестандартные символы? Это не наезд. Просто мне не понятна идея разработчиков Windows и ее пользователей использовать нелатинские символы в именах файлов и каталогов. И мне понятна идея, что программа должна работать не зависимо от действующего окружения.

( 2 * b ) || ! ( 2 * b )

7 (изменено: wisgest, 2012-11-23 19:39:42)

Re: JScript/VBScript: WSH интерпретатор

Rumata пишет:

Я нигде не нашел в документации упоминаний об обязательности добавления спец.кодов BOM в WSF-файлы.

Я лишь описал ситуацию, с которой столкнулся и был вынужден как-то разбираться, причём не намекал, что её причина — отсутствие BOM, причину в виде сообщения об ошибке я привёл. Хотел описать причину, как наличие в файле байтов со значениями выше 0x7f, но это не точная формулировка — для некоторых из них возникает описанная ошибка, для других — «Недопустимый знак», третьи ошибку не вызывают, но декодируются непредсказуемым для меня образом…

Rumata пишет:

Это не решит проблему в случае подключения файла "имя_файла.js". В этом случае нет смысла писать относительные пути.

В этом случае относительный путь — само имя файла. Или речь о том, что в имени файла тоже могут быть русские буквы? Это меньшая проблема тем более, что в самом названии «$$$wscmd_.wsf» русских букв нет. Что касается имён папок, то пользовательские папки с русскими названиями просто уже есть в системе и хотя я изменил расположение большинства из них, перетименовывать не стал. Я часто использую «Рабочий стол». И думаю, пользователь не должен задумаваться о том, есть в промежуточном пути русские буквы и пробелы или нет.

wisgest пишет:

…исправления потребуют строки 561, 552…

Конкретно я имел в виду: в строке 561 убрать «f»,  в 552 — «dp».

Rumata пишет:
wisgest пишет:

Указал в wscmd.ini кодировку cp866

Это правильное решение.

Как я понимаю, в этой кодировке должны быть и все подключаемые скрипты?

Rumata пишет:
wisgest пишет:

спотыкается на пробеле в пути

Не понятно.

По-моему я всё расписал: если текущий путь «D:\Путь с русскими буквами\wscmd\», то

wisgest пишет:

…Если не создавать wscmd.ini, то перед этой строкой добавляется 3 одинаковых:

<script language="javascript" src="D:\Путь"></script>

т.е. путь в src урезается до первого пробела. Хотя эти строки сами по себе уже ошибка.

Rumata пишет:
wisgest пишет:

$$$wscmd_.wsf лучше удалять сразу после его запуска

Он удаляется.

После возврата в командный файл, но не после запуска.

Rumata пишет:
wisgest пишет:

Работоспособность clip() зависит от настроек безопасности IE

Это мне известно. Но реализована работа к буфером только в одну сторону.

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

8 (изменено: Rumata, 2012-11-23 20:21:32)

Re: JScript/VBScript: WSH интерпретатор

wisgest пишет:

Или речь о том, что в имени файла тоже могут быть русские буквы? Это меньшая проблема тем более, что в самом названии «$$$wscmd_.wsf» русских букв нет. Что касается имён папок, то пользовательские папки с русскими названиями просто уже есть в системе и хотя я изменил расположение большинства из них, перетименовывать не стал. Я часто использую «Рабочий стол». И думаю, пользователь не должен задумаваться о том, есть в промежуточном пути русские буквы и пробелы или нет.

Да. Я имел ввиду имя самого файла с кириллицей.

Проблема в том, что имя/путь файла может содержать кириллицу, либо в тексте подключаемого файла (опция /embed) могут содержаться символы отличные от ASCII7 или Unicode. Для этого достаточно один раз прописать в файле wscmd.ini нужную кодировку.

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

wisgest пишет:

путь в src урезается до первого пробела

Понял. Все опять же решается настройкой wscmd.ini

wisgest пишет:

Как я понимаю, в этой кодировке должны быть и все подключаемые скрипты?

Да. Либо windows-1251.

wisgest пишет:

После возврата в командный файл, но не после запуска.

Уточню. По завершении работы скрипта, перед возвратом в командный файл. Это нормально. Как-то еще можно удалить файл? Из самого скрипта? А если требуется именно этот временный файл? А потом его надо запустить уже самостоятельно? Это излишне.

wisgest пишет:

Насколько помнится, эта настройка распространяется на работу с буфером в обе стороны

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

( 2 * b ) || ! ( 2 * b )

9

Re: JScript/VBScript: WSH интерпретатор

Rumata пишет:

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

Хотелось бы обходится без необходимости устанавливать OEM-кодировку, если только подключаемые скрипты нарочно её не используют, если все подключаемые скрипты в именах имеют только базовую латиницу и расположены в том же каталоге, что и wscmd.bat или его подкаталогах, тоже имеющих в именах только базовую латиницу, независимо от того что творится на более высоких уровнях. Для этого нужны относительные пути. Как мне кажется, это достижимо с помощью чисто косметических изменений.

Rumata пишет:
wisgest пишет:

путь в src урезается до первого пробела

Понял. Все опять же решается настройкой wscmd.ini

Речь идёт именно о случае, когда wscmd.ini отсутствует. В этом случае применяются какие-то стандартные значения. Таким нехорошим образом.

wisgest пишет:

$$$wscmd_.wsf лучше удалять сразу после его запуска

Rumata пишет:

Уточню. По завершении работы скрипта, перед возвратом в командный файл. Это нормально. Как-то еще можно удалить файл? Из самого скрипта?

Можно попробовать запускать его через START /B и сразу же удалять, но на этом пути возможны свои трудности, так что если ничего не получится, то да — из самого скрипта, первым же опреатором!

Rumata пишет:

А если требуется именно этот временный файл? А потом его надо запустить уже самостоятельно?

Так он же всё равно потом удаляется. В каком месте его надо будет запустить: из самого себя? Зачем?

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

Более вероятно, что особенные у меня.

REGEDIT4

[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings\Zones\3]

; Разрешить операции вставки из сценария?
;"1407"=dword:00000000; Разрешить
;"1407"=dword:00000001; Предлагать
"1407"=dword:00000003; Отключить

Возможно, влияет не только этот параметр.

10

Re: JScript/VBScript: WSH интерпретатор

не читал, но осуждаю (с)
mshta работает сразу - нет ?

Я конечно далек от мысли... (с)

11

Re: JScript/VBScript: WSH интерпретатор

wisgest пишет:

применяются какие-то стандартные значения

Я полностью согласен, что было бы неплохо использовать стандартные значения. Или найти простой способ использовать Unicode как стандартное значение для XML. Но как быть с файлами (не именами а именно содержимым файлов), которые могут содержать символы в другой кодировке. Вот пример. По умолчанию, консоль использует cp866, WSF-файл - Unicode, а подключается с помощью /embed файл, содержащий символы windows-1251. И команда chcp в этом случае плохой. И других способов разрешить эту проблему нет.

Хотя можно использовать короткие имена. Так можно решить проблему не-ASCII символов в именах файлов. Но не решит всех проблем.

wisgest пишет:

Можно попробовать запускать его через START /B

Возможно. Главное чтобы не возникло ситуации, когда WSH еще загружает файл, а мы уже пытаемся удалить его. Кстати, опция START /I очень симпатична в этом случае, чтобы не загрязнять окружение WSF-скрипта собственными вспомогательными переменными. И главное, чтобы скрипты запускались вообще в принципе.

Я понимаю, что всё это выглядит как нечто с заявками на некую универсальность, но теми средствами, что я использую для их реализации, добиться весьма сложно. Естественно, я тестировал скрипт в окружении вида C:\Это моя\песочница, но в то время я был доволен своими тестами. К сожалению подробностей вспомнить уже не могу.

smaharbA пишет:

не читал, но осуждаю

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

( 2 * b ) || ! ( 2 * b )

12

Re: JScript/VBScript: WSH интерпретатор

Rumata пишет:
wisgest пишет:

применяются какие-то стандартные значения

Я полностью согласен, что было бы неплохо использовать стандартные значения. Или найти простой способ использовать Unicode как стандартное значение для XML.

Э-э… я имел в виду (если правильно нашёл место):

:: Set defaults
...
...
if not defined wscmd.ini.include set wscmd.ini.include=%~dp0js\*.js %~dp0js\win32\*.js %~dp0vbs\win32\*.vbs

— возможно (не проверял), здесь не хватает каких-то кавычек. И в целом мне не нравится как задаются «относительные» пути в этом месте и в файле настроек и, кажется, их несложно переделать на настоящие относительные пути. Может попозже подумаю над этим…

Rumata пишет:

…И команда chcp в этом случае плохой. И других способов разрешить эту проблему нет.

Не знаю, может ли это оказаться чем-то полезно: Convert Unicode to ASCII vv?
Вроде бы (не вникал), что-то похожее есть в теме smaharbA «CMD/BAT: О кодировке»
в процедуре :iconv (from,to,input).

Rumata пишет:

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

Не уверен насчёт START, но если удалять WSF-скрипт из самого себя, то если он начал выполнятся, то уже должен быть полностью загружен.

13

Re: JScript/VBScript: WSH интерпретатор

wisgest пишет:

не нравится как задаются «относительные» пути в этом месте и в файле настроек и, кажется, их несложно переделать на настоящие относительные пути

Три варианта. Использовать стандартные разделители путей: пробел или точку с запятой. Придумать свой разделитель, чтобы исключить попадание пробела или точки с запятой в имя пути или файла. Например, "файл; файл же.js". Третий вариант - кавычки. Он и используется.

А приведенная Вами строка - мой косяк. Исправьте ее на следующую. Позже исправлю в репозитории.

if not defined wscmd.ini.include set wscmd.ini.include="%~dp0js\*.js" "%~dp0js\win32\*.js" "%~dp0vbs\win32\*.vbs"
wisgest пишет:

Не знаю, может ли это оказаться чем-то полезно: Convert Unicode to ASCII vv?
Вроде бы (не вникал), что-то похожее есть в теме smaharbA «CMD/BAT: О кодировке»
в процедуре :iconv (from,to,input).

Спасибо. Посмотрю. Главное, чтобы это еще больше не усложнило.

START /B работает как-то странно. У меня в одних случаях происходит "наложение" на командную строку, в других - выводит ошибку. Но это опять упирается все в проблему кодировок Unicode, cp866, windows-1251 и прочие, в зависимости от локали.

( 2 * b ) || ! ( 2 * b )

14 (изменено: wisgest, 2012-11-28 14:50:54)

Re: JScript/VBScript: WSH интерпретатор

wscmd /man пишет:

execute
    This option defines a name of the resulting file. If it is not specially
    specified, the default value will be used. There are two placeholders
    $TIME, the current time, and $UID, the unique number generated by
    the program, to make the resulting filename unique.

Попробовал указать $TIME: поскольку для получения времени используется WMIC.EXE, под ограниченной учётной записью это не работает — вместо $TIME ничего в имя WSF не подставляется (он создаётся и успешно запускается).

Почему вместо WMIC.EXE не использовать псевдопеременную %TIME% (насколько я могу судить, её формат не зависит от региональных настроек, в отличии от %DATE%, да это и не важно)?

(28.11.2012: Всё-таки зависит (от разделителя компонентов времени sTime и разделителя целой и дробной части sDecimal, но не от общего формата времени sTimeFormat), но изменения, даже если они сделаны через Панель управления, на уже запущенный процесс CMD.EXE не влияют, в отличии от %DATE%, которая мгновенно реагирует на изменения параметра sShortDate.)

Исчезла описанная ранее ошибка:

wisgest пишет:

если не завершая работы закрыть окно консоли или запустить новый экземпляр, то он зависает, нагружая процессор.

— при указании $UID у меня вместо него тоже ничего не подставлялось, но если файл с таким именем уже существовал, командный файл зависал, безуспешно пытаясь подобрать новое:

if exist "!wscmd.tmpfile!" goto wscmd.execute.uid

— ага, здесь опять WMIC.EXE и сообщения об ошибках подавляются. Почему в качестве $UID просто не взять целое число (0), наращивая на 1, если файл существует?


Сказанное, не отменяет необходимость, чтобы не собирался мусор, удалять временный WSF, не дожидаясь, пока пользователь введёт «quit()» — более вероятно, что он просто закроет окно. Удалять файл скрипта из него самого вполне безопасно, т.к. он уже запустился.

15 (изменено: wisgest, 2012-11-26 23:21:35)

Re: JScript/VBScript: WSH интерпретатор

P.S.

wisgest пишет:

Почему вместо WMIC.EXE не использовать псевдопеременную %TIME%

В общем-то можно и в самом файле настроек указать не $TIME, а %TIME::=%

16 (изменено: Rumata, 2012-11-27 10:48:45)

Re: JScript/VBScript: WSH интерпретатор

wisgest пишет:

для получения времени используется WMIC.EXE, под ограниченной учётной записью это не работает

это заставляет меня обоснованно ненавидеть рэдмондских волшебников.

wisgest пишет:

Почему вместо WMIC.EXE не использовать псевдопеременную %TIME%

Эта проблема связана с тем, что при запуске WSCMD в конвейера вида wscmd | wscmd возникают коллизии, которые я когда-то описал в теме Проблема создания уникальных значений в конвейерных командах.

wisgest пишет:

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

Там же №42 есть временное обходное решение. Видимо у Вас удалось наблюдать коллизию, которую я предсказывал там же:

Rumata пишет:

WSCRIPT //B - пока единственная более-менее надежная команда, которую можно запустить и которая пока ни разу не привела к коллизиям. Хотя коллизии теоретически возможны и с нею.

( 2 * b ) || ! ( 2 * b )

17 (изменено: wisgest, 2012-11-27 22:28:51)

Re: JScript/VBScript: WSH интерпретатор

Rumata пишет:
wisgest пишет:

Почему вместо WMIC.EXE не использовать псевдопеременную %TIME%

Эта проблема связана с тем, что при запуске WSCMD в конвейера вида wscmd | wscmd возникают коллизии, которые я когда-то описал в теме Проблема создания уникальных значений в конвейерных командах.

Проблема понятна, но это относится к подстановке $UID, а не $TIME —
чем время полученное через WMIC лучше %TIME%?
А если на запуск WMIC не хватит прав $TIME заменится пустой подстрокой.

Rumata пишет:
wisgest пишет:

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

<…> Видимо у Вас удалось наблюдать коллизию, которую я предсказывал там же…

Неужели я так путанно объясняю? Вот полный код процедуры:

:wscmd.execute.uid
    for /f "tokens=1,2 delims==; " %%a in ( 
        '%WMIC% Process call create "%windir%\System32\wscript.exe //b" 2^>nul ^| %FIND% "ProcessId"' 
    ) do (
        set wscmd.uid=%%b
    )

    set wscmd.tmpfile=!wscmd.execute:$UID=%wscmd.uid%!
if exist "!wscmd.tmpfile!" goto wscmd.execute.uid

set wscmd.execute=%wscmd.tmpfile%
goto :EOF

Причина «коллизии» в ошибке, сообщение о которой подавляется:

Ошибка при регистрации mof-файлов.
Использовать WMIC.EXE могут только администраторы.
Причина:Ошибка Win32: Отказано в доступе.

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

18

Re: JScript/VBScript: WSH интерпретатор

wisgest пишет:

Проблема понятна, но это относится к подстановке $UID, а не $TIME —
чем время полученное через WMIC лучше %TIME%?
А если на запуск WMIC не хватит прав $TIME заменится пустой подстрокой.

Ни чем кроме региональных отличий в представлении времени. Я просто не уверен, что время везде представлено в формате ЧЧ:ММ:СС.
Другой момент. Если брать время из переменной %TIME%, то в случае запуска программы в конвейере это значение будет для всех процессов одно. Опять коллизия, от которой я пытался уйти.

wisgest пишет:

Неужели я так путанно объясняю?

Я подумал, что Вы запустили конвейер., а программа пыталась подобрать уникальный UID. Симптомы похожие.

( 2 * b ) || ! ( 2 * b )

19 (изменено: wisgest, 2012-11-28 05:53:47)

Re: JScript/VBScript: WSH интерпретатор

Может вместо мучений с поиском уникальных значений пойти другим путём?

test.bat:

@echo off
setlocal enableextensions
set x=0
:REPEAT
((
  echo,var fso = new ActiveXObject("Scripting.FileSystemObject"^);
  echo,try {fso.DeleteFile(WScript.ScriptFullName, true^);} catch (Err^) {}
  echo,WScript.Echo(WScript.ScriptFullName^);
  echo,WScript.StdErr.WriteLine("[%~1]"^);
  attrib +r "%~f0.%x%.js">nul
)>"%~f0.%x%.js") 2>nul || (set /a "x+=1" & goto REPEAT)
CScript.exe //logo "%~f0.%x%.js"
endlocal

Испытания:

for /l %i in (1 1 20) do @start "%i" cmd /c test.bat %i ^& pause
test 1 | test 2
test 1 1>&2 | test 2

(В последних случаях выводимые строки смешиваются в произвольном порядке: чаще «[1]» предшествует «[2]», иногда наоборот…)

20 (изменено: wisgest, 2012-11-29 06:04:15)

Re: JScript/VBScript: WSH интерпретатор

wisgest пишет:

Может вместо мучений с поиском уникальных значений пойти другим путём?

Даже при практически приемлемых способах получения уникальных значений, остаётся теоретическая вероятность их совпадений. Поэтому при одновременном запуске нескольких экземпляров программы полагаться лишь на предварительную проверку незанятости имени файла не стоит — нужно ещё обрабатывать попытки одновременного доступа к нему. А в этом случае можно не заботится об уникальности начального значения. При этом, если в качестве начального значения можно брать как всегда одно и тоже, так и псевдослучайное, то перебирать значения при неудаче лучше последовательно, не обращаясь более к генератору псевдослучайных чисел (как это имеет место в теме «CMD/BAT: генерация пути для временного файла или папки»), т.к. особенности исполнения генератора псевдослучайных чисел могут допускать возможность его зацикливания.

В приведённом примере попытка записи во временный файл *.js приведёт к ошибке, если он открыт на запись другим процессом или имеет атрибут «Только чтение», причём атрибут нужно устанавливать не закрывая файл, иначе файл может быть перехвачен (используется атрибут, т.к. просто запустить сценарий WSH, пока он ещё открыт на запись не получится), которая (ошибка) обрабатывается изменением (увеличением на 1) переменной части имени временного файла и повтором попытки.

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

21 (изменено: Rumata, 2016-02-15 17:09:17)

Re: JScript/VBScript: WSH интерпретатор

Вернулся к своему интерпретатору. Сделал небольшое, но существенное исправление:

wisgest пишет:

$$$wscmd_.wsf лучше удалять сразу после его запуска

По-прежнему можно скачать архив скрипта, поиграться со скриптом и сообщить о своих впечатлениях. Свежая версия программы всегда доступны из репозитория.

( 2 * b ) || ! ( 2 * b )

22 (изменено: Rumata, 2013-10-04 20:06:09)

Re: JScript/VBScript: WSH интерпретатор

Не один я маюсь. (-:

JavaScript REPL for Windows в трех частях
часть 1
часть 2
часть 3

( 2 * b ) || ! ( 2 * b )

23

Re: JScript/VBScript: WSH интерпретатор

wisgest пишет:

для получения времени используется WMIC.EXE, под ограниченной учётной записью это не работает

Речь идет об этой команде:


WMIC path win32_localtime get Hour,Minute,Second /value

Полагаю, что другие средства не имеют таких ограничений, например:


powershell -command "date -uformat '%H%M%S'"

У меня вопрос к знатокам и пользователям с ограниченной учеткой. Как работает следующая команда именно под ограниченной учеткой. Доступна ли она и ее результат для непривилегированного пользователя?


powershell -command "(gwmi win32_process -filter processid=$pid).parentprocessid"
( 2 * b ) || ! ( 2 * b )

24

Re: JScript/VBScript: WSH интерпретатор

Очень интересная тема! Будем разбираться!
Спасибо вам за труды!

25 (изменено: Rumata, 2020-09-09 17:12:49)

Re: JScript/VBScript: WSH интерпретатор

Достаточно много прошло времени и я вернулся к этому проекту. Задачи прежние:
-- реализация REPL без привлечения сторонних утилит, то есть внутренними средствами самого интерпретатора
-- в некоторой степени приблизить JS/VBS/WSH к определенным возможностям современных интерпретаторов (NodeJS, Rhino, Chakra)

Конечно, NodeJS с его крутыми возможностями значительно выигрывает даже по сравнению с такими крупными аналогами как Rhino (JS, написанный на Java) или Chakra (родная реализация от Microsoft, но все еще внешняя утилита, устанавливаемая вручную).

Предыдущий скрипт был выполнен как гибрид cmd+js, создающий временный wsf-файл, который затем и исполнялся. Здесь я решил немного изменить подход и сделал гибрид cmd+wsf, который сам же все и исполняет. В результате получилось что-то весьма забавное -- js-код, который исполняет js- или vbs-код. Но здесь я добавил несколько дополнительных полезных "плюшек", включил поддержку стандартных команд типа require("...") и console.log(...). Получилось неплохо. При этом "научил" загружать vbs-код.

На будущее хотелось бы реализовать автоматическое раскрытие файловых шаблонов (file globbing), поддержку файла аргументов, REPL для VBS, сборку автономных скриптов js/vbs/wsf/bat, возможность "компилировать" команды в wsc-файл.

В настоящее время проект лежит на гитхабе https://github.com/ildar-shaimordanov/jsxt. Вполне возможно, когда Chakra станет по умолчанию встроенной, я перестану заниматься им.

Сейчас несколько примеров

Посчитать количество строк в каждом файле и в конце вывести суммарный результат (аналог юниксовой команды wc -l:


rem реализация на JS
wsx /n /endfile:"echo(FLN, FILE)" /end:"echo(LN)" ...

rem реализация на VBS
wsx /n /endfile:vbs:"echo FLN, FILE" /end:vbs:"echo LN" ...

Пронумеровать строки (аналогично юниксовой команде cat -bn):


rem реализация на JS
wsx /p /e:"LINE = LN + ':' + LINE" ...

rem реализация на VBS
wsx /let:delim=":" /p /e:vbs:"LINE = LN & delim & LINE" ...

Распечатать первые 10 строк (аналог юникосвой команды head -10):


rem реализация на JS
wsx /let:limit=10 /p /e:"LN > limit && quit()"

rem реализация на VBS
wsx /use:vbs /let:limit=10 /p /e:"if LN > limit then exit : end if"

Обоснованная критика -- принимается, предложения -- обсуждаются, улучшения -- приветствуются. А здесь полный текст скрипта wsx.bat:

+ полный текст скрипта "wsx.bat"

<?xml :
: version="1.0" encoding="utf-8" ?><!--
@echo off
cscript //nologo "%~f0?.wsf" %*
goto :EOF
: -->

<package>
<job id="wsx">
<?job error="false" debug="false" ?>

<script language="javascript"><![CDATA[

var NAME    = 'WSX';
var VERSION = '1.0.1 Alpha';

]]></script>

<runtime>
<description><![CDATA[
WSX: Version 1.0.1 Alpha
Copyright (C) 2009-2015, 2019, 2020 Ildar Shaimordanov

Run an external script file in the same way as it can be done traditionally via "cscript" or "wscript" with additional benefits making its usage closer to NodeJS, Perl, Python etc.

Run itself in the interactive mode. Type in the prompt any JS or VBS commands and execute them immediately. In this mode each entered line is evaluated immediately. To enable many lines executed as one you need to surround them with the double colons "::". The first double colon turns on the multiline mode, the second one turns it off. After that everything entered will be executed.

Run one line program from CLI and apply it on inputstream and other files. One line programs allow to estimate some code on the fly, without creating a temporary file. Writing one line programs you focus on the most important parts of the program implementation. Some implementation stuff -- like objects initialization, I/O operations etc -- are hidden on your eyes, however executed yet implicitly.

If the tool is launched with the one line program, everything after is assumed as a file name. Each argument is opened as a file and processed line by line until the end of file. Otherwise, if no any one line program is entered, the first item of the argument list is the script file and the rest of arguments are arguments for the script file. They could be everything and the script can use them accordingly its functionality.

For more convenience there are few predefined global definitions:

Common objects:

FSO     - The object "Scripting.FileSystemObject"
STDIN   - The reference to "WScript.StdIn"
STDOUT  - The reference to "WScript.StdOut"
STDERR  - The reference to "WScript.StdErr"

Common helper functions:

usage(), help()          - Display this help
echo(), print(), alert() - Print expressions
quit(), exit()           - Quit this shell
cmd(), shell()           - Run a command or DOS-session
sleep(n)                 - Sleep n milliseconds
clip()                   - Read from or write to clipboard
gc()                     - Run the JScript garbage collector

ERROR   - The variable keeping the last error
USE     - The instance of "Importer" class to import VBS easier
ARGV    - The CLI arguments

Used in the loop mode:

STREAM  - The reference to the stream of the current file
FILE    - The name of the current file
FILEFMT - The format to open files ("ascii", "unicode" or system "default")
LINE    - The current line
FLN     - The line number in the current file
LN      - The total line number

These special functions can be used on the loop mode only to cover the issue when we can't use "continue" and "break".

next()  - The "continue" operator
last()  - The "break" operator

Used in REPL:

The interactive mode provides the following useful properties for referencing to the history of the commands and REPL mode:

REPL.number  - the current line number
REPL.history - the list of all commands entered in the current session
REPL.quiet   - the current session mode

The CLI options supplying the program parts for execution could be infixed with the engine identifier ("js" or "vbs") supposed to be used for processing these options. See examples below.

The name explanation:

Following the old good tradition to explain acronyms recursively "WSX" means "WSX Simulates eXecutable".

]]></description>
<example><![CDATA[
Examples:

- Run interactively:
  wsx

- Count the number of lines (similar to "wc -l", the unix tool):
  wsx /n /endfile:"echo(FLN, FILE)" /end:"echo(LN)"
  wsx /n /endfile:vbs:"echo FLN, FILE" /end:vbs:"echo LN"
  wsx /use:vbs /n /endfile:"echo FLN, FILE" /end:"echo LN"

- Numerate lines of each input file (VScript example shows how to bypass the trouble with quotes within quotes):
  wsx /p /e:"LINE = LN + ':' + LINE"
  wsx /let:delim=":" /p /e:vbs:"LINE = LN & delim & LINE"

- Print first 10 lines (similar to "head", the unix tool):
  wsx /let:limit=10 /p /e:"LN > limit && quit()"
  wsx /use:vbs /let:limit=10 /p /e:"if LN > limit then exit : end if"

- Print last 10 lines (similar to "tail", the unix tool):
  wsx /let:limit=10 /n /beginfile:"lines=[]" /e:"lines.push(LINE); lines.length > limit && lines.shift()" /endfile:"echo(lines.join('\n'))"
]]></example>
<named
	name="help"
	helpstring="Print this help and exit (&#34;/h&#34; shortcut)"
	type="simple"
	required="false"
	/>
<named
	name="version"
	helpstring="Print version information and exit"
	type="simple"
	required="false"
	/>
<named
	name="dry-run"
	helpstring="Show in pseudocode what is going to be executed"
	type="simple"
	required="false"
	/>
<!--
<named
	name="compile"
	helpstring="Compile and store to another file without execution"
	type="simple"
	required="false"
	/>
-->
<named
	name="quiet"
	helpstring="Be quiet in the interactive mode (&#34;/q&#34; shortcut)"
	type="simple"
	required="false"
	/>
<named
	name="use"
	helpstring="Use the engine (&#34;js&#34; or &#34;vbs&#34;)"
	type="string"
	required="false"
	/>
<named
	name="m"
	helpstring="Load the module (similar to &#34;require(...)&#34; in NodeJS)"
	type="string"
	required="false"
	/>
<named
	name="let"
	helpstring="Assign the value: &#34;name=value&#34;"
	type="string"
	required="false"
	/>
<named
	name="set"
	helpstring="Create the object: &#34;name=CreateObject(object)&#34;"
	type="string"
	required="false"
	/>
<named
	name="get"
	helpstring="Get the object: &#34;name=GetObject(object)&#34;"
	type="string"
	required="false"
	/>
<named
	name="e"
	helpstring="One line program (multiple &#34;/e&#34;'s supported)"
	type="string"
	required="false"
	/>
<named
	name="n"
	helpstring="Apply a program in a loop &#34;while read LINE { ... }&#34;"
	type="simple"
	required="false"
	/>
<named
	name="p"
	helpstring="Apply a program in a loop &#34;while read LINE { ... print }&#34;"
	type="simple"
	required="false"
	/>
<named
	name="begin"
	helpstring="The code for executing before the loop"
	type="string"
	required="false"
	/>
<named
	name="end"
	helpstring="The code for executing after the loop"
	type="string"
	required="false"
	/>
<named
	name="beginfile"
	helpstring="The code for executing before each file"
	type="string"
	required="false"
	/>
<named
	name="endfile"
	helpstring="The code for executing after each file"
	type="string"
	required="false"
	/>
<unnamed
	name="scriptfile"
	helpstring="The script file"
	required="false"
	/>
<!--
<named
	name="@"
	helpstring="Read arguments from the specified file"
	type="string"
	required="false"
	/>
-->
<named
	name="f"
	helpstring="Open a file as &#34;ascii&#34;, &#34;unicode&#34; or using system &#34;default&#34;"
	type="string"
	required="false"
	/>
<unnamed
	name="arguments"
	helpstring="Other arguments to be passed to the program"
	required="false"
	/>
</runtime>

<!-- <script language="javascript" src="./wsx/Helpers.js"></script> -->
<script language="javascript"><![CDATA[
//
// Set of useful and convenient definitions
// This script is the part of the wsx
//
// Copyright (c) 2019, 2020 by Ildar Shaimordanov
//

var FSO = new ActiveXObject('Scripting.FileSystemObject');

var STDIN = WScript.StdIn;
var STDOUT = WScript.StdOut;
var STDERR = WScript.StdErr;

var usage = help = (function() {
	var helpMsg = [
		  'Commands                 Descriptions'
		, '========                 ============'
		, 'usage(), help()          Display this help'
		, 'echo(), print(), alert() Print expressions'
		, 'quit(), exit()           Quit this shell'
		, 'cmd(), shell()           Run a command or DOS-session'
		, 'sleep(n)                 Sleep n milliseconds'
		, 'clip()                   Read from or write to clipboard'
		, 'gc()                     Run the JScript garbage collector'
	].join('\n');

	return function() {
		WScript.Echo(helpMsg);
	};
})();

var echo = print = alert = (function() {
	var slice = Array.prototype.slice;

	return function() {
		WScript.Echo(slice.call(arguments));
	};
})();

var quit = exit = function(exitCode) {
	WScript.Quit(exitCode);
};

var cmd = shell = function(command) {
	var shell = new ActiveXObject('WScript.Shell');
	shell.Run(command || '%COMSPEC%');
};

var sleep = function(time) {
	return WScript.Sleep(time);
};

var clip = function(text) {
	if ( typeof text == 'undefined' ) {
		return new ActiveXObject('htmlfile').parentWindow.clipboardData.getData('Text');
	}

	// Validate a value is integer in the range 1..100
	// Otherwise, defaults to 20
	var clamp = function(x) {
		x = Number(x);
		if ( isNaN(x) || x < 1 || x > 100 ) {
			x = 20;
		}
		return x;
	};

	var WAIT1 = clamp(clip.WAIT_READY);
	var WAIT2 = clamp(clip.WAIT_LOADED);

	// Borrowed from https://stackoverflow.com/a/16216602/3627676
	var msie = new ActiveXObject('InternetExplorer.Application');
	msie.silent = true;
	msie.Visible = false;
	msie.Navigate('about:blank');

	// Wait until MSIE ready
	while ( msie.ReadyState != 4 ) {
		WScript.Sleep(WAIT1);
	}

	// Wait until document loaded
	while ( msie.document.readyState != 'complete' ) {
		WScript.Sleep(WAIT2);
	}

	msie.document.body.innerHTML = '<textarea id="area" wrap="off" />';
	var area = msie.document.getElementById('area');
	area.value = text;
	area.select();
	area = null;

	// 12 - "Edit" menu, "Copy" command
	//  0 - the default behavior
	msie.ExecWB(12, 0);
	msie.Quit();
	msie = null;
};

var gc = CollectGarbage;

if ( typeof exports != "undefined" ) {
	exports.FSO = FSO;
	exports.STDIN = STDIN;
	exports.STDOUT = STDOUT;
	exports.STDERR = STDERR;

	exports.usage = usage;
	exports.help = help;

	exports.echo = echo;
	exports.alert = alert;
	exports.print = print;

	exports.quit = quit;
	exports.exit = exit;

	exports.cmd = cmd;
	exports.shell = shell;

	exports.sleep = sleep;
	exports.clip = clip;
	exports.gc = gc;
}
]]></script>

<!-- <script language="javascript" src="./core/console.js"></script> -->
<script language="javascript"><![CDATA[
//
// console.js
// Imitation of the NodeJS console in the Windows Scripting Host
//
// Copyright (c) 2012, 2013, 2019, 2020 by Ildar Shaimordanov
//

/*

The module adds the useful NodeJS console features to WSH

"console.log(), console.debug(), ..." can be used the same way as used in NodeJS.

Log messages with custom icon depending on the function used.
	console.log(object[, object, ...])
	console.debug(object[, object, ...])
	console.info(object[, object, ...])
	console.warn(object[, object, ...])
	console.error(object[, object, ...])

Test if the expression. If it is false, the info will be logged in the console
	console.assert(expression[, object, ...])

Starts and stops a timer and writes the time elapsed
	console.time(name)
	console.timeEnd(name)

Customizing the console
	console.fn

Checks that the object is a formatting string
	console.fn.isFormat(object)

The simplest formatting function immitating C-like format
	console.fn.format(pattern, objects)

Details for the complex object
	console.fn.inspect(object)

The low-level printing function
	console.fn.print(msgType, msg)

The deep of nestion for complex structures (default is 5)
	console.fn.deep

The initial value of indentation (4 whitespaces, by default).
A numeric value defines indentaion size, the number of space chars.
	console.fn.space

Numeric values controls the visibility of functions. Defaults to 0.
(0 - do not show function, 1 - show [Function] string, 2 - show a details)
	console.fn.func

The visibility properties from the prototype of the oject. Defaults to 0.
(0 - do not show properties from prototype, 1 - show then)
	console.fn.proto

The string to glue the set of arguments when output them
	console.fn.separator = ' '

The following functions are not implemented:
	console.clear
	console.count
	console.dir
	console.dirxml
	console.group
	console.groupCollapsed
	console.groupEnd
	console.profile
	console.profileEnd
	console.table
	console.trace

*/

var console = console || (function() {

	var entities = {
		'&': '&amp;', 
		'"': '&quot;', 
		'<': '&lt;', 
		'>': '&gt;'
	};

	var escaped = /[\x00-\x1F\"\\]/g;
	var special = {
		'"': '\\"', 
		'\r': '\\r', 
		'\n': '\\n', 
		'\t': '\\t', 
		'\b': '\\b', 
		'\f': '\\f', 
		'\\': '\\\\'
	};

	var space;
	var indent = '';

	var deep;

	var proto;
	var func;

	function _quote(value) {
		var result = value.replace(escaped, function($0) {
			return special[$0] || $0;
		});
		return '"' + result + '"';
	};

	// The main method for printing objects
	var inspect = function(object) {
		switch (typeof object) {
		case 'string':
			return _quote(object);

		case 'boolean':
		case 'number':
		case 'undefined':
		case 'null':
			return String(object);

		case 'function':
			if ( func == 1 ) {
				return '[Function]';
			}
			if ( func > 1 ) {
				return object.toString();
			}
			return '';

		case 'object':
			if ( object === null ) {
				return String(object);
			}

			// Assume win32 COM objects
			if ( object instanceof ActiveXObject ) {
				return '[ActiveXObject]';
			}

			var t = Object.prototype.toString.call(object);

			// Assume the RegExp object
			if ( t == '[object RegExp]' ) {
				return String(object);
			}

			// Assume the Date object
			if ( t == '[object Date]' ) {
				return object.toUTCString();
			}

			// Stop the deeper nestings
			if ( ! deep ) {
				return '[...]';
			}

			var saveDeep = deep;
			deep--;

			var saveIndent = indent;
			indent += space;

			var result = [];
			for (var k in object) {
				if ( ! object.hasOwnProperty(k) && ! proto ) {
					continue;
				}

				var v;

				if ( object[k] === object ) {
					v = '[Recursive]';
				} else {
					v = inspect(object[k]);
					if ( v === '' ) {
						// Sure that any property will return non-empty string
						// Only functions can return an empty string when func == 0
						continue;
					}
				}

				result.push(k + ': ' + v);
			}

			var pred;
			var post;

			if ( t == '[object Array]' ) {
				pred = 'Array(' + object.length + ') [';
				post = ']';
			} else {
				pred = 'Object {';
				post = '}';
			}

			result = result.length == 0 
				? '\n' + saveIndent 
				: '\n' + indent + result.join('\n' + indent) + '\n' + saveIndent;

			indent = saveIndent;
			deep = saveDeep;

			return pred + result + post;

		default:
			return '[Unknown]';
		}
	};

	// This regular expression is used to recongnize a formatting string 
	var reFormat = /%%|%(\d+)?([idfxso])/g;

	// Checks that the object is a formatting string
	var isFormat = function(object) {
		return Object.prototype.toString.call(object) == '[object String]' && object.match(reFormat);
	};

	var formatters = {};
	formatters.i = function(v) { return Number(v).toFixed(0); };
	formatters.d = 
	formatters.f = function(v) { return Number(v).toString(10); };
	formatters.x = function(v) { return Number(v).toString(16); }, 
	formatters.o = inspect;
	formatters.s = function(v) { return String(v); };

	// The formatting function immitating C-like "printf"
	var format = function(pattern, objects) {
		var i = 0;
		return pattern.replace(reFormat, function(format, width, id) {
			if ( format == '%%' ) {
				return '%';
			}

			i++;

			var r = formatters[id](objects[i]);

			return r;
		});
	};

	// The low-level printing function
	var print = function(msgType, msg) {
		WScript.Echo(msg);
	};


	// The core function
	var printMsg = function(msgType, objects) {
		// Get the actual configuration of the console
		var fn = console.fn || {};

		var sep = fn.separator || ' ';

		space = fn.space;
		deep = Number(fn.deep) > 0 ? fn.deep : 5;
		proto = fn.proto || 0;
		func = fn.func || 0;

		var t = Object.prototype.toString.call(space);
		if ( t == '[object Number]' && space >= 0 ) {
			space = new Array(space + 1).join(' ');
		} else if ( t != '[object String]' ) {
			space = '    ';
		}

		if ( typeof fn.isFormat == 'function' ) {
			isFormat = fn.isFormat;
		}
		if ( typeof fn.format == 'function' ) {
			format = fn.format;
		}
		if ( typeof fn.inspect == 'function' ) {
			inspect = fn.inspect;
		}
		if ( typeof fn.print == 'function' ) {
			print = fn.print;
		}

		var result;

		if ( isFormat(objects[0]) ) {
			//result = format(objects[0], Array.prototype.slice.call(objects, 1));
			result = format(objects[0], objects);
		} else {
			result = [];
			for (var i = 0; i < objects.length; i++) {
				result.push(inspect(objects[i]));
			}
			result = result.join(sep);
		}

		print(msgType, result);
	};


	var log = function() {
		printMsg(0, arguments);
	};

	var debug = log;

	var info = function() {
		printMsg(64, arguments);
	};

	var warn = function() {
		printMsg(48, arguments);
	};

	var error = function() {
		printMsg(16, arguments);
	};


	// If the expression is false, the rest of arguments will be logged in 
	// the console
	var assert = function(expr) {
		if ( expr ) {
			return;
		}
		error.apply(console, arguments.length < 2 ? ['Assertion error'] : Array.prototype.slice.call(arguments, 1));
	};


	// Processing of timers start/stop
	var timeNames = {};

	var timeStart = function(name) {
		if ( ! name ) {
			return;
		}

		timeNames[name] = new Date();
		log(name + ': Timer started');
	};

	var timeEnd = function(name) {
		if ( ! name || ! timeNames.hasOwnProperty(name) ) {
			return;
		}

		var t = new Date() - timeNames[name];
		delete timeNames[name];

		log(name + ': ' + t + 'ms');
	};


	return {
		// Implemented methods
		log: log, 
		debug: debug, 
		info: info, 
		warn: warn, 
		error: error, 

		assert: assert, 

		time: timeStart, 
		timeEnd: timeEnd, 

		// Not implemented methods
		clear: function() {}, 

		dir: function() {}, 
		dirxml: function() {}, 

		group: function() {}, 
		groupCollapsed: function() {}, 
		groupEnd: function() {}, 

		profile: function() {}, 
		profileEnd: function() {}, 

		table: function() {}, 

		trace: function() {}, 

		// Customizing the console
		fn: {
			space: 4,
			deep: 5,
			proto: 0,
			func: 0,
			separator: ' '
		}
	};

})();

if ( typeof module != "undefined" ) {
	module.exports = console;
}
]]></script>

<!-- <script language="javascript" src="./core/require.js"></script> -->
<script language="javascript"><![CDATA[
//
// require.js
// Imitation of the NodeJS function require in the Windows Scripting Host
//
// Copyright (c) 2019, 2020 by Ildar Shaimordanov
//

/*

The module adds to WSH the "require" function, the useful NodeJS feature, and some additional extensions.

Load a module by name of filename
	require(id[, options])

Resolve a location of the module
	require.resolve(id[, options])

The list of of paths used to resolve the module location
	require.paths

Cache for the imported modules
	require.cache

*/

var require = require || (function() {

	var fso = WScript.CreateObject("Scripting.FileSystemObject");

	function loadFile(file, format) {
		var stream = fso.OpenTextFile(file, 1, false, format || 0);
		var text = stream.ReadAll();
		stream.Close();

		return text;
	};

	/**
	 * Load a module by name of filename
	 *
	 * @param	<String>	module name or path
	 * @param	<Object>	options
	 * @return	<Object>	exported module content
	 *
	 * Available options:
	 * - paths	<Array>		Paths to resolve the module location
	 * - format	<Integer>	format of the opened file (-2 - system default, -1 - Unicode file, 0 - ASCII file)
	 */
	function require(id, options) {
		if ( ! id ) {
			throw new TypeError("Missing path");
		}

		if ( typeof id != "string" ) {
			throw new TypeError("Path must be a string");
		}

		options = options || {};

		var file = require.resolve(id, options);

		require.cache = require.cache || {};

		if ( ! require.cache[file] || ! require.cache[file].loaded ) {
			var text = loadFile(file, options.format);

			var code
				= "(function(module) {\n"
				+ "var exports = module.exports;\n"
				+ text + ";\n"
				+ "return module.exports;\n"
				+ "})({ exports: {} })";

			var evaled = eval(code);

			require.cache[file] = {
				exports: evaled,
				loaded: true
			};
		}

		return require.cache[file].exports;
	};

	function absolutePath(file) {
		if ( fso.FileExists(file) ) {
			return fso.GetAbsolutePathName(file);
		}
	}

	/**
	 * Resolve a location of the module
	 *
	 * @param	<String>	module name or path
	 * @param	<Object>	options
	 * @return	<String>	resolved location of the module
	 *
	 * Available options:
	 * - paths	<Array>		Paths to resolve the module location
	 */
	require.resolve = function(id, options) {
		options = options || {};

		var file = /[\\\/]|\.js$/i.test(id)

			// module looks like a path
			? absolutePath(/\.[^.\\\/]+$/.test(id) ? id : id + ".js")

			// attempt to find a librarian module
			: (function() {
				var paths = [].concat(options.paths || [], require.paths);
				for (var i = 0; i < paths.length; i++) {
					var file = absolutePath(paths[i] + "\\" + id + ".js");
					if ( file ) {
						return file;
					}
				}
			})();

		if ( ! file ) {
			throw new Error("Cannot find module '" + id + "'");
		}

		return file;
	};

	var myDir = fso.GetParentFolderName(WScript.ScriptFullName);
	var me = WScript.ScriptName.replace(/(\.[^.]+\?)?\.[^.]+$/, '');

	var shell = WScript.CreateObject ("WScript.Shell");
	var cwd = shell.CurrentDirectory;

	require.paths = [
		  myDir + "\\js"
		, myDir + "\\" + me
		, myDir + "\\" + me + "\\js"
		, myDir + "\\lib"
		, cwd
	];

	if ( fso.GetBaseName(myDir) == "bin" ) {
		require.paths.push(myDir + "\\..\\lib");
	}

	return require;
})();
]]></script>
<!-- <script language="vbscript" src="./core/importer.vbs"></script> -->
<script language="vbscript"><![CDATA[
'
' importer.js
' Import modules by name or filename similar to the NodeJS "require"
'
' Copyright (c) 2019, 2020 by Ildar Shaimordanov
'
' @see
' https://blog.ctglobalservices.com/scripting-development/jgs/include-other-files-in-vbscript/
' https://helloacm.com/include-external-files-in-vbscriptjscript-wsh/
' https://www.planetcobalt.net/sdb/importvbs.shtml
' https://stackoverflow.com/a/316169/3627676
' https://stackoverflow.com/a/43957897/3627676
' https://riptutorial.com/vbscript/topic/8345/include-files


' Create and return an instance of the Importer class
' Can be useful to import VBScript modules from JScript
'
' @return	<Importer>
Function CreateImporter
	Set CreateImporter = New Importer
End Function


Class Importer
	' For caching already loaded modules
	Private cache

	' File system object, used internally
	Private fso

	' The list of of paths used to resolve the module location
	Public paths

	' format of the opened file
	' (-2 - system default, -1 - Unicode file, 0 - ASCII file)
	Public format

	' Initialize importer
	Private Sub Class_Initialize
		Set cache = CreateObject("Scripting.Dictionary")
		Set fso = CreateObject("Scripting.FileSystemObject")

		Dim re
		Set re = New RegExp
		re.Pattern = "(\.[^.]+\?)?\.[^.]+$"

		Dim mydir, myself

		mydir = fso.GetParentFolderName(WScript.ScriptFullName)
		myself = re.Replace(WScript.ScriptName, "")

		Dim shell, cwd

		Set shell = WScript.CreateObject("WScript.Shell")
		cwd = shell.CurrentDirectory

		paths = Array( _
			  mydir & "\vbs" _
			, mydir & "\" & myself _
			, mydir & "\" & myself & "\vbs" _
			, mydir & "\lib" _
			, cwd _
		)
		If fso.GetBaseName(mydir) = "bin" Then
			ReDim Preserve paths(UBound(paths) + 1)
			paths(UBound(paths)) = mydir & "\..\lib"
		End If
	End Sub

	' Destroy importer
	Private Sub Class_Terminate
		Set paths = Nothing
		Set fso = Nothing
		Set cache = Nothing
	End Sub

	Private Sub PathIncrement(insert, apath)
		Dim gain
		If IsArray(apath) Then
			gain = UBound(apath) + 1
		Else
			gain = 1
		End If

		Dim count
		count = UBound(paths)

		Redim Preserve paths(count + gain)

		Dim i

		If insert = 1 Then
			For i = count To 0 Step -1
				paths(i + gain) = paths(i)
			Next
			count = 0
		End If

		If IsArray(apath) Then
			For i = 0 To gain - 1
				paths(count + i) = apath(i)
			Next
		Else
			paths(count) = apath
		End If
	End Sub

	' Add a path or array of paths to the begin of the list of paths
	'
	' @param	<String|Array>	Path or array of paths
	Public Sub PathInsert(apath)
		PathIncrement 1, apath
	End Sub

	' Add a path or array of paths to the end of the list of paths
	'
	' @param	<String|Array>	Path or array of paths
	Public Sub PathAppend(apath)
		PathIncrement 0, apath
	End Sub

	' Load a text
	'
	' @param	<String>	a text to be executed
	Public Sub Execute(text)
		On Error GoTo 0
		ExecuteGlobal text
		On Error Resume Next
	End Sub

	' Load a module by name of filename
	'
	' @param	<String>	module name or path
	Public Sub Import(id)
		On Error GoTo 0

		Dim Name

		Dim ErrStr

		Select Case VarType(id)
		Case vbString
			Name = id
		Case vbEmpty
			ErrStr = "Missing path (Empty)"
		Case vbNull
			ErrStr = "Missing path (Null)"
		Case Else
			ErrStr = "Path must be a string"
		End Select

		If ErrStr <> "" Then
			Err.Description = ErrStr
			Err.Raise vbObjectError
		End If

		Dim file
		file = Resolve(id)

		If Not cache.Exists(file) Then
			Dim text
			text = ReadFile(file)
			ExecuteGlobal text

			cache.Add file, 1
		End If

		On Error Resume Next
	End Sub

	' Read a file
	Private Function ReadFile(file)
		Dim stream
		Set stream = fso.OpenTextFile(file, 1, False, format)
		ReadFile = stream.ReadAll
		stream.Close
		Set stream = Nothing
	End Function

	' Resolve a location of the module
	'
	' @param	<String>	module name or path
	' @return	<String>	resolved location of the module
	Public Function Resolve(id)
		Dim file

		Dim re
		Set re = New RegExp

		re.Pattern = "[\\\/]|\.vbs$"
		re.IgnoreCase = True
		If re.Test(id) Then
			' module looks like a path
			re.Pattern = "\.[^.\\\/]+$"
			If re.Test(id) Then
				file = id
			Else
				file = id & ".vbs"
			End If
			Resolve = AbsolutePath(file)
		Else
			' attempt to load a librarian module
			Dim path
			For Each path In paths
				file = path & "\" & id & ".vbs"
				Resolve = AbsolutePath(file)
				If Resolve <> "" Then
					Exit For
				End If
			Next
		End If

		If Resolve = "" Then
			Err.Description = "Cannot find module '" & id & "'"
			Err.Raise vbObjectError
		End If
	End Function

	' Return the absolute path if file exists, otherwise - empty string
	Private Function AbsolutePath(file)
		AbsolutePath = ""
		If fso.FileExists(file) Then
			AbsolutePath = fso.GetAbsolutePathName(file)
		End If
	End Function

End Class
]]></script>

<!-- <script language="javascript" src="./wsx/Program.js"></script> -->
<script language="javascript"><![CDATA[
//
// Program
// This script is the part of the wsx
//
// Copyright (c) 2019, 2020 by Ildar Shaimordanov
//

var Program = {
	dryRun: false,
	quiet: false,
	inLoop: 0,

	engine: "js",

	modules: [],
	vars: [],

	main: [],

	begin: [],
	end: [],
	beginfile: [],
	endfile: [],

	setEngine: function(engine) {
		this.engine = engine;
	},
	getEngine: function(engine) {
		return (engine || this.engine).toLowerCase();
	},

	setMode: function(mode) {
		var loopTypes = { i: 0, n: 1, p: 2 };
		this.inLoop = loopTypes[mode];
	},

	setQuiet: function() {
		this.quiet = true;
	},

	addScript: function(engine, name) {
		name = name.replace(/\\/g, '\\\\');
		var result = '';
		if ( this.getEngine(engine) == "vbs" ) {
			result = 'USE.Import("' + name + '")';
		} else {
			result = 'require("' + name + '")';
		}
		return result;
	},
	addModule: function(engine, name) {
		this.modules.push(this.addScript(engine, name));
	},

	vbsVar: function(name, value, setter) {
		var result = 'Dim ' + name;
		if ( value ) {
			switch( setter.toLowerCase() ) {
			case 'let': result += ' : ' + name + ' = \\"' + value + '\\"'; break;
			case 'set': result += ' : Set ' + name + ' = CreateObject(\\"' + value + '\\")'; break;
			case 'get': result += ' : Set ' + name + ' = GetObject(\\"' + value + '\\")'; break;
			}
		}
		return 'USE.Execute("' + result + '")';
	},
	jsVar: function(name, value, setter) {
		//var result = 'var ' + name;
		var result = ''
		if ( value ) {
			result = name;
			switch( setter.toLowerCase() ) {
			case 'let': result += ' = "' + value + '"'; break;
			case 'set': result += ' = new ActiveXObject("' + value + '")'; break;
			case 'get': result += ' = GetObject("' + value + '")'; break;
			}
		}
		return result;
	},
	addVar: function(engine, name, value, setter) {
		var result;
		if ( this.getEngine(engine) == "vbs" ) {
			result = this.vbsVar(name, value, setter);
		} else {
			result = this.jsVar(name, value, setter);
		}
		this.vars.push(result);
	},

	addCode: function(engine, code, region) {
		var result = '';
		if ( this.getEngine(engine) == "vbs" ) {
			result = 'USE.Execute("' + code + '")';
		} else {
			result = code;
		}
		this[region || 'main'].push(result);
	},

	detectScriptFile: function(args) {
		if ( this.main.length ) {
			return;
		}
		if ( this.inLoop ) {
			return;
		}
		if ( args.length ) {
			var scriptFile = args.shift();
			var engine = /\.vbs$/.test(scriptFile) ? 'vbs' : 'js';
			this.main.push(this.addScript(engine, scriptFile));
		}
	}
};
]]></script>
<!-- <script language="javascript" src="./wsx/Runner.js"></script> -->
<script language="javascript"><![CDATA[
//
// Code processor: Runner
// This script is the part of the wsx
//
// Copyright (c) 2019, 2020 by Ildar Shaimordanov
//

var Runner = function(Program, argv) {
	if ( Program.dryRun ) {
		Runner.dump(Program);
		return;
	}

	var modules = Program.modules.join(';\n');
	var vars = Program.vars.join(';\n');
	var begin = Program.begin.join(';\n');
	var beginfile = Program.beginfile.join(';\n');
	var main = Program.main.join(';\n');
	var endfile = Program.endfile.join(';\n');
	var end = Program.end.join(';\n');

	/*
	The following variables are declared without the keyword "var". So
	they become global and available for all codes in JScript and VBScript.
	*/

	// Helper to simplify VBS importing
	USE = CreateImporter();

	// Keep a last exception
	ERROR = null;

	// Reference to CLI arguments
	ARGV = argv;

	/*
	Load provided modules
	Set user-defined variables
	*/
	eval(modules);
	eval(vars);

	if ( Program.main.length == 0 && Program.inLoop == false ) {
		/*
		Run REPL
		*/
		REPL.quiet = Program.quiet;
		REPL();
		return;
	}

	if ( ! Program.inLoop ) {
		/*
		Load the main script and do nothing more.
		*/
		eval(main);
		return;
	}

	// The currently open stream
	STREAM = null;

	// The current filename, file format and file number
	FILE = '';
	FILEFMT = 0;

	// The current line read from the stream
	LINE = '';

	// The line number in the current file
	FLN = 0;

	// The total line number
	LN = 0;

	// Emulate the "continue" operator
	next = function() {
		throw new EvalError('next');
	};

	// Emulate the "break" operator
	last = function() {
		throw new EvalError('last');
	};

	/*
	Execute the code before starting to process any file.
	This is good place to initialize.
	*/
	eval(begin);

	if ( ! ARGV.length ) {
		ARGV.push('con');
	}

	while ( ARGV.length ) {
		FILE = ARGV.shift();

		var m = FILE.match(/^\/f:(ascii|unicode|default)$/i);

		if ( m ) {
			var fileFormats = { ascii: 0, unicode: -1, 'default': -2 };
			FILEFMT = fileFormats[ m[1] ];
			continue;
		}

		FLN = 0;

		/*
		Execute the code before starting to process the file.
		We can do here something while the file is not opened.
		*/
		eval(beginfile);

		try {
			STREAM = FILE.toLowerCase() == 'con'
				? STDIN
				: FSO.OpenTextFile(FILE, 1, false, FILEFMT);
		} catch (ERROR) {
			WScript.Echo(ERROR.message + ': ' + FILE);
			continue;
		}

		/*
		Prevent failure of reading out of STDIN stream
		The real exception number is 800a005b (-2146828197)
		"Object variable or With block variable not set"
		*/
		//// NEED MORE INVESTIGATION
		//try {
		//	stream.AtEndOfStream;
		//} catch (ERROR) {
		//	WScript.StdErr.WriteLine('Out of stream: ' + file);
		//	continue;
		//}

		while ( ! STREAM.AtEndOfStream ) {
			FLN++;
			LN++;

			LINE = STREAM.ReadLine();

			/*
			Execute the main code per each input line.
			*/
			try {
				eval(main);
			} catch (ERROR) {
				if ( ERROR instanceof EvalError && ERROR.message == 'next' ) {
					continue;
				}
				if ( ERROR instanceof EvalError && ERROR.message == 'last' ) {
					break;
				}
				throw ERROR;
			}

			if ( Program.inLoop == 2 ) {
				WScript.Echo(LINE);
			}
		}

		if ( STREAM != STDIN ) {
			STREAM.Close();
		}

		/*
		Execute the code when the file is already closed. We can do
		some finalization (i.e.: print the number of lines in the file).
		*/
		eval(endfile);
	}

	/*
	Execute the code when everything is completed.
	We can finalize the processing (i.e.: print the total number of lines).
	*/
	eval(end);
};

Runner.dump = function(Program) {
	var s = [];

	function dumpCode(code) {
		if ( code.length ) {
			s.push(code.join(';\n'));
		}
	}

	dumpCode(Program.modules);
	dumpCode(Program.vars);

	if ( Program.inLoop ) {
		dumpCode(Program.begin);
		s.push('::foreach FILE do');
		dumpCode(Program.beginfile);
		s.push('::while read LINE do');
	}

	if ( Program.main.length == 0 && Program.inLoop == false ) {
		s = s.concat([
			'::while read',
			'::eval',
			'::print',
			'::loop while'
		]);
	}

	dumpCode(Program.main);

	if ( Program.inLoop == 2 ) {
		s.push('::print LINE');
	}

	if ( Program.inLoop ) {
		s.push('::loop while');
		dumpCode(Program.endfile);
		s.push('::loop foreach');
		dumpCode(Program.end);
	}

	WScript.Echo(s.join('\n'));
};
]]></script>
<!-- <script language="javascript" src="./wsx/REPL.js"></script> -->
<script language="javascript"><![CDATA[
//
// Code processor: REPL
// This script is the part of the wsx
//
// Copyright (c) 2019, 2020 by Ildar Shaimordanov
//

var REPL = function() {
	if ( ! WScript.FullName.match(/cscript.exe$/i) ) {
		WScript.Echo('REPL works with cscript only');
		WScript.Quit();
	}

	while ( true ) {

		try {

			(function(storage, result) {
				/*
				A user can modify the codes of these methods so
				to prevent the script malfunctioning we have
				to keep their original codes and restore them later
				*/
				eval = storage.eval;
				REPL = storage.REPL;

				if ( result === void 0 ) {
					return;
				}

				if ( result && typeof result == 'object'
				&& console && typeof console == 'object'
				&& typeof console.log == 'function' ) {
					console.log(result);
				} else {
					WScript.StdOut.WriteLine('' + result);
				}
			})({
				eval: eval,
				REPL: REPL
			},
			eval((function(PS1, PS2) {

				if ( REPL.quiet ) {
					PS1 = '';
					PS2 = '';
				} else {
					var me = WScript.ScriptName;
					PS1 = me + ' js > ';
					PS2 = me + ' js :: ';
				}

				/*
				The REPL.history can be changed by the user as he
				can. We should prevent a concatenation with the one
				of the empty values such as undefined, null, etc.
				*/
				if ( ! REPL.history || ! ( REPL.history instanceof [].constructor ) ) {
					REPL.history = [];
				}

				/*
				The REPL.number can be changed by the user as he can.
				We should prevent an incrementing of non-numeric values.
				*/
				if ( ! REPL.number || typeof REPL.number != 'number' ) {
					REPL.number = 0;
				}

				/*
				The line consisting of two colons only switches the
				multiline mode. The first entry of double colons
				means turn on the multiline mode. The next entry
				turns off. In the multiline mode it's possible to
				type a code of few lines without inpterpreting.
				*/
				var multiline = false;

				/*
				Storages for:
				-- one input line
				-- one or more input lines as array
				   (more than one are entered in multiline mode)
				-- the resulting string of all entered lines
				   (leading and trailing whitespaces are trimmed)
				*/
				var input = [];
				var inputs = [];
				var result = '';

				WScript.StdOut.Write(PS1);

				while ( true ) {

					try {
						REPL.number++;
						input = [];
						while ( ! WScript.StdIn.AtEndOfLine ) {
							input[input.length] = WScript.StdIn.Read(1);
						}
						WScript.StdIn.ReadLine();
					} catch (ERROR) {
						input = [ 'WScript.Quit()' ];
					}

					if ( input.length == 2 && input[0] + input[1] == '::' ) {
						input = [];
						multiline = ! multiline;
					}

					if ( inputs.length ) {
						inputs[inputs.length] = '\n';
					}
					for (var i = 0; i < input.length; i++) {
						inputs[inputs.length] = input[i];
					}

					if ( ! multiline ) {
						break;
					}

					WScript.StdOut.Write(PS2);

				} // while ( true )

				// Trim left
				var k = 0;
				while ( inputs[k] <= ' ' ) {
					k++;
				}
				// Trim right
				var m = inputs.length - 1;
				while ( inputs[m] <= ' ' ) {
					m--;
				}

				var result = '';
				for (var i = k; i <= m; i++) {
					result += inputs[i];
				}

				if ( result == '' ) {
					return '';
				}

				REPL.history[REPL.history.length] = result;

				return result;

			})()));

		} catch (ERROR) {

			WScript.StdErr.WriteLine(WScript.ScriptName
				+ ': "<stdin>", line ' + REPL.number
				+ ': ' + ERROR.name
				+ ': ' + ERROR.message);

		}

	} // while ( true )
};
]]></script>
<!-- <script language="javascript" src="./wsx/CommandLine.js"></script> -->
<script language="javascript"><![CDATA[
//
// Command Line processor
// This script is the part of the wsx
//
// Copyright (c) 2019, 2020 by Ildar Shaimordanov
//

(function(Program, Runner, REPL) {

	var argv = [];

	function ShowVersion() {
		var me = WScript.ScriptName.replace(/(\.[^.]+\?)?\.[^.]+$/, '');
		var name = typeof NAME == 'string' ? NAME : me;
		var version = typeof VERSION == 'string' ? VERSION : '0.0.1';
		WScript.Echo(name + ' (' + me + '): Version ' + version
			+ '\n' + WScript.Name
			+ ': Version ' + WScript.Version
			+ ', Build ' + WScript.BuildVersion);
	}

	// Walk through all named and unnamed arguments because
	// we have to handle each of them even if they duplicate
	for (var i = 0; i < WScript.Arguments.length; i++) {

		var arg = WScript.Arguments.Item(i);

		var m;

		m = arg.match(/^\/h(?:elp)?$/i);
		if ( m ) {
			WScript.Arguments.ShowUsage();
			WScript.Quit();
		}

		m = arg.match(/^\/version$/i);
		if ( m ) {
			ShowVersion();
			WScript.Quit();
		}

		m = arg.match(/^\/dry-run$/i);
		if ( m ) {
			Program.dryRun = true;
			continue;
		}

		m = arg.match(/^\/q(?:uiet)?$/i);
		if ( m ) {
			Program.setQuiet();
			continue;
		}

		m = arg.match(/^\/use:(js|vbs)$/i);
		if ( m ) {
			Program.setEngine(m[1]);
			continue;
		}

		m = arg.match(/^\/m(?::(js|vbs))?:(.+)$/i);
		if ( m ) {
			Program.addModule(m[1], m[2]);
			continue;
		}

		m = arg.match(/^\/(let|set|get)(?::(js|vbs))?:(\w+)=(.*)$/i);
		if ( m ) {
			Program.addVar(m[2], m[3], m[4], m[1]);
			continue;
		}

		m = arg.match(/^\/(?:e|((?:begin|end)(?:file)?))(?::(js|vbs))?(?::(.*))?$/i);
		if ( m ) {
			Program.addCode(m[2], m[3], m[1]);
			continue;
		}

		m = arg.match(/^\/([np])$/i);
		if ( m ) {
			Program.setMode(m[1]);
			continue;
		}

		/*
		This looks like ugly, but it works and reliable enough to
		stop looping over the rest of the CLI options. From this
		point we allow end users to specify their own options even,
		if their names intersect with names of our options.
		*/
		break;

	}

	for ( ; i < WScript.Arguments.length; i++) {
		var arg = WScript.Arguments.Item(i);
		argv.push(arg);
	}

	Program.detectScriptFile(argv);

	Runner(Program, argv);

})(Program, Runner, REPL);
]]></script>

</job>
</package>
( 2 * b ) || ! ( 2 * b )