First draft defining the structure of the program, needs to implement «imports» and the actual compilation
This commit is contained in:
commit
3ebe1040dd
10 changed files with 311 additions and 0 deletions
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# ---> Haskell
|
||||||
|
dist
|
||||||
|
dist-*
|
||||||
|
cabal-dev
|
||||||
|
*.o
|
||||||
|
*.hi
|
||||||
|
*.chi
|
||||||
|
*.chs.h
|
||||||
|
*.dyn_o
|
||||||
|
*.dyn_hi
|
||||||
|
.hpc
|
||||||
|
.hsenv
|
||||||
|
.cabal-sandbox/
|
||||||
|
cabal.sandbox.config
|
||||||
|
*.prof
|
||||||
|
*.aux
|
||||||
|
*.hp
|
||||||
|
*.eventlog
|
||||||
|
.stack-work/
|
||||||
|
cabal.project.local
|
||||||
|
cabal.project.local~
|
||||||
|
.HTF/
|
||||||
|
.ghc.environment.*
|
||||||
|
|
5
CHANGELOG.md
Normal file
5
CHANGELOG.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# Revision history for SJW
|
||||||
|
|
||||||
|
## 0.1.0.0 -- YYYY-mm-dd
|
||||||
|
|
||||||
|
* First version. Released on an unsuspecting world.
|
30
LICENSE
Normal file
30
LICENSE
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
Copyright (c) 2019, Tissevert
|
||||||
|
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following
|
||||||
|
disclaimer in the documentation and/or other materials provided
|
||||||
|
with the distribution.
|
||||||
|
|
||||||
|
* Neither the name of Tissevert nor the names of other
|
||||||
|
contributors may be used to endorse or promote products derived
|
||||||
|
from this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
3
README.md
Normal file
3
README.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# SJW
|
||||||
|
|
||||||
|
The Simple Javascript Wrench is a tool made to compile a set of independant Javascript modules into a single executable javascript file as expected by a webbrowser. It allows you to write (relatively) clean Javascript and to package it into one single script that will run once the page loading it is loaded.
|
36
SJW.cabal
Normal file
36
SJW.cabal
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
cabal-version: >=1.10
|
||||||
|
-- Initial package description 'SJW.cabal' generated by 'cabal init'. For
|
||||||
|
-- further documentation, see http://haskell.org/cabal/users-guide/
|
||||||
|
|
||||||
|
name: SJW
|
||||||
|
version: 0.1.0.0
|
||||||
|
synopsis: The Simple Javascript Wrench is a very simple tool to pack several JS «modules» into a single script.
|
||||||
|
-- description:
|
||||||
|
homepage: https://git.marvid.fr/Tissevert/SJW
|
||||||
|
-- bug-reports:
|
||||||
|
license: BSD3
|
||||||
|
license-file: LICENSE
|
||||||
|
author: Tissevert
|
||||||
|
maintainer: tissevert+devel@marvid.fr
|
||||||
|
-- copyright:
|
||||||
|
category: Web
|
||||||
|
build-type: Simple
|
||||||
|
extra-source-files: CHANGELOG.md
|
||||||
|
|
||||||
|
executable sjw
|
||||||
|
main-is: Main.hs
|
||||||
|
other-modules: Compiler
|
||||||
|
, Context
|
||||||
|
, Module
|
||||||
|
, Paths_SJW
|
||||||
|
-- other-extensions:
|
||||||
|
build-depends: attoparsec
|
||||||
|
, base >=4.12 && <4.13
|
||||||
|
, containers
|
||||||
|
, directory
|
||||||
|
, filepath
|
||||||
|
, mtl
|
||||||
|
, optparse-applicative
|
||||||
|
, text
|
||||||
|
hs-source-dirs: src
|
||||||
|
default-language: Haskell2010
|
2
Setup.hs
Normal file
2
Setup.hs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
import Distribution.Simple
|
||||||
|
main = defaultMain
|
33
src/Compiler.hs
Normal file
33
src/Compiler.hs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
{-# LANGUAGE NamedFieldPuns #-}
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
module Compiler (
|
||||||
|
main
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Context (Contextual)
|
||||||
|
import Data.Text (Text, cons)
|
||||||
|
import qualified Data.Text as Text (concat)
|
||||||
|
import Module (Module(..))
|
||||||
|
|
||||||
|
indent :: [Text] -> [Text]
|
||||||
|
indent = fmap (cons '\t')
|
||||||
|
|
||||||
|
include :: Bool -> Module -> Text
|
||||||
|
include isMain (Module {imports, payload}) = Text.concat $
|
||||||
|
header isMain
|
||||||
|
: indent payload
|
||||||
|
++ footer isMain
|
||||||
|
where
|
||||||
|
header True = "(function() {\n"
|
||||||
|
header False = "modules[''] = (function() {\n"
|
||||||
|
footer True = ["})());"]
|
||||||
|
footer False = [
|
||||||
|
"})());"
|
||||||
|
, "Object.freeze(modules['']);"
|
||||||
|
]
|
||||||
|
|
||||||
|
scan :: Contextual ()
|
||||||
|
scan = undefined
|
||||||
|
|
||||||
|
main :: Contextual Text
|
||||||
|
main = undefined
|
34
src/Context.hs
Normal file
34
src/Context.hs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
module Context (
|
||||||
|
CodePath(..)
|
||||||
|
, Context(..)
|
||||||
|
, Contextual
|
||||||
|
, Path(..)
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Control.Monad.RWS (RWST)
|
||||||
|
import Data.List (intercalate)
|
||||||
|
import Data.Map (Map)
|
||||||
|
import Text.ParserCombinators.ReadP (char, munch, sepBy)
|
||||||
|
import Text.ParserCombinators.ReadPrec (lift)
|
||||||
|
import Text.Read (readPrec)
|
||||||
|
|
||||||
|
newtype Path = Path [String]
|
||||||
|
newtype CodePath = CodePath [FilePath]
|
||||||
|
type Dependencies = Map Path [Path]
|
||||||
|
|
||||||
|
data Context = Context {
|
||||||
|
codePaths :: CodePath
|
||||||
|
, mainModule :: Path
|
||||||
|
}
|
||||||
|
|
||||||
|
type Contextual = RWST Context [String] Dependencies IO
|
||||||
|
|
||||||
|
instance Show Path where
|
||||||
|
show (Path components) = intercalate "." components
|
||||||
|
|
||||||
|
instance Read Path where
|
||||||
|
readPrec = fmap Path . lift $
|
||||||
|
munch (/= '.') `sepBy` char '.'
|
||||||
|
|
||||||
|
instance Show CodePath where
|
||||||
|
show (CodePath dirs) = intercalate ":" dirs
|
75
src/Main.hs
Normal file
75
src/Main.hs
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
{-# LANGUAGE NamedFieldPuns #-}
|
||||||
|
module Main where
|
||||||
|
|
||||||
|
import qualified Compiler (main)
|
||||||
|
import Context (CodePath(..), Context (..), Path(..))
|
||||||
|
import Control.Applicative (many, optional)
|
||||||
|
import Control.Monad.RWS (evalRWST)
|
||||||
|
import qualified Data.Map as Map (empty)
|
||||||
|
import Data.Text (Text)
|
||||||
|
import qualified Data.Text as Text (unpack)
|
||||||
|
import Data.Version (showVersion)
|
||||||
|
import qualified Module (parse)
|
||||||
|
import Options.Applicative (
|
||||||
|
Parser, execParser, fullDesc, info, header, help, helper, long, metavar
|
||||||
|
, option, short, str, strArgument, strOption, value
|
||||||
|
)
|
||||||
|
import qualified Paths_SJW as SJW (version)
|
||||||
|
import System.IO (stderr, hPutStrLn)
|
||||||
|
|
||||||
|
data Config = Config {
|
||||||
|
includes :: [FilePath]
|
||||||
|
, mainIs :: String
|
||||||
|
, outputFile :: FilePath
|
||||||
|
, target :: FilePath
|
||||||
|
} deriving (Show)
|
||||||
|
|
||||||
|
configParser :: Parser Config
|
||||||
|
configParser = Config
|
||||||
|
<$> many (strOption (
|
||||||
|
long "include"
|
||||||
|
<> short 'I'
|
||||||
|
<> metavar "PACKAGE"
|
||||||
|
<> help "Include this package during compilation"
|
||||||
|
))
|
||||||
|
<*> strOption (
|
||||||
|
long "main-is"
|
||||||
|
<> short 'm'
|
||||||
|
<> metavar "MODULE_NAME"
|
||||||
|
<> help "The name of the main module containing the code to run"
|
||||||
|
<> value "Main"
|
||||||
|
)
|
||||||
|
<*> strOption (
|
||||||
|
long "output"
|
||||||
|
<> short 'o'
|
||||||
|
<> metavar "OUTPUT_PATH"
|
||||||
|
<> help "The path where to create the compiled script (stdout if \"-\" or if the option is missing)"
|
||||||
|
<> value "-"
|
||||||
|
)
|
||||||
|
<*> strArgument (
|
||||||
|
metavar "SOURCE_DIR"
|
||||||
|
<> help "The path where to look for the sources"
|
||||||
|
)
|
||||||
|
|
||||||
|
getConfig :: IO Config
|
||||||
|
getConfig = execParser $
|
||||||
|
info
|
||||||
|
(helper <*> configParser)
|
||||||
|
(fullDesc <> header ("SJW v" ++ showVersion SJW.version))
|
||||||
|
|
||||||
|
runCompiler :: Config -> IO (Text, [String])
|
||||||
|
runCompiler (Config {includes, mainIs, target}) =
|
||||||
|
flip (evalRWST Compiler.main) Map.empty $ Context {
|
||||||
|
codePaths = CodePath (target : includes)
|
||||||
|
, mainModule = read mainIs
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: IO ()
|
||||||
|
main = do
|
||||||
|
config@(Config {outputFile}) <- getConfig
|
||||||
|
(sourceCode, logs) <- runCompiler config
|
||||||
|
mapM_ (hPutStrLn stderr) logs
|
||||||
|
output outputFile . Text.unpack $ sourceCode
|
||||||
|
where
|
||||||
|
output "-" = putStr
|
||||||
|
output fileName = writeFile fileName
|
69
src/Module.hs
Normal file
69
src/Module.hs
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
{-# LANGUAGE NamedFieldPuns #-}
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
module Module (
|
||||||
|
Module(..)
|
||||||
|
, parse
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Context (CodePath(..), Context(..), Contextual, Path(..))
|
||||||
|
import Control.Applicative ((<|>))
|
||||||
|
import Control.Monad.IO.Class (MonadIO(..))
|
||||||
|
import Control.Monad.Reader (asks)
|
||||||
|
import Data.Attoparsec.Text (
|
||||||
|
Parser, char, parseOnly, sepBy, string, takeTill, takeWhile
|
||||||
|
)
|
||||||
|
import Data.Text (Text)
|
||||||
|
import qualified Data.Text as Text (concat, pack)
|
||||||
|
import Prelude hiding (takeWhile)
|
||||||
|
import System.Directory (doesFileExist)
|
||||||
|
import System.FilePath ((</>), (<.>))
|
||||||
|
import Text.Printf (printf)
|
||||||
|
|
||||||
|
type ImportTree = Int
|
||||||
|
data Mapping = Mapping {
|
||||||
|
exposedName :: String
|
||||||
|
, sourceObject :: String
|
||||||
|
}
|
||||||
|
|
||||||
|
data Module = Module {
|
||||||
|
imports :: ImportTree
|
||||||
|
, payload :: [Text]
|
||||||
|
} deriving Show
|
||||||
|
|
||||||
|
importParser :: Parser [Mapping]
|
||||||
|
importParser =
|
||||||
|
string "import" *> space *> pure [] <* space <* char ';'
|
||||||
|
where
|
||||||
|
space = takeWhile (`elem` [' ', '\t'])
|
||||||
|
|
||||||
|
treeOf :: [[Mapping]] -> ImportTree
|
||||||
|
treeOf = foldl (\n l -> n + length l) 0
|
||||||
|
|
||||||
|
moduleParser :: Parser Module
|
||||||
|
moduleParser = Module
|
||||||
|
<$> (treeOf <$> importParser `sepBy` blank)
|
||||||
|
<*> line `sepBy` eol
|
||||||
|
where
|
||||||
|
eol = string "\r\n" <|> string "\r" <|> string "\n"
|
||||||
|
blank = takeWhile (`elem` [' ', '\t', '\r', '\n'])
|
||||||
|
line = takeTill (`elem` ['\r', '\n'])
|
||||||
|
|
||||||
|
parse :: Path -> Contextual (Either String Module)
|
||||||
|
parse path = do
|
||||||
|
searchPath <- asks codePaths
|
||||||
|
maybeSource <- find searchPath path
|
||||||
|
case maybeSource of
|
||||||
|
Nothing -> return . Left $
|
||||||
|
printf "Module %s not found in paths : %s" (show path) (show searchPath)
|
||||||
|
Just source ->
|
||||||
|
parseOnly moduleParser . Text.pack <$> liftIO (readFile source)
|
||||||
|
|
||||||
|
find :: MonadIO m => CodePath -> Path -> m (Maybe FilePath)
|
||||||
|
find (CodePath []) _ = return Nothing
|
||||||
|
find (CodePath (dir:otherDirs)) path@(Path components) = do
|
||||||
|
fileExists <- liftIO $ doesFileExist filePath
|
||||||
|
if fileExists
|
||||||
|
then return (Just filePath)
|
||||||
|
else find (CodePath otherDirs) path
|
||||||
|
where
|
||||||
|
filePath = foldl (</>) dir components <.> "js"
|
Loading…
Reference in a new issue