├── cabal.project ├── cabal.haskell-ci ├── stack.yaml ├── stack-8.0.2.yaml ├── stack-8.2.2.yaml ├── stack-8.4.4.yaml ├── stack-8.6.5.yaml ├── stack-8.8.4.yaml ├── test ├── List.hs ├── AST.hs ├── Exp.hs ├── CMM.hs ├── straightLine.hs ├── customVisitorsStraightLine.hs ├── Makefile └── MJAbsSyn.hs ├── src ├── Version.hs ├── String1.hs ├── Lexer.x ├── Parser.y ├── Main.hs ├── Options.hs ├── Syntax.hs └── Printer.hs ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── java-adt.cabal ├── README.md └── .github └── workflows └── haskell-ci.yml /cabal.project: -------------------------------------------------------------------------------- 1 | packages: . 2 | -------------------------------------------------------------------------------- /cabal.haskell-ci: -------------------------------------------------------------------------------- 1 | branches: master 2 | -------------------------------------------------------------------------------- /stack.yaml: -------------------------------------------------------------------------------- 1 | resolver: nightly-2025-11-04 2 | -------------------------------------------------------------------------------- /stack-8.0.2.yaml: -------------------------------------------------------------------------------- 1 | resolver: lts-9.21 2 | packages: 3 | - . 4 | -------------------------------------------------------------------------------- /stack-8.2.2.yaml: -------------------------------------------------------------------------------- 1 | resolver: lts-11.22 2 | packages: 3 | - . 4 | -------------------------------------------------------------------------------- /stack-8.4.4.yaml: -------------------------------------------------------------------------------- 1 | resolver: lts-12.26 2 | packages: 3 | - . 4 | -------------------------------------------------------------------------------- /stack-8.6.5.yaml: -------------------------------------------------------------------------------- 1 | resolver: lts-14.27 2 | packages: 3 | - . 4 | -------------------------------------------------------------------------------- /stack-8.8.4.yaml: -------------------------------------------------------------------------------- 1 | resolver: lts-16.31 2 | packages: 3 | - . 4 | -------------------------------------------------------------------------------- /test/List.hs: -------------------------------------------------------------------------------- 1 | data List A = Nil | Cons { head :: A, tail :: List A } 2 | -------------------------------------------------------------------------------- /test/AST.hs: -------------------------------------------------------------------------------- 1 | data Exp 2 | = EId { x :: String } 3 | | EAdd { e1 :: Exp, e2 :: Exp } 4 | | ESub { e1 :: Exp, e2 :: Exp } 5 | --visitor void EvalVisitor 6 | -------------------------------------------------------------------------------- /test/Exp.hs: -------------------------------------------------------------------------------- 1 | data Exp 2 | = EInt { i :: Integer } 3 | | EAdd { e1 :: Exp, e2 :: Exp } 4 | | ECall { f :: String, es :: [Exp] } 5 | --visitor Integer EvalVisitor 6 | -------------------------------------------------------------------------------- /test/CMM.hs: -------------------------------------------------------------------------------- 1 | -- C-- syntax 2 | 3 | data Exp 4 | = EId { x :: String } 5 | | EAdd { e1 :: Exp, e2 :: Exp } 6 | --visitor void EvalVisitor 7 | 8 | data Stm 9 | = SExp { e :: Exp } 10 | | SIf { e :: Exp, s1 :: Stm, s2 :: Stm } 11 | -- --visitor void EvalVisitor -- FAILS 12 | -------------------------------------------------------------------------------- /src/Version.hs: -------------------------------------------------------------------------------- 1 | module Version ( version ) where 2 | 3 | import Data.List ( intercalate ) 4 | import Data.Version ( Version(versionBranch) ) 5 | 6 | import qualified Paths_java_adt as Paths 7 | 8 | -- | The program version obtained from the cabal file. 9 | 10 | version :: String 11 | version = intercalate "." $ map show $ versionBranch Paths.version 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.aux 2 | *.class 3 | *.chi 4 | *.chs.h 5 | *.dyn_hi 6 | *.dyn_o 7 | *.eventlog 8 | *.hi 9 | *.hp 10 | *.lock 11 | *.o 12 | *.prof 13 | *~ 14 | .cabal-sandbox/ 15 | .ghc.environment.* 16 | .hpc 17 | .hsenv 18 | .stack-work/ 19 | /src/Lexer.hs 20 | /src/Parser.hs 21 | /test/*.java 22 | /test/out/ 23 | cabal-dev 24 | cabal.project.local 25 | cabal.sandbox.config 26 | dist 27 | dist-* 28 | -------------------------------------------------------------------------------- /src/String1.hs: -------------------------------------------------------------------------------- 1 | -- | Non-empty strings. 2 | 3 | module String1 4 | ( String1 5 | , fromString, toString 6 | , module X 7 | ) where 8 | 9 | import Data.List.NonEmpty 10 | import Data.List.NonEmpty as X ( pattern (:|), head ) 11 | 12 | type List1 = NonEmpty 13 | type String1 = List1 Char 14 | 15 | -- | Partial function, raises error on empty string. 16 | fromString :: String -> String1 17 | fromString = fromList 18 | 19 | toString :: String1 -> String 20 | toString = toList 21 | -------------------------------------------------------------------------------- /test/straightLine.hs: -------------------------------------------------------------------------------- 1 | -- Haskell grammar for StraightLine abs. syn. 2 | 3 | data Stm 4 | = CompoundStm { stm1 :: Stm, stm2 :: Stm } 5 | | AssignStm { id :: String, exp :: Exp } 6 | | PrintStm { exps :: ExpList } 7 | 8 | data Exp 9 | = IdExp { id :: String } 10 | | NumExp { num :: int } 11 | | OpExp { left :: Exp, op :: int, right :: Exp } 12 | | EseqExp { stm :: Stm, exp :: Exp } 13 | 14 | data ExpList 15 | = PairExpList { head :: Exp, tail :: ExpList } 16 | | LastExpList { head :: Exp } 17 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changelog for java-adt 2 | ====================== 3 | 4 | 1.0.20251105 5 | ------------ 6 | 7 | - Drop support for GHC < 8.4. 8 | - Add option `--version`. 9 | - Address GHC warning `-Wx-partial`. 10 | - Tested with GHC 8.4.4 - 9.12.2. 11 | 12 | 1.0.20231204 13 | ------------ 14 | 15 | - Switch to Haskell Package Versioning Policy. 16 | - Correct sdist tarball 17 | (`cabal install java-adt-0.2018.11.4` fails due to packaging of `Lexer.hs` and `Parser.hs`). 18 | - Tested with GHC 8.0 - 9.8. 19 | 20 | 0.2018.11.4 21 | ----------- 22 | 23 | - Fixed compilation with ghc-8.4.4 and ghc-8.6.1. 24 | -------------------------------------------------------------------------------- /test/customVisitorsStraightLine.hs: -------------------------------------------------------------------------------- 1 | -- Haskell grammar for StraightLine abs. syn. 2 | 3 | data Stm 4 | = CompoundStm { stm1 :: Stm, stm2 :: Stm } 5 | | AssignStm { id :: String, exp :: Exp } 6 | | PrintStm { exps :: ExpList } 7 | --visitor void RunVisitor 8 | 9 | data Exp 10 | = IdExp { id :: String } 11 | | NumExp { num :: int } 12 | | OpExp { left :: Exp, op :: int, right :: Exp } 13 | | EseqExp { stm :: Stm, exp :: Exp } 14 | --visitor int EvalVisitor 15 | 16 | data ExpList 17 | = PairExpList { head :: Exp, tail :: ExpList } 18 | | LastExpList { head :: Exp } 19 | --visitor void PrintVisitor 20 | -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | java-adt=$(shell cabal list-bin java-adt) 2 | 3 | .PRECIOUS : out/*.java out/%.java out 4 | 5 | .PHONY: all 6 | all : out out/Exp.class out/CMM.class out/List.class Stm.class out/CustomVisitorsStraightLine.class out/MJAbsSyn.class 7 | 8 | # create default visitor 9 | out/List.java : List.hs 10 | $(java-adt) -d -o $@ $< 11 | 12 | # create public files 13 | Stm.java : straightLine.hs 14 | $(java-adt) -p $< 15 | 16 | out/CustomVisitorsStraightLine.java : customVisitorsStraightLine.hs 17 | $(java-adt) -o $@ $< 18 | 19 | # create default visitor 20 | out/MJAbsSyn.java : MJAbsSyn.hs 21 | $(java-adt) -d -o $@ $< 22 | 23 | # don't create default visitor 24 | out/%.java : %.hs 25 | $(java-adt) -o $@ $< 26 | 27 | %.class : %.java 28 | javac $< 29 | 30 | out : 31 | -mkdir $@ 32 | 33 | .PHONY: debug 34 | debug: 35 | @echo "java-adt = $(java-adt)" 36 | 37 | # EOF 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /test/MJAbsSyn.hs: -------------------------------------------------------------------------------- 1 | -- Grammar for MJ ASTs 2 | 3 | data Maybe a 4 | = Nothing 5 | | Just { just :: a } 6 | 7 | data Decl = 8 | MethDecl { ty :: Type, id :: String, ps :: [Param] 9 | , ds :: [Decl], ss :: [Statement], e :: Expression } 10 | | VarDecl { ty :: Type, id :: String} 11 | | ClassDecl { id :: String, id0 :: Maybe String, vs :: [Decl], ms :: [Decl]} 12 | | MainDecl { id :: String, id0 :: String, s :: Statement} 13 | 14 | data Type = BoolTy | IntTy | ObjTy { c :: Class } | ArrTy { ty :: Type } 15 | 16 | data Param = Par { id :: String, ty :: Type } 17 | 18 | data Statement 19 | = SList { ss :: [Statement] } 20 | | IfStm { e :: Expression, sT :: Statement, sE :: Statement } 21 | | WhileStm { e :: Expression, s :: Statement } 22 | | PrintStm { e :: Expression } 23 | | AssignStm { id :: String, e :: Expression } 24 | | ArrAssignStm { id :: String, e1 :: Expression, e2 :: Expression } 25 | 26 | data Expression 27 | = CTrue 28 | | CFalse 29 | | This 30 | | NewInt { e :: Expression} 31 | | New { id :: String} 32 | | Infx { e1 :: Expression, op :: int, e2 :: Expression } 33 | | Get { e1 :: Expression, e2 :: Expression } 34 | | Len { e :: Expression } 35 | | Invoke { e :: Expression, id :: String, es :: [Expression] } 36 | | IConst { n :: int } 37 | | Id { id :: String } 38 | -------------------------------------------------------------------------------- /src/Lexer.x: -------------------------------------------------------------------------------- 1 | { 2 | module Lexer where 3 | 4 | import String1 (String1, fromString) 5 | } 6 | 7 | %wrapper "basic" 8 | 9 | $digit = 0-9 10 | $alpha = [ A-Z a-z ] 11 | 12 | tokens :- 13 | 14 | $white+ ; 15 | "--"[^v].* ; 16 | "--"v[^i].* ; 17 | "--"vi[^s].* ; 18 | "--"vis[^i].* ; 19 | "--"visi[^t].* ; 20 | "--"visit[^o].* ; 21 | "--"visito[^r].* ; 22 | "--visitor" { \ _ -> TkVisitor } 23 | 24 | data { \ _ -> TkData } 25 | \| { \ _ -> TkBar } 26 | \, { \ _ -> TkComma } 27 | \; { \ _ -> TkSemi } 28 | \:\: { \ _ -> TkColon } 29 | \= { \ _ -> TkEqual } 30 | \{ { \ _ -> TkOpenBrace } 31 | \} { \ _ -> TkCloseBrace } 32 | \[ { \ _ -> TkOpenSquare } 33 | \] { \ _ -> TkCloseSquare } 34 | \( { \ _ -> TkOpenParen } 35 | \) { \ _ -> TkCloseParen } 36 | 37 | [$alpha $digit \_ \' \< \>]+ { TkId . fromString } 38 | 39 | { 40 | 41 | data Token 42 | = TkId String1 43 | | TkData | TkVisitor 44 | | TkOpenBrace | TkCloseBrace 45 | | TkOpenSquare | TkCloseSquare 46 | | TkOpenParen | TkCloseParen 47 | | TkEqual | TkColon 48 | | TkComma | TkSemi 49 | | TkBar 50 | | TkEOF 51 | deriving Show 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/Parser.y: -------------------------------------------------------------------------------- 1 | { 2 | module Parser where 3 | 4 | import Syntax 5 | import Lexer 6 | } 7 | 8 | %name parser Decls 9 | %tokentype { Token } 10 | 11 | 12 | %token 13 | id { TkId $$ } 14 | '::' { TkColon } 15 | data { TkData } 16 | visitor { TkVisitor } 17 | '|' { TkBar } 18 | '{' { TkOpenBrace } 19 | '}' { TkCloseBrace } 20 | '[' { TkOpenSquare } 21 | ']' { TkCloseSquare } 22 | '(' { TkOpenParen } 23 | ')' { TkCloseParen } 24 | '=' { TkEqual } 25 | ',' { TkComma } 26 | 27 | %% 28 | 29 | Decls : MaybeDefs { $1 } 30 | 31 | MaybeDefs 32 | : { [] } 33 | | Def MaybeDefs { $1 : $2 } 34 | 35 | Def : data id Params '=' Constrs Visitors { Data $2 $3 $5 $6 } 36 | 37 | Params 38 | : { [] } 39 | | id Params { $1 : $2} 40 | 41 | -- no empty types 42 | 43 | Constrs 44 | : Constr '|' Constrs { $1 : $3 } 45 | | Constr { [$1] } 46 | 47 | Constr 48 | : id Fields { Constructor $1 $2 } 49 | | id { Constructor $1 [] } 50 | 51 | Fields : '{' FieldList '}' { $2 } 52 | 53 | FieldList 54 | : id '::' Type ',' FieldList { Field $1 $3 : $5 } 55 | | id '::' Type { [Field $1 $3] } 56 | 57 | Type 58 | : Type Atom { App $1 $2 } 59 | | Atom { $1 } 60 | 61 | Atom 62 | : id { Name $1 } 63 | | '[' Type ']' { List $2 } 64 | | '(' Type ')' { $2 } 65 | 66 | Visitors 67 | : visitor id id Visitors { Visitor $3 (TypeId $2) : $4 } 68 | | { [] } 69 | 70 | { 71 | 72 | happyError = error "Parse error" 73 | 74 | } 75 | -------------------------------------------------------------------------------- /java-adt.cabal: -------------------------------------------------------------------------------- 1 | name: java-adt 2 | version: 1.0.20251105 3 | build-type: Simple 4 | cabal-version: 2.0 5 | license: OtherLicense 6 | license-file: LICENSE 7 | author: Andreas Abel 8 | maintainer: Andreas Abel 9 | homepage: https://github.com/andreasabel/java-adt 10 | bug-reports: https://github.com/andreasabel/java-adt/issues 11 | category: Java 12 | synopsis: Create immutable algebraic data structures for Java. 13 | description: 14 | A simple tool to create immutable algebraic data structures and visitors for Java 15 | (such as abstract syntax trees). The input syntax is similar to Haskell data types, 16 | and they will be compiled to Java class hierarchies. 17 | 18 | tested-with: 19 | GHC == 9.12.2 20 | GHC == 9.10.3 21 | GHC == 9.8.4 22 | GHC == 9.6.7 23 | GHC == 9.4.8 24 | GHC == 9.2.8 25 | GHC == 9.0.2 26 | GHC == 8.10.7 27 | GHC == 8.8.4 28 | GHC == 8.6.5 29 | GHC == 8.4.4 30 | 31 | data-files: 32 | test/Makefile 33 | test/*.hs 34 | 35 | extra-doc-files: 36 | CHANGELOG.md 37 | README.md 38 | 39 | source-repository head 40 | type: git 41 | location: https://github.com/andreasabel/java-adt.git 42 | 43 | executable java-adt 44 | 45 | -- Project definition 46 | hs-source-dirs: 47 | src 48 | main-is: 49 | Main.hs 50 | other-modules: 51 | Lexer 52 | Options 53 | Parser 54 | Printer 55 | Syntax 56 | String1 57 | Version 58 | Paths_java_adt 59 | autogen-modules: 60 | Paths_java_adt 61 | 62 | -- Dependencies 63 | build-depends: 64 | base >= 4.11 && < 5 65 | , array 66 | , pretty 67 | build-tool-depends: 68 | alex:alex >= 3.0 69 | , happy:happy >= 1.15 70 | 71 | -- Haskell language settings 72 | default-language: 73 | Haskell2010 74 | default-extensions: 75 | LambdaCase 76 | PatternSynonyms 77 | FlexibleInstances 78 | TypeSynonymInstances 79 | ghc-options: 80 | -Wall 81 | -Wcompat 82 | -Wno-name-shadowing 83 | -Wno-unused-imports 84 | -------------------------------------------------------------------------------- /src/Main.hs: -------------------------------------------------------------------------------- 1 | -- A generator for immutable JAVA datastructures which simulate Haskell data types 2 | -- Author : Andreas Abel 3 | -- Created : 2005-10-17 4 | -- Modified: 2016-11-28, 2025-11-05 5 | 6 | module Main where 7 | 8 | import System.Environment 9 | import System.IO 10 | 11 | import Syntax 12 | import Lexer 13 | import Parser 14 | import Options 15 | import Printer 16 | import String1 (String1, pattern (:|), fromString, toString) 17 | 18 | main :: IO () 19 | main = do 20 | -- Parse command line. 21 | (opt, src, dest) <- parseCmdLine =<< getArgs 22 | -- Parse input file. 23 | ds' <- parser . alexScanTokens <$> readFile src 24 | -- Optionally add default visitor to each class. 25 | let ds = if defaultVisitor opt then map addDefaultVisitor ds' 26 | else ds' 27 | -- Print the structures to .java files. 28 | outputClasses opt dest $ 29 | ds >>= \ d -> dataToClasses opt d ++ dataToVisitors opt d 30 | 31 | -- | Add default visitor to existing visitors of a data type. 32 | addDefaultVisitor :: Data -> Data 33 | addDefaultVisitor (Data id params cs vs) = Data id params cs $ 34 | Visitor (id <> fromString "Visitor") (Gen fresh) : vs 35 | where 36 | fresh :: String1 37 | fresh = case filter (`notElem` params) $ map (:|[]) $ ['R' .. 'Z'] ++ ['A' .. 'Q'] of 38 | (x:_) -> x 39 | [] -> error "addDefaultVisitor: too many type parameters" 40 | 41 | -- | Print generated classes to file or to individual files. 42 | outputClasses :: Options -> Maybe String -> [Class] -> IO () 43 | outputClasses opts dest cs 44 | | pubClasses opts = mapM_ printToFile cs 45 | | otherwise = oneFile dest cs 46 | 47 | -- | Each class one file. 48 | printToFile :: Class -> IO () 49 | printToFile (Class id usesList body) = do 50 | let name = toString id ++ ".java" 51 | createFile name usesList $ body ++ "\n" 52 | 53 | -- | One file for all classes. 54 | oneFile :: Maybe String -> [Class] -> IO () 55 | oneFile mName cs = do 56 | let contents = cs >>= \ (Class _ _ body) -> body ++ "\n\n" 57 | maybe (putStr contents) 58 | (\ name -> createFile name (any classUsesList cs) contents) 59 | mName 60 | 61 | -- | Dump contents into file. 62 | createFile :: String -> Bool -> String -> IO () 63 | createFile name usesList contents = do 64 | hPutStrLn stderr ("creating " ++ name) 65 | writeFile name $ header name usesList ++ contents 66 | 67 | -- | Java file header. Possibly import statement for List. 68 | header :: String -> Bool -> String 69 | header name usesList = 70 | "// " ++ name ++ "\n// Created by github.com/andreasabel/java-adt\n\n" ++ 71 | if usesList then "import java.util.List;\n\n" else "" 72 | -------------------------------------------------------------------------------- /src/Options.hs: -------------------------------------------------------------------------------- 1 | module Options where 2 | 3 | import Control.Monad 4 | 5 | import System.Console.GetOpt 6 | import System.Exit 7 | import System.IO 8 | 9 | import Version (version) 10 | 11 | -- | Entry point: parse command line arguments. 12 | -- Print usage information and exit upon error. 13 | parseCmdLine :: [String] -> IO (Options, String, Maybe String) 14 | parseCmdLine argv = do 15 | let (os, ns, errs) = getOpt Permute optDescrs argv 16 | when (Version `elem` os) printVersion 17 | when (Help `elem` os) $ halt [] 18 | case (ns, errs) of 19 | ([n] , []) -> do 20 | (opts, dest) <- foldM (flip doFlag) (emptyOpts, Nothing) os 21 | return (opts, n, dest) 22 | ([_] , _) -> halt errs 23 | ([] , _) -> halt $ "no input file" : errs 24 | ((_:_:_), _) -> halt $ "too many input files (just one allowed)" : errs 25 | 26 | -- | Options for @java-adt@ program. 27 | data Options = Options 28 | { pubClasses :: Bool 29 | , defaultVisitor :: Bool 30 | } 31 | 32 | -- | Default options. 33 | emptyOpts :: Options 34 | emptyOpts = Options 35 | { pubClasses = False -- Don't output generated classes as @public@. 36 | , defaultVisitor = False -- Don't add default visitors. 37 | } 38 | 39 | -- | Helper data structure for @getOpt@. 40 | 41 | data Flag 42 | = Public 43 | | Help 44 | | Version 45 | | Visit 46 | | Output String 47 | deriving (Eq, Show) 48 | 49 | optDescrs :: [OptDescr Flag] 50 | optDescrs = 51 | [ Option ['h','?'] ["help"] (NoArg Help) "show usage information" 52 | , Option ['V'] ["version"](NoArg Version)"show program version" 53 | , Option ['p'] ["public"] (NoArg Public) "produce public classes (many files)" 54 | , Option ['o'] ["output"] (ReqArg Output "FILE") "output file" 55 | , Option ['d'] ["defaultVisitor"] (NoArg Visit) "produce default visitor (Java 1.5)" 56 | ] 57 | 58 | -- | Process a command line option. Halt if contradictory. 59 | doFlag :: Flag -> (Options, Maybe String) -> IO (Options, Maybe String) 60 | doFlag Visit (opts, s ) = return (opts{ defaultVisitor = True }, s) 61 | doFlag Public (opts, Nothing) = return (opts{ pubClasses = True }, Nothing) 62 | doFlag (Output s) (opts, Nothing) 63 | | not (pubClasses opts) = return (opts, Just s) 64 | doFlag _ _ = halt ["cannot use both -o and -p\n"] 65 | 66 | -- | Print errors and usage information. 67 | halt :: [String] -> IO a 68 | halt errs = do 69 | hPutStrLn stderr $ concat errs ++ usageInfo header optDescrs 70 | exitFailure 71 | where header = "usage: java-adt [OPTION...] " 72 | 73 | -- | Print program version and exit 74 | printVersion :: IO a 75 | printVersion = do 76 | putStrLn $ unwords [ "java-adt", version ] 77 | exitFailure 78 | -------------------------------------------------------------------------------- /src/Syntax.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveFoldable #-} 2 | 3 | {- | 4 | 5 | Example: (only ground inductive types) 6 | @ 7 | data D = C1 { x1 :: D1, ..., xn ::Dn } 8 | | C2 ... 9 | ... 10 | @ 11 | is printed as 12 | @ 13 | public abstract class D {} 14 | 15 | public class C1 extends D { 16 | public D1 x1; 17 | ... 18 | public Dn xn; 19 | public C1 (D1 x1, ..., Dn xn) { 20 | this.x1 = x1; 21 | ... 22 | this xn = xn; 23 | } 24 | } 25 | @ 26 | @ 27 | data D = C1 { x1 :: D1, ..., xn ::Dn } 28 | | C2 ... 29 | ... 30 | --visitor E1 V1 31 | --visitor E2 V2 32 | @ 33 | is printed as 34 | @ 35 | class D { 36 | public E1 accept (V1 v) { 37 | return v.visit (this); 38 | } 39 | public E2 accept (V2 v) { 40 | return v.visit (this); 41 | } 42 | } 43 | 44 | class C1 extends D { 45 | public D1 x1; 46 | ... 47 | public Dn xn; 48 | public C1 (D1 x1, ..., Dn xn) { 49 | this.x1 = x1; 50 | ... 51 | this xn = xn; 52 | } 53 | } 54 | @ 55 | the visitor interface is created as follows 56 | @ 57 | interface V1 { 58 | public E1 visit (C1 x); 59 | public E1 visit (C2 x); 60 | ... 61 | } 62 | @ 63 | Example: 64 | @ 65 | data List a = Nil | Cons { head :: a, tail :: List a } 66 | @ 67 | becomes 68 | @ 69 | abstract class List {} 70 | 71 | class Nil extends List { 72 | Nil () {} 73 | } 74 | 75 | class Cons extends List { 76 | public a head; 77 | public List tail; 78 | Cons (a head, List tail) { 79 | this.head = head; 80 | this.tail = tail; 81 | } 82 | } 83 | @ 84 | 85 | Grammar: 86 | @ 87 | datadecl :: 'data' uppername '=' constructors visitors 88 | 89 | constructor :: uppername [fields] 90 | 91 | fields :: '{' fieldlist '}' 92 | 93 | fieldlist :: fieldlist ',' field 94 | | field 95 | 96 | field :: lowername '::' type 97 | 98 | type :: type atom 99 | | atom 100 | 101 | atom :: name 102 | | '[' type ']' 103 | | '(' type ') 104 | 105 | visitor :: '--visitor' name name 106 | @ 107 | -} 108 | 109 | module Syntax where 110 | 111 | import Data.Monoid 112 | import String1 (String1, toString) 113 | 114 | type FieldId = String1 115 | type ConstrId = String1 116 | type DataId = String1 117 | type Param = String1 118 | type ClassId = String1 119 | 120 | data TypeId 121 | = TypeId String1 122 | | Gen String1 -- ^ Type variable. 123 | deriving (Eq, Show) 124 | 125 | data Visitor = Visitor 126 | { name :: ClassId 127 | , returnType :: TypeId 128 | } deriving Show 129 | 130 | data Type 131 | = List Type -- ^ @List@ 132 | | App Type Type -- ^ @Type@ 133 | | Name String1 -- ^ @Type@ 134 | deriving (Eq, Show) 135 | 136 | data Field' a = Field 137 | { fieldId :: FieldId 138 | , fieldType :: a 139 | } deriving (Show, Foldable) 140 | 141 | data Constructor' a = Constructor 142 | { constrId :: ConstrId 143 | , constrFields :: [Field' a] 144 | } deriving (Show, Foldable) 145 | 146 | data Data' a = Data 147 | { dataId :: DataId 148 | , dataParams :: [Param] 149 | , dataConstructors :: [Constructor' a] 150 | , dataVisitors :: [Visitor] 151 | } deriving (Show, Foldable) 152 | 153 | type Field = Field' Type 154 | type Constructor = Constructor' Type 155 | type Data = Data' Type 156 | 157 | usesList :: Foldable f => f Type -> Bool 158 | usesList = getAny . foldMap (Any . isList) 159 | where 160 | isList :: Type -> Bool 161 | isList (List _) = True 162 | isList _ = False 163 | 164 | isTypeVoid :: TypeId -> Bool 165 | isTypeVoid = \case 166 | TypeId s -> toString s == "void" 167 | Gen _ -> False 168 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | java-adt 2 | ======== 3 | 4 | A tool to create immutable algebraic data structures and visitors for Java 5 | (such as abstract syntax trees). The input syntax is similar to Haskell data types, 6 | and they will be compiled to Java class hierarchies. 7 | 8 | Installation 9 | ------------ 10 | 11 | With a running [Haskell installation](https://www.haskell.org/ghcup/), simply type into your shell 12 | ``` 13 | cabal install java-adt 14 | ``` 15 | and make sure your `.cabal/bin/` (or similar) is part of your system PATH. 16 | 17 | Example 1: Immutable linked lists with default visitor 18 | ------------------------------------------------------ 19 | 20 | Input: List.hs 21 | ```haskell 22 | data List A = Nil | Cons { head :: A, tail :: List A } 23 | ``` 24 | Invocation `java-adt List.hs` prints to standard output: 25 | ```java 26 | abstract class List { 27 | } 28 | 29 | class Nil extends List { 30 | public Nil () { 31 | } 32 | } 33 | 34 | class Cons extends List { 35 | public A head; 36 | public List tail; 37 | public Cons (A head, List tail) { 38 | this.head = head; 39 | this.tail = tail; 40 | } 41 | } 42 | ``` 43 | Invocation: `java-adt -o List.java List.hs` leaves output in `List.java`. 44 | 45 | Invocation: `java-adt -d List.hs` outputs same but with default visitor on standard output: 46 | ```java 47 | abstract class List { 48 | public abstract R accept (ListVisitor v); 49 | } 50 | 51 | class Nil extends List { 52 | public Nil () { 53 | } 54 | public R accept (ListVisitor v) { 55 | return v.visit (this); 56 | } 57 | } 58 | 59 | class Cons extends List { 60 | public A head; 61 | public List tail; 62 | public Cons (A head, List tail) { 63 | this.head = head; 64 | this.tail = tail; 65 | } 66 | public R accept (ListVisitor v) { 67 | return v.visit (this); 68 | } 69 | } 70 | 71 | interface ListVisitor { 72 | public R visit (Nil l); 73 | public R visit (Cons l); 74 | } 75 | ``` 76 | 77 | Example 2: A simple AST with custom visitor 78 | ------------------------------------------- 79 | 80 | Input file `Exp.hs`: (Note the use of Haskell lists in `[Exp]`) 81 | ```haskell 82 | data Exp 83 | = EInt { i :: Integer } 84 | | EAdd { e1 :: Exp, e2 :: Exp } 85 | | ECall { f :: String, es :: [Exp] } 86 | --visitor Integer EvalVisitor 87 | ``` 88 | Invocation `java-ast -o Exp.java Exp.hs` outputs into `Exp.java`: 89 | ```java 90 | import java.util.List; 91 | 92 | abstract class Exp { 93 | public abstract Integer accept (EvalVisitor v); 94 | } 95 | 96 | class EInt extends Exp { 97 | public Integer i; 98 | public EInt (Integer i) { 99 | this.i = i; 100 | } 101 | public Integer accept (EvalVisitor v) { 102 | return v.visit (this); 103 | } 104 | } 105 | 106 | class EAdd extends Exp { 107 | public Exp e1; 108 | public Exp e2; 109 | public EAdd (Exp e1, Exp e2) { 110 | this.e1 = e1; 111 | this.e2 = e2; 112 | } 113 | public Integer accept (EvalVisitor v) { 114 | return v.visit (this); 115 | } 116 | } 117 | 118 | class ECall extends Exp { 119 | public String f; 120 | public List es; 121 | public ECall (String f, List es) { 122 | this.f = f; 123 | this.es = es; 124 | } 125 | public Integer accept (EvalVisitor v) { 126 | return v.visit (this); 127 | } 128 | } 129 | 130 | interface EvalVisitor { 131 | public Integer visit (EInt e); 132 | public Integer visit (EAdd e); 133 | public Integer visit (ECall e); 134 | } 135 | 136 | ``` 137 | 138 | Input file grammar 139 | ------------------ 140 | 141 | The input file format is similar to Haskell data type declarations, 142 | with the special comment `--visitor`. 143 | ``` 144 | datadecl ::= 'data' uppername '=' constructors visitors 145 | 146 | constructor ::= uppername ['{' fieldlist '}'] 147 | 148 | fieldlist ::= fieldlist ',' field 149 | | field 150 | 151 | field ::= lowername '::' type 152 | 153 | type ::= type atom 154 | | atom 155 | 156 | atom ::= name 157 | | '[' type ']' 158 | | '(' type ')' 159 | 160 | visitor ::= '--visitor' type name 161 | ``` 162 | 163 | Limitations 164 | ----------- 165 | 166 | - Visitors do not support mutually recursive data types. 167 | - Record types with same constructor name as record name do not produce valid Java. E.g. 168 | ```haskell 169 | data R = R { f :: A } 170 | ``` 171 | creates two classes with name `R` and subsequent Java compilation errors. 172 | -------------------------------------------------------------------------------- /src/Printer.hs: -------------------------------------------------------------------------------- 1 | module Printer where 2 | 3 | import Prelude hiding ((<>)) -- requires GHC >= 7.6 to not be an error 4 | 5 | import Data.Char (toLower) 6 | import Text.PrettyPrint 7 | ( Doc, ($+$), (<+>), (<>) 8 | , cat, char, comma, equals, lbrace, nest, parens, punctuate, rbrace, render, semi, space, vcat ) 9 | import qualified Text.PrettyPrint as Pretty 10 | 11 | import Syntax 12 | import Options 13 | import String1 (String1) 14 | import qualified String1 15 | 16 | type ClassDef = String 17 | 18 | data Class = Class 19 | { classId :: ClassId 20 | , classUsesList :: Bool 21 | , classDef :: ClassDef 22 | } 23 | 24 | -- | Indentation level 25 | ind :: Int 26 | ind = 4 27 | 28 | -- | Entry point 1: construct visitors for data type. 29 | 30 | dataToVisitors :: Options -> Data -> [Class] 31 | dataToVisitors opt d@(Data _ _ _ vs) = 32 | map (constrToVisitor opt d) vs 33 | 34 | constrToVisitor :: Options -> Data -> Visitor -> Class 35 | constrToVisitor opt d@(Data x params cs _) (Visitor name rt) = 36 | Class x (usesList d) $ render $ 37 | condsep (pubClasses opt) public $ 38 | interface <+> jApp (text name) (genReturnType rt ++ map text params) <+> lbrace 39 | $+$ 40 | (nest ind $ vcat $ 41 | map (\ (Constructor c _) -> 42 | public <+> flatReturnType rt <+> visit <+> 43 | parens (jApp (text c) (map text params) <+> var) <> semi) 44 | cs) 45 | $+$ rbrace 46 | where var = char $ toLower (String1.head x) 47 | 48 | -- | Entry point 2: construct abstract parent classes and subclasses for data type. 49 | 50 | dataToClasses :: Options -> Data -> [Class] 51 | dataToClasses opt d@(Data x params cs vs) = cl : map (constrToClass opt x params vs) cs 52 | where 53 | cl = Class x (usesList d) . render $ 54 | publicOpt $ abstract <+> 55 | text "class" <+> jApp (text x) (map text params) <+> lbrace 56 | $+$ (nest ind $ vcat $ 57 | map (\ (Visitor name rt) -> 58 | public <+> abstract <+> quantReturnType rt <+> 59 | accept <+> parens (jApp (text name) (genReturnType rt ++ map text params) <+> v) <> semi) 60 | vs) 61 | $+$ rbrace 62 | publicOpt doc | pubClasses opt = public <+> doc 63 | | otherwise = doc 64 | 65 | constrToClass :: Options -> DataId -> [Param] -> [Visitor] -> Constructor -> Class 66 | constrToClass opt super params vs c@(Constructor x fs) = 67 | Class x (usesList c) . render $ 68 | (publicOpt $ text "class" <+> jApp (text x) (map text params) <+> 69 | extends <+> jApp (text super) (map text params) <+> lbrace) 70 | $+$ (nest ind $ 71 | (vcat (map (\ (Field f t) -> public <+> printType t <+> text f <> semi) fs)) 72 | $+$ 73 | (public <+> text x <+> 74 | parens (cat (punctuate (comma<>space) (map (\ (Field f t) -> printType t <+> text f) fs))) <+> 75 | lbrace 76 | $+$ (nest ind (vcat (map (\ (Field f _) -> this <> dot <> text f <+> equals <+> text f <> semi) fs))) 77 | $+$ rbrace) 78 | $+$ 79 | (vcat $ map (\ (Visitor name rt) -> 80 | public <+> quantReturnType rt <+> accept <+> 81 | parens (jApp (text name) (genReturnType rt ++ map text params) <+> v) <+> lbrace 82 | $+$ 83 | (nest ind $ condsep (not $ isTypeVoid rt) (text "return") 84 | $ v <> dot <> visit <+> (parens this) <> semi) 85 | $+$ rbrace) vs) 86 | ) 87 | $+$ rbrace 88 | where 89 | publicOpt doc | pubClasses opt = public <+> doc 90 | | otherwise = doc 91 | 92 | -- | Java type application @T@. 93 | jApp :: Doc -> [Doc] -> Doc 94 | jApp d1 [] = d1 95 | jApp d1 ds = d1 <> text "<" <> cat (punctuate comma ds) <> text ">" 96 | 97 | printType :: Type -> Doc 98 | printType t = printType' t [] 99 | 100 | printType' :: Type -> [Type] -> Doc 101 | printType' (List t) ts = jApp (text "List") $ map printType $ t:ts 102 | printType' (App r s) ts = printType' r $ s:ts 103 | printType' (Name x) ts = jApp (text x) $ map printType $ ts 104 | 105 | flatReturnType :: TypeId -> Doc 106 | flatReturnType (TypeId s) = text s 107 | flatReturnType (Gen s) = text s 108 | 109 | quantReturnType :: TypeId -> Doc 110 | quantReturnType (TypeId s) = text s 111 | quantReturnType (Gen s) = text "<" <> text s <> text ">" <+> text s 112 | 113 | genReturnType :: TypeId -> [Doc] 114 | genReturnType (Gen s) = [text s] 115 | genReturnType (TypeId _) = [] 116 | 117 | condsep :: Bool -> Doc -> Doc -> Doc 118 | condsep True d1 d2 = d1 <+> d2 119 | condsep False _ d2 = d2 120 | 121 | dot, public, interface, abstract, extends, this, accept, v, visit :: Doc 122 | dot = text "." 123 | public = text "public" 124 | interface = text "interface" 125 | abstract = text "abstract" 126 | extends = text "extends" 127 | this = text "this" 128 | accept = text "accept" 129 | v = text "v" 130 | visit = text "visit" 131 | 132 | -- | Overload 'text' to also work with 'String1'. 133 | class PrettyString a where 134 | text :: a -> Doc 135 | 136 | instance PrettyString String where 137 | text = Pretty.text 138 | 139 | instance PrettyString String1 where 140 | text = Pretty.text . String1.toString 141 | -------------------------------------------------------------------------------- /.github/workflows/haskell-ci.yml: -------------------------------------------------------------------------------- 1 | # This GitHub workflow config has been generated by a script via 2 | # 3 | # haskell-ci 'github' 'java-adt.cabal' 4 | # 5 | # To regenerate the script (for example after adjusting tested-with) run 6 | # 7 | # haskell-ci regenerate 8 | # 9 | # For more information, see https://github.com/haskell-CI/haskell-ci 10 | # 11 | # version: 0.19.20250917 12 | # 13 | # REGENDATA ("0.19.20250917",["github","java-adt.cabal"]) 14 | # 15 | name: Haskell-CI 16 | on: 17 | push: 18 | branches: 19 | - master 20 | pull_request: 21 | branches: 22 | - master 23 | merge_group: 24 | branches: 25 | - master 26 | jobs: 27 | linux: 28 | name: Haskell-CI - Linux - ${{ matrix.compiler }} 29 | runs-on: ubuntu-24.04 30 | timeout-minutes: 31 | 60 32 | container: 33 | image: buildpack-deps:jammy 34 | continue-on-error: ${{ matrix.allow-failure }} 35 | strategy: 36 | matrix: 37 | include: 38 | - compiler: ghc-9.12.2 39 | compilerKind: ghc 40 | compilerVersion: 9.12.2 41 | setup-method: ghcup 42 | allow-failure: false 43 | - compiler: ghc-9.10.3 44 | compilerKind: ghc 45 | compilerVersion: 9.10.3 46 | setup-method: ghcup 47 | allow-failure: false 48 | - compiler: ghc-9.8.4 49 | compilerKind: ghc 50 | compilerVersion: 9.8.4 51 | setup-method: ghcup 52 | allow-failure: false 53 | - compiler: ghc-9.6.7 54 | compilerKind: ghc 55 | compilerVersion: 9.6.7 56 | setup-method: ghcup 57 | allow-failure: false 58 | - compiler: ghc-9.4.8 59 | compilerKind: ghc 60 | compilerVersion: 9.4.8 61 | setup-method: ghcup 62 | allow-failure: false 63 | - compiler: ghc-9.2.8 64 | compilerKind: ghc 65 | compilerVersion: 9.2.8 66 | setup-method: ghcup 67 | allow-failure: false 68 | - compiler: ghc-9.0.2 69 | compilerKind: ghc 70 | compilerVersion: 9.0.2 71 | setup-method: ghcup 72 | allow-failure: false 73 | - compiler: ghc-8.10.7 74 | compilerKind: ghc 75 | compilerVersion: 8.10.7 76 | setup-method: ghcup 77 | allow-failure: false 78 | - compiler: ghc-8.8.4 79 | compilerKind: ghc 80 | compilerVersion: 8.8.4 81 | setup-method: ghcup 82 | allow-failure: false 83 | - compiler: ghc-8.6.5 84 | compilerKind: ghc 85 | compilerVersion: 8.6.5 86 | setup-method: ghcup 87 | allow-failure: false 88 | - compiler: ghc-8.4.4 89 | compilerKind: ghc 90 | compilerVersion: 8.4.4 91 | setup-method: ghcup 92 | allow-failure: false 93 | fail-fast: false 94 | steps: 95 | - name: apt-get install 96 | run: | 97 | apt-get update 98 | apt-get install -y --no-install-recommends gnupg ca-certificates dirmngr curl git software-properties-common libtinfo5 libnuma-dev 99 | - name: Install GHCup 100 | run: | 101 | mkdir -p "$HOME/.ghcup/bin" 102 | curl -sL https://downloads.haskell.org/ghcup/0.1.50.1/x86_64-linux-ghcup-0.1.50.1 > "$HOME/.ghcup/bin/ghcup" 103 | chmod a+x "$HOME/.ghcup/bin/ghcup" 104 | - name: Install cabal-install 105 | run: | 106 | "$HOME/.ghcup/bin/ghcup" install cabal 3.16.0.0 || (cat "$HOME"/.ghcup/logs/*.* && false) 107 | echo "CABAL=$HOME/.ghcup/bin/cabal-3.16.0.0 -vnormal+nowrap" >> "$GITHUB_ENV" 108 | - name: Install GHC (GHCup) 109 | if: matrix.setup-method == 'ghcup' 110 | run: | 111 | "$HOME/.ghcup/bin/ghcup" install ghc "$HCVER" || (cat "$HOME"/.ghcup/logs/*.* && false) 112 | HC=$("$HOME/.ghcup/bin/ghcup" whereis ghc "$HCVER") 113 | HCPKG=$(echo "$HC" | sed 's#ghc$#ghc-pkg#') 114 | HADDOCK=$(echo "$HC" | sed 's#ghc$#haddock#') 115 | echo "HC=$HC" >> "$GITHUB_ENV" 116 | echo "HCPKG=$HCPKG" >> "$GITHUB_ENV" 117 | echo "HADDOCK=$HADDOCK" >> "$GITHUB_ENV" 118 | env: 119 | HCKIND: ${{ matrix.compilerKind }} 120 | HCNAME: ${{ matrix.compiler }} 121 | HCVER: ${{ matrix.compilerVersion }} 122 | - name: Set PATH and environment variables 123 | run: | 124 | echo "$HOME/.cabal/bin" >> $GITHUB_PATH 125 | echo "LANG=C.UTF-8" >> "$GITHUB_ENV" 126 | echo "CABAL_DIR=$HOME/.cabal" >> "$GITHUB_ENV" 127 | echo "CABAL_CONFIG=$HOME/.cabal/config" >> "$GITHUB_ENV" 128 | HCNUMVER=$(${HC} --numeric-version|perl -ne '/^(\d+)\.(\d+)\.(\d+)(\.(\d+))?$/; print(10000 * $1 + 100 * $2 + ($3 == 0 ? $5 != 1 : $3))') 129 | echo "HCNUMVER=$HCNUMVER" >> "$GITHUB_ENV" 130 | echo "ARG_TESTS=--enable-tests" >> "$GITHUB_ENV" 131 | echo "ARG_BENCH=--enable-benchmarks" >> "$GITHUB_ENV" 132 | echo "HEADHACKAGE=false" >> "$GITHUB_ENV" 133 | echo "ARG_COMPILER=--$HCKIND --with-compiler=$HC" >> "$GITHUB_ENV" 134 | env: 135 | HCKIND: ${{ matrix.compilerKind }} 136 | HCNAME: ${{ matrix.compiler }} 137 | HCVER: ${{ matrix.compilerVersion }} 138 | - name: env 139 | run: | 140 | env 141 | - name: write cabal config 142 | run: | 143 | mkdir -p $CABAL_DIR 144 | cat >> $CABAL_CONFIG <> $CABAL_CONFIG < cabal-plan.xz 177 | echo 'f62ccb2971567a5f638f2005ad3173dba14693a45154c1508645c52289714cb2 cabal-plan.xz' | sha256sum -c - 178 | xz -d < cabal-plan.xz > $HOME/.cabal/bin/cabal-plan 179 | rm -f cabal-plan.xz 180 | chmod a+x $HOME/.cabal/bin/cabal-plan 181 | cabal-plan --version 182 | - name: checkout 183 | uses: actions/checkout@v5 184 | with: 185 | path: source 186 | - name: initial cabal.project for sdist 187 | run: | 188 | touch cabal.project 189 | echo "packages: $GITHUB_WORKSPACE/source/." >> cabal.project 190 | cat cabal.project 191 | - name: sdist 192 | run: | 193 | mkdir -p sdist 194 | $CABAL sdist all --output-dir $GITHUB_WORKSPACE/sdist 195 | - name: unpack 196 | run: | 197 | mkdir -p unpacked 198 | find sdist -maxdepth 1 -type f -name '*.tar.gz' -exec tar -C $GITHUB_WORKSPACE/unpacked -xzvf {} \; 199 | - name: generate cabal.project 200 | run: | 201 | PKGDIR_java_adt="$(find "$GITHUB_WORKSPACE/unpacked" -maxdepth 1 -type d -regex '.*/java-adt-[0-9.]*')" 202 | echo "PKGDIR_java_adt=${PKGDIR_java_adt}" >> "$GITHUB_ENV" 203 | rm -f cabal.project cabal.project.local 204 | touch cabal.project 205 | touch cabal.project.local 206 | echo "packages: ${PKGDIR_java_adt}" >> cabal.project 207 | echo "package java-adt" >> cabal.project 208 | echo " ghc-options: -Werror=missing-methods -Werror=missing-fields" >> cabal.project 209 | if [ $((HCNUMVER >= 90400)) -ne 0 ] ; then echo "package java-adt" >> cabal.project ; fi 210 | if [ $((HCNUMVER >= 90400)) -ne 0 ] ; then echo " ghc-options: -Werror=unused-packages" >> cabal.project ; fi 211 | if [ $((HCNUMVER >= 90000)) -ne 0 ] ; then echo "package java-adt" >> cabal.project ; fi 212 | if [ $((HCNUMVER >= 90000)) -ne 0 ] ; then echo " ghc-options: -Werror=incomplete-patterns -Werror=incomplete-uni-patterns" >> cabal.project ; fi 213 | cat >> cabal.project <> cabal.project.local 216 | cat cabal.project 217 | cat cabal.project.local 218 | - name: dump install plan 219 | run: | 220 | $CABAL v2-build $ARG_COMPILER $ARG_TESTS $ARG_BENCH --dry-run all 221 | cabal-plan 222 | - name: restore cache 223 | uses: actions/cache/restore@v4 224 | with: 225 | key: ${{ runner.os }}-${{ matrix.compiler }}-${{ github.sha }} 226 | path: ~/.cabal/store 227 | restore-keys: ${{ runner.os }}-${{ matrix.compiler }}- 228 | - name: install dependencies 229 | run: | 230 | $CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks --dependencies-only -j2 all 231 | $CABAL v2-build $ARG_COMPILER $ARG_TESTS $ARG_BENCH --dependencies-only -j2 all 232 | - name: build w/o tests 233 | run: | 234 | $CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks all 235 | - name: build 236 | run: | 237 | $CABAL v2-build $ARG_COMPILER $ARG_TESTS $ARG_BENCH all --write-ghc-environment-files=always 238 | - name: cabal check 239 | run: | 240 | cd ${PKGDIR_java_adt} || false 241 | ${CABAL} -vnormal check 242 | - name: haddock 243 | run: | 244 | $CABAL v2-haddock --disable-documentation --haddock-all $ARG_COMPILER --with-haddock $HADDOCK $ARG_TESTS $ARG_BENCH all 245 | - name: unconstrained build 246 | run: | 247 | rm -f cabal.project.local 248 | $CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks all 249 | - name: save cache 250 | if: always() 251 | uses: actions/cache/save@v4 252 | with: 253 | key: ${{ runner.os }}-${{ matrix.compiler }}-${{ github.sha }} 254 | path: ~/.cabal/store 255 | --------------------------------------------------------------------------------