Обфускатор JS (Haskell)Воскресенье, Ноябрь 7, 2010 г. Háskell (русск. Ха́скель, Ха́скелл) — стандартизованный чистый функциональный язык программирования общего назначения. Является одним из самых распространённых языков программирования с поддержкой отложенных вычислений. Типизация в Хаскеле строгая, статическая, с автоматическим выводом типов. Поскольку язык функциональный, то основная управляющая структура — это функция. Серьёзное отношение к типизации — ещё одна отличительная черта Хаскеля. Концепция языка отражает идею математика Хаскелла Карри, писавшего, что «доказательство — это программа, а доказываемая формула — это тип программы». Почему Haskell? Я считаю что любой программист, даже самый закоренелый императивщик ;) должен хотя бы ознакомиться с принципами функционального программирования. Подобное расширение кругозора пойдёт только на пользу. Появится более глубокое понимание некоторых часто используемых приёмов и инструментов. Откроются новые пути решения задач. Более рациональные и изящные. Haskell привлёк меня сложностью и красотой исходного кода. Плотно заинтересовался этим языком я совсем недавно, поэтому на данный момент отношу себя к чайникам. В процессе неспешного изучения я попытаюсь написать и представить на всеобщее обозрение обфускатор для javascript. Мне показалось что обработка текста, задача для функционального языка вполне подходящая. Ну а javascript, был выбран спонтанно. Как говорится: а почему бы и нет? Начнём с постановки задачи. Что мы будем вкладывать в понятие обфускация. Мы попытаемся сделать исходный код js минималистичным и трудно читаемым сохранив его функциональность. Создавать код будем поэтапно. В этой статье начнём с удаления комментариев. В js два типа комментариев, блочные «/**/» и строчные «//». Вроде бы ничего сложного на первый взгляд. Но только на первый. Главное это учесть одно условие: открывающая комментарий последовательность «/*» для блочных или «//» для строчных, не должна быть замкнута в кавычки. То есть не являться частью строковой переменной. Как определить какая часть, правая или левая от кавычки является строкой а не кодом? Да ещё не забыть что кавычка находящаяся внутри строки может быть экранирована и не являться кавычкой играющей роль ограничителя. Над этим пришлось изрядно поломать голову. Сложность была не в самой задаче а в образе мышления. В голову лезли императивные решения - разбить на части, пройтись по частям в цикле и тд :). Хотелось найти изящное функциональное решение, ведь сам язык к этому распологает, и я его нашёл. Насколько изящное судить вам.
module Main (main) where
import System (getArgs)
import Text.Regex.Posix
import List
import Char
import Maybe
main = do
[f1,f2] <- getArgs
s <- readFile f1
writeFile f2 $ noLineComm $ noBlockComm s
-- удаляем блочные комментарии
noBlockComm :: String -> String
noBlockComm s
| isNothing $ c =
if isNothing $ findSubstr "/*" s
then s
else noBlockComm $ delBlockComm s
| isJust $ findSubstr "/*" x = noBlockComm $ delBlockComm s
| otherwise = x ++ y ++ noBlockComm z
where
c = firstQuote s
(x,y,z) = s =~ (fromJust c:".*[^\\]"++[fromJust c]) :: (String, String, String)
-- удаляем строчные комментарии
noLineComm :: String -> String
noLineComm s
| isNothing $ c =
if isNothing $ findSubstr "//" s
then s
else noLineComm $ delLineComm s
| isJust $ findSubstr "//" x = noLineComm $ delLineComm s
| otherwise = x ++ y ++ noLineComm z
where
c = firstQuote s
(x,y,z) = s =~ (fromJust c:".*[^\\]"++[fromJust c]) :: (String, String, String)
-- удаляем первый встреченный блок /* */
delBlockComm :: String -> String
delBlockComm s
| isNothing (findSubstr "/*" s) = s
| isNothing (findSubstr "*/" s) = s
| otherwise = x ++ z
where
(x,y,z) = s=~ "/\\*([^(\\*/)]|\n*)*\\*/" :: (String, String, String)
-- удаляем первый встреченный строчный комментарий //
delLineComm :: String -> String
delLineComm s
| isNothing (findSubstr "//" s) = s
| otherwise = x ++ z
where
(x,y,z) = s=~ "//.*" :: (String, String, String)
-- какая кавычка первая
firstQuote :: String -> Maybe Char
firstQuote s
| null s = Nothing
| notElem '\'' s && notElem '"' s = Nothing
| elem '\'' s && notElem '"' s = Just '\''
| notElem '\'' s && elem '"' s = Just '"'
| otherwise =
if elemIndex '\'' s > elemIndex '"' s
then Just '"'
else Just '\''
-- поиск подстроки
findSubstr :: String -> String -> Maybe Int
findSubstr xs ys = findIndex (isPrefixOf xs) (tails ys)
Собираем бинарник demoriz [~]% ghc --make js.hs Программа принимает 2 аргумента, путь до исходного js файла и путь для сохранения результата. demoriz [~]% ./js /path/before.js /path/after.js Если /path/after.js уже существует то он будет перезаписан. Если нет то создан. Код скорее всего не оптимален, но на данном уровне моего знакомства с Haskell как сделать лучше пока не вижу. Буду благодарен за любую аргументированную критику гуру :) Теги:
Haskell
| ||
Комментарии (2)
09.06.2011 в 15:17
Немного ламерский вопрос. Вам не приходилось в регулярных выражениях делать такое: нужно использовать скобки (например - "(aaa)bbb(ccc|ddd)eee" - хотим выбрать только aaa), но чтобы взятый в скобки результат не возвращался, как совпадение. В регулярных выражениях Perl это делается так - "(aaa)bbb(?:ccc|ddd)eee", а как в Posix - не знаю даже, возможно ли такое.
ответить
demoriz
14.06.2011 в 11:30
Чтото я вопроса не понял.
ответить
)
