Обфускатор JS (Haskell)

Воскресенье, Ноябрь 7, 2010 г.
Сегодня в моей копилке пополнение. Речь пойдёт о довольно интересном языке программирования — Haskell. Традиционно не буду заострять внимание на самом языке. Описательной информации о нём в рунете вполне достаточно. Приведу лишь краткую выдержку из wikipedia:

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)
Немного ламерский вопрос. Вам не приходилось в регулярных выражениях делать такое: нужно использовать скобки (например - "(aaa)bbb(ccc|ddd)eee" - хотим выбрать только aaa), но чтобы взятый в скобки результат не возвращался, как совпадение. В регулярных выражениях Perl это делается так - "(aaa)bbb(?:ccc|ddd)eee", а как в Posix - не знаю даже, возможно ли такое.
demoriz
14.06.2011 в 11:30
Чтото я вопроса не понял.
Оставить комментарий   Нажмите, чтобы отменить ответ.
Доступен html впределах разумного. Для цитирования используйте <blockquote></blockquote>, для отрисовки программного кода [code][/code].
Для всяких хакеров и прочих: комментарии проходят санитизацию, всё лишнее будет вырезано. Так что не тратьте своё драгоценное время.
Имя (обязательно)
E-Mail (Не будет опубликован , обязательно)
Сайт (необязательно)